/*  This file is part of the program psim.

    Copyright (C) 1994-1997, Andrew Cagney <cagney@highland.com.au>

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

#include "misc.h"
#include "lf.h"
#include "table.h"
#include "filter.h"

#include "igen.h"

#include "ld-insn.h"
#include "ld-decode.h"

#include "gen.h"

#include "gen-idecode.h"
#include "gen-engine.h"
#include "gen-icache.h"
#include "gen-semantics.h"


static void
print_run_body (lf *file,
	        gen_entry *table)
{
  /* Output the function to execute real code:
     
     Unfortunatly, there are multiple cases to consider vis:
     
     <icache> X <smp>
     
     Consequently this function is written in multiple different ways */
  
  lf_printf (file, "{\n");
  lf_indent (file, +2);
  if (!options.gen.smp)
    {
      lf_printf (file, "instruction_address cia;\n");
    }
  lf_printf (file, "int current_cpu = next_cpu_nr;\n");
  
  if (options.gen.icache)
    {
      lf_printf (file, "/* flush the icache of a possible break insn */\n");
      lf_printf (file, "{\n");
      lf_printf (file, "  int cpu_nr;\n");
      lf_printf (file, "  for (cpu_nr = 0; cpu_nr < nr_cpus; cpu_nr++)\n");
      lf_printf (file, "    cpu_flush_icache (STATE_CPU (sd, cpu_nr));\n");
      lf_printf (file, "}\n");
    }
  
  if (!options.gen.smp)
    {
      
      lf_putstr (file, "
/* CASE 1: NO SMP (with or with out instruction cache).

In this case, we can take advantage of the fact that the current
instruction address (CIA) does not need to be read from / written to
the CPU object after the execution of an instruction.

Instead, CIA is only saved when the main loop exits.  This occures
when either sim_engine_halt or sim_engine_restart is called.  Both of
these functions save the current instruction address before halting /
restarting the simulator.

As a variation, there may also be support for an instruction cracking
cache. */

");

      lf_putstr (file, "\n");
      lf_putstr (file, "/* prime the main loop */\n");
      lf_putstr (file, "SIM_ASSERT (current_cpu == 0);\n");
      lf_putstr (file, "SIM_ASSERT (nr_cpus == 1);\n");
      lf_putstr (file, "cia = CIA_GET (CPU);\n");

      lf_putstr (file, "\n");
      lf_putstr (file, "while (1)\n");
      lf_putstr (file, "  {\n");
      lf_indent (file, +4);
      
      lf_printf (file, "%sinstruction_address nia;\n",
		 options.prefix.global.name);

      lf_printf (file, "\n");
      if (!options.gen.icache)
	{
	  lf_printf (file, "%sinstruction_word instruction_0 = IMEM (cia);\n",
		     options.prefix.global.name);
	  lf_printf (file, "\n");
	  lf_indent_suppress (file);
	  lf_printf (file, "#if defined (%sENGINE_ISSUE_PREFIX_HOOK)\n",
		     options.prefix.global.name);
	  lf_printf (file, "%sENGINE_ISSUE_PREFIX_HOOK();\n",
		     options.prefix.global.name);
	  lf_indent_suppress (file);
	  lf_printf (file, "#endif\n");
	  lf_printf (file, "\n");
	  print_idecode_body (file, table, "nia = ");
	  lf_printf (file, "\n");
	  lf_indent_suppress (file);
	  lf_printf (file, "#if defined (%sENGINE_ISSUE_POSTFIX_HOOK)\n",
		     options.prefix.global.name);
	  lf_printf (file, "%sENGINE_ISSUE_POSTFIX_HOOK();\n",
		     options.prefix.global.name);
	  lf_indent_suppress (file);
	  lf_printf (file, "#endif\n");
	  lf_printf (file, "\n");
	}
      else
	{
	  lf_putstr (file, "idecode_cache *cache_entry =\n");
	  lf_putstr (file, "  cpu_icache_entry (cpu, cia);\n");
	  lf_putstr (file, "if (cache_entry->address == cia)\n");
	  lf_putstr (file, "  {\n");
	  lf_indent (file, -4);
	  lf_putstr (file, "/* cache hit */\n");
	  lf_putstr (file, "idecode_semantic *const semantic = cache_entry->semantic;\n");
	  lf_putstr (file, "cia = semantic (cpu, cache_entry, cia);\n");
	  /* tail */
	  lf_indent (file, -4);
	  lf_putstr (file, "  }\n");
	  lf_putstr (file, "else\n");
	  lf_putstr (file, "  {\n");
	  lf_indent (file, +4);
	  lf_putstr (file, "/* cache miss */\n");
	  if (!options.gen.semantic_icache)
	    {
	      lf_putstr (file, "idecode_semantic *semantic;\n");
	    }
	  lf_putstr (file, "instruction_word instruction = IMEM (cia);\n");
	  lf_putstr (file, "if (WITH_MON != 0)\n");
	  lf_putstr (file, "  mon_event (mon_event_icache_miss, cpu, cia);\n");
	  if (options.gen.semantic_icache)
	    {
	      lf_putstr (file, "{\n");
	      lf_indent (file, +2);
	      print_idecode_body (file, table, "nia =");
	      lf_indent (file, -2);
	      lf_putstr (file, "}\n");
	    }
	  else
	    {
	      print_idecode_body (file, table, "semantic =");
	      lf_putstr (file, "nia = semantic (cpu, cache_entry, cia);\n");
	    }
	  lf_indent (file, -4);
	  lf_putstr (file, "  }\n");
	}
      
      /* update the cpu if necessary */
      switch (options.gen.nia)
	{
	case nia_is_cia_plus_one:
	  lf_printf (file, "\n");
	  lf_printf (file, "/* Update the instruction address */\n");
	  lf_printf (file, "cia = nia;\n");
	  break;
	case nia_is_void:
	case nia_is_invalid:
	  ERROR ("engine gen when NIA complex");
	}

      /* events */
      lf_putstr (file, "\n");
      lf_putstr (file, "/* process any events */\n");
      lf_putstr (file, "if (sim_events_tick (sd))\n");
      lf_putstr (file, "  {\n");
      lf_putstr (file, "    CIA_SET (CPU, cia);\n");
      lf_putstr (file, "    sim_events_process (sd);\n");
      lf_putstr (file, "  }\n");

      lf_indent (file, -4);
      lf_printf (file, "  }\n");
    }
  
  if (options.gen.smp)
    {
      
      lf_putstr (file, "
/* CASE 2: SMP (With or without ICACHE)

The complexity here comes from needing to correctly halt the simulator
when it is aborted.  For instance, if cpu0 requests a restart then
cpu1 will normally be the next cpu that is run.  Cpu0 being restarted
after all the other CPU's and the event queue have been processed */

");
      
      lf_putstr (file, "\n");
      lf_printf (file, "/* have ensured that the event queue is NOT next */\n");
      lf_printf (file, "SIM_ASSERT (current_cpu >= 0);\n");
      lf_printf (file, "SIM_ASSERT (current_cpu <= nr_cpus - 1);\n");
      lf_printf (file, "SIM_ASSERT (nr_cpus <= MAX_NR_PROCESSORS);\n");

      lf_putstr (file, "\n");
      lf_putstr (file, "while (1)\n");
      lf_putstr (file, "  {\n");
      lf_indent (file, +4);
      lf_putstr (file, "sim_cpu *cpu = STATE_CPU (sd, current_cpu);\n");
      lf_putstr (file, "instruction_address cia = CIA_GET (cpu);\n");
      lf_putstr (file, "\n");

      if (!options.gen.icache)
	{
	  lf_putstr (file, "instruction_word instruction_0 = IMEM (cia);\n");
	  print_idecode_body (file, table, "cia =");
	  lf_putstr (file, "CIA_SET (cpu, cia);\n");
	}
      
      if (options.gen.icache)
	{
	  lf_putstr (file, "engine_cache *cache_entry =\n");
	  lf_putstr (file, "  cpu_icache_entry(processor, cia);\n");
	  lf_putstr (file, "\n");
	  lf_putstr (file, "if (cache_entry->address == cia) {\n");
	  {
	    lf_indent (file, +2);
	    lf_putstr (file, "\n");
	    lf_putstr (file, "/* cache hit */\n");
	    lf_putstr (file, "engine_semantic *semantic = cache_entry->semantic;\n");
	    lf_putstr (file, "cia = semantic(processor, cache_entry, cia);\n");
	    /* tail */
	    lf_putstr (file, "cpu_set_program_counter(processor, cia);\n");
	    lf_putstr (file, "\n");
	    lf_indent (file, -2);
	  }
	  lf_putstr (file, "}\n");
	  lf_putstr (file, "else {\n");
	  {
	    lf_indent (file, +2);
	    lf_putstr (file, "\n");
	    lf_putstr (file, "/* cache miss */\n");
	    if (!options.gen.semantic_icache)
	      {
		lf_putstr (file, "engine_semantic *semantic;\n");
	      }
	    lf_putstr (file, "instruction_word instruction = IMEM (cia);\n");
	    lf_putstr (file, "if (WITH_MON != 0)\n");
	    lf_putstr (file, "  mon_event(mon_event_icache_miss, processors[current_cpu], cia);\n");
	    if (options.gen.semantic_icache)
	      {
		lf_putstr (file, "{\n");
		lf_indent (file, +2);
		print_idecode_body(file, table, "cia =");
		lf_indent (file, -2);
		lf_putstr (file, "}\n");
	      }
	    else
	      {
		print_idecode_body(file, table, "semantic = ");
		lf_putstr (file, "cia = semantic(processor, cache_entry, cia);\n");
	      }
	    /* tail */
	    lf_putstr (file, "cpu_set_program_counter(processor, cia);\n");
	    lf_putstr (file, "\n");
	    lf_indent (file, -2);
	  }
	  lf_putstr (file, "}\n");
	}
      
      lf_putstr (file, "\n");
      lf_putstr (file, "current_cpu += 1;\n");
      lf_putstr (file, "if (current_cpu == nr_cpus)\n");
      lf_putstr (file, "  {\n");
      lf_putstr (file, "    if (sim_events_tick (sd))\n");
      lf_putstr (file, "      {\n");
      lf_putstr (file, "        sim_events_process (sd);\n");
      lf_putstr (file, "      }\n");
      lf_putstr (file, "    current_cpu = 0;\n");
      lf_putstr (file, "  }\n");
      
      /* tail */
      lf_indent (file, -4);
      lf_putstr (file, "  }\n");
    }
  
  
  lf_indent (file, -2);
  lf_putstr (file, "}\n");
}


/****************************************************************/

#if 0
static void
print_jump (lf *file,
	    int is_tail)
{
  if (!options.gen.smp)
    {
      lf_putstr (file, "if (event_queue_tick (sd))\n");
      lf_putstr (file, "  {\n");
      lf_putstr (file, "    CPU_CIA (processor) = nia;\n");
      lf_putstr (file, "    sim_events_process (sd);\n");
      lf_putstr (file, "  }\n");
      lf_putstr (file, "}\n");
    }
  
  if (options.gen.smp)
    {
      if (is_tail)
	lf_putstr (file, "cpu_set_program_counter(processor, nia);\n");
      lf_putstr (file, "current_cpu += 1;\n");
      lf_putstr (file, "if (current_cpu >= nr_cpus)\n");
      lf_putstr (file, "  {\n");
      lf_putstr (file, "    if (sim_events_tick (sd))\n");
      lf_putstr (file, "      {\n");
      lf_putstr (file, "        sim_events_process (sd);\n");
      lf_putstr (file, "      }\n");
      lf_putstr (file, "    current_cpu = 0;\n");
      lf_putstr (file, "  }\n");
      lf_putstr (file, "processor = processors[current_cpu];\n");
      lf_putstr (file, "nia = cpu_get_program_counter(processor);\n");
    }
  
  if (options.gen.icache)
    {
      lf_putstr (file, "cache_entry = cpu_icache_entry(processor, nia);\n");
      lf_putstr (file, "if (cache_entry->address == nia) {\n");
      lf_putstr (file, "  /* cache hit */\n");
      lf_putstr (file, "  goto *cache_entry->semantic;\n");
      lf_putstr (file, "}\n");
      if (is_tail) {
	lf_putstr (file, "goto cache_miss;\n");
      }
    }
  
  if (!options.gen.icache && is_tail)
    {
      lf_printf (file, "goto engine;\n");
    }
  
}
#endif


#if 0
static void
print_jump_insn (lf *file,
		 insn_entry *instruction,
		 opcode_bits *expanded_bits,
		 opcode_field *opcodes,
		 cache_entry *cache_rules)
{
  insn_opcodes opcode_path;

  memset (&opcode_path, 0, sizeof (opcode_path));
  opcode_path.opcode = opcodes;

  /* what we are for the moment */
  lf_printf (file, "\n");
  print_my_defines (file,
		    instruction->name,
		    instruction->format_name,
		    expanded_bits);
  
  /* output the icache entry */
  if (options.gen.icache)
    {
      lf_printf (file, "\n");
      lf_indent (file, -1);
      print_function_name (file,
			   instruction->name,
			   instruction->format_name,
			   NULL,
			   expanded_bits,
			   function_name_prefix_icache);
      lf_printf (file, ":\n");
      lf_indent (file, +1);
      lf_printf (file, "{\n");
      lf_indent (file, +2);
      lf_putstr (file, "const unsigned_word cia = nia;\n");
      print_itrace (file, instruction, 1/*putting-value-in-cache*/);
      print_idecode_validate (file, instruction, &opcode_path);
      lf_printf (file, "\n");
      lf_printf (file, "{\n");
      lf_indent (file, +2);
      print_icache_body (file,
			 instruction,
			 expanded_bits,
			 cache_rules,
			 0, /*use_defines*/
			 put_values_in_icache);
      lf_printf (file, "cache_entry->address = nia;\n");
      lf_printf (file, "cache_entry->semantic = &&");
      print_function_name (file,
			   instruction->name,
			   instruction->format_name,
			   NULL,
			   expanded_bits,
			   function_name_prefix_semantics);
      lf_printf (file, ";\n");
      if (options.gen.semantic_icache)
	{
	  print_semantic_body (file,
			       instruction,
			       expanded_bits,
			       &opcode_path);
	  print_jump(file, 1/*is-tail*/);
	}
      else
	{
	  lf_printf (file, "/* goto ");
	  print_function_name (file,
			       instruction->name,
			       instruction->format_name,
			       NULL,
			       expanded_bits,
			       function_name_prefix_semantics);
	  lf_printf (file, "; */\n");
	}
      lf_indent (file, -2);
      lf_putstr (file, "}\n");
      lf_indent (file, -2);
      lf_printf (file, "}\n");
    }
  
  /* print the semantics */
  lf_printf (file, "\n");
  lf_indent (file, -1);
  print_function_name (file,
		       instruction->name,
		       instruction->format_name,
		       NULL,
		       expanded_bits,
		       function_name_prefix_semantics);
  lf_printf (file, ":\n");
  lf_indent (file, +1);
  lf_printf (file, "{\n");
  lf_indent (file, +2);
  lf_putstr (file, "const unsigned_word cia = nia;\n");
  print_icache_body (file,
		     instruction,
		     expanded_bits,
		     cache_rules,
		     (options.gen.direct_access
		      ? define_variables
		      : declare_variables),
		     (options.gen.icache
		      ? get_values_from_icache
		      : do_not_use_icache));
  print_semantic_body (file,
		       instruction,
		       expanded_bits,
		       &opcode_path);
  if (options.gen.direct_access)
    print_icache_body (file,
		       instruction,
		       expanded_bits,
		       cache_rules,
		       undef_variables,
		       (options.gen.icache
			? get_values_from_icache
			: do_not_use_icache));
  print_jump(file, 1/*is tail*/);
  lf_indent (file, -2);
  lf_printf (file, "}\n");
}
#endif


#if 0
static void
print_jump_definition (lf *file,
		       gen_entry *entry,
		       int depth,
		       void *data)
{
  cache_entry *cache_rules = (cache_entry*)data;
  if (entry->opcode_rule->with_duplicates)
    {
      ASSERT (entry->nr_insns == 1
	      && entry->opcode == NULL
	      && entry->parent != NULL
	      && entry->parent->opcode != NULL);
      ASSERT (entry->nr_insns == 1
	      && entry->opcode == NULL
	      && entry->parent != NULL
	      && entry->parent->opcode != NULL
	      && entry->parent->opcode_rule != NULL);
      print_jump_insn (file,
		       entry->insns->insn,
		       entry->expanded_bits,
		       entry->opcode,
		       cache_rules);
    }
  else 
    {
      print_jump_insn (file,
		       entry->insns->insn,
		       NULL,
		       NULL,
		       cache_rules);
    }
}
#endif


#if 0
static void
print_jump_internal_function (lf *file,
			      function_entry *function,
			      void *data)
{
  if (function->is_internal)
    {
      lf_printf (file, "\n");
      lf_print__line_ref (file, function->line);
      lf_indent (file, -1);
      print_function_name (file,
			   function->name,
			   NULL,
			   NULL,
			   NULL,
			   (options.gen.icache
			    ? function_name_prefix_icache
			    : function_name_prefix_semantics));
      lf_printf (file, ":\n");
      lf_indent (file, +1);
      lf_printf (file, "{\n");
      lf_indent (file, +2);
      lf_printf (file, "const unsigned_word cia = nia;\n");
      table_print_code (file, function->code);
      lf_print__internal_ref (file);
      lf_printf (file, "error(\"Internal function must longjump\\n\");\n");
      lf_indent (file, -2);
      lf_printf (file, "}\n");
    }
}
#endif


#if 0
static void
print_jump_body (lf *file,
		 gen_entry *entry,
		 insn_table *isa,
		 cache_entry *cache_rules)
{
  lf_printf (file, "{\n");
  lf_indent (file, +2);
  lf_putstr (file, "jmp_buf halt;\n");
  lf_putstr (file, "jmp_buf restart;\n");
  lf_putstr (file, "cpu *processor = NULL;\n");
  lf_putstr (file, "unsigned_word nia = -1;\n");
  lf_putstr (file, "instruction_word instruction = 0;\n");
  if (options.gen.icache)
    {
      lf_putstr (file, "engine_cache *cache_entry = NULL;\n");
    }
  if (options.gen.smp)
    {
      lf_putstr (file, "int current_cpu = -1;\n");
    }
  
  /* all the switches and tables - they know about jumping */
  print_idecode_lookups (file, entry, cache_rules);
  
  /* start the simulation up */
  if (options.gen.icache)
    {
      lf_putstr (file, "\n");
      lf_putstr (file, "{\n");
      lf_putstr (file, "  int cpu_nr;\n");
      lf_putstr (file, "  for (cpu_nr = 0; cpu_nr < nr_cpus; cpu_nr++)\n");
      lf_putstr (file, "    cpu_flush_icache(processors[cpu_nr]);\n");
      lf_putstr (file, "}\n");
    }
  
  lf_putstr (file, "\n");
  lf_putstr (file, "psim_set_halt_and_restart(system, &halt, &restart);\n");
  
  lf_putstr (file, "\n");
  lf_putstr (file, "if (setjmp(halt))\n");
  lf_putstr (file, "  return;\n");
  
  lf_putstr (file, "\n");
  lf_putstr (file, "setjmp(restart);\n");
  
  lf_putstr (file, "\n");
  if (!options.gen.smp)
    {
      lf_putstr (file, "processor = processors[0];\n");
      lf_putstr (file, "nia = cpu_get_program_counter(processor);\n");
    }
  else
    {
      lf_putstr (file, "current_cpu = psim_last_cpu(system);\n");
    }
  
  if (!options.gen.icache)
    {
      lf_printf (file, "\n");
      lf_indent (file, -1);
      lf_printf (file, "engine:\n");
      lf_indent (file, +1);
    }
  
  print_jump(file, 0/*is_tail*/);
  
  if (options.gen.icache)
    {
      lf_indent (file, -1);
      lf_printf (file, "cache_miss:\n");
      lf_indent (file, +1);
    }
  
  lf_putstr (file, "instruction\n");
  lf_putstr (file, "  = vm_instruction_map_read(cpu_instruction_map(processor),\n");
  lf_putstr (file, "                            processor, nia);\n");
  print_idecode_body (file, entry, "/*IGORE*/");
  
  /* print out a table of all the internals functions */
  function_entry_traverse (file, isa->functions,
			   print_jump_internal_function,
			   NULL);
  
  /* print out a table of all the instructions */
  ERROR ("Use the list of semantic functions, not travere_tree");
  gen_entry_traverse_tree (file, entry,
			   1,
			   NULL, /* start */
			   print_jump_definition, /* leaf */
			   NULL, /* end */
			   cache_rules);
  lf_indent (file, -2);
  lf_printf (file, "}\n");
}
#endif


/****************************************************************/


void
print_engine_run_function_header (lf *file,
				  char *processor,
				  function_decl_type decl_type)
{
  int indent;
  lf_printf (file, "\n");
  switch (decl_type)
    {
    case is_function_declaration:
      lf_print__function_type (file, "void", "INLINE_ENGINE", "\n");
      break;
    case is_function_definition:
      lf_print__function_type (file, "void", "INLINE_ENGINE", " ");
      break;
    case is_function_variable:
      lf_printf (file, "void (*");
      break;
    }
  indent = print_function_name (file,
				"run",
				NULL, /* format name */
				processor,
				NULL, /* expanded bits */
				function_name_prefix_engine);
  switch (decl_type)
    {
    case is_function_definition:
      lf_putstr (file, "\n(");
      indent = 1;
      break;
    case is_function_declaration:
      indent += lf_printf (file, " (");
      break;
    case is_function_variable:
      lf_putstr (file, ")\n(");
      indent = 1;
      break;
    }
  lf_indent (file, +indent);
  lf_printf (file, "SIM_DESC sd,\n");
  lf_printf (file, "int next_cpu_nr,\n");
  lf_printf (file, "int nr_cpus,\n");
  lf_printf (file, "int siggnal)");
  lf_indent (file, -indent);
  switch (decl_type)
    {
    case is_function_definition:
      lf_putstr (file, "\n");
      break;
    case is_function_variable:
    case is_function_declaration:
      lf_putstr (file, ";\n");
      break;
    }
}


void
gen_engine_h (lf *file,
	      gen_table *gen,
	      insn_table *isa,
	      cache_entry *cache_rules)
{
  gen_list *entry;
  for (entry = gen->tables; entry != NULL; entry = entry->next)
    {
      print_engine_run_function_header (file,
					(options.gen.multi_sim
					 ? entry->model->name
					 : NULL),
					is_function_declaration);
    }
}


void
gen_engine_c(lf *file,
	     gen_table *gen,
	     insn_table *isa,
	     cache_entry *cache_rules)
{
  gen_list *entry;
  /* the intro */
  lf_printf (file, "#include \"sim-inline.c\"\n");
  lf_printf (file, "\n");
  lf_printf (file, "#include \"sim-main.h\"\n");
  lf_printf (file, "#include \"itable.h\"\n");
  lf_printf (file, "#include \"idecode.h\"\n");
  lf_printf (file, "#include \"semantics.h\"\n");
  lf_printf (file, "#include \"icache.h\"\n");
  lf_printf (file, "#include \"engine.h\"\n");
  lf_printf (file, "#include \"support.h\"\n");
  lf_printf (file, "\n");
  lf_printf (file, "#include \"sim-assert.h\"\n");
  lf_printf (file, "\n");
  print_idecode_globals (file);
  lf_printf (file, "\n");

  for (entry = gen->tables; entry != NULL; entry = entry->next)
    {
      switch (options.gen.code)
	{
	case generate_calls:
	  print_idecode_lookups (file, entry->table, cache_rules);
	  
	  /* output the main engine routine */
	  print_engine_run_function_header (file,
					    (options.gen.multi_sim
					     ? entry->model->name
					     : NULL),
					    is_function_definition);
	  print_run_body (file, entry->table);
	  break;
	  
	case generate_jumps:
	  ERROR ("Jumps currently unimplemented");
#if 0
	  print_engine_run_function_header (file,
					    entry->processor,
					    is_function_definition);
	  print_jump_body (file, entry->table,
			   isa, cache_rules);
#endif
	  break;
	}
    }
}
