/* SPIM S20 MIPS simulator.
   Terminal interface for SPIM simulator.
   Copyright (C) 1990-1994 by James Larus (larus@cs.wisc.edu).
   ALL RIGHTS RESERVED.

   SPIM is distributed under the following conditions:

     You may make copies of SPIM for your own use and modify those copies.

     All copies of SPIM must retain my name and copyright notice.

     You may not sell SPIM or distributed SPIM in conjunction with a
     commerical product or service without the expressed written consent of
     James Larus.

   THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
   IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   PURPOSE. */


/* $Header: /var/home/larus/Software/SPIM/RCS/spim.c,v 3.41 1994/01/18 14:47:12 larus Exp larus $
*/


#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#ifdef RS
/* This is problem on HP Snakes, which define RS in syscall.h */
#undef RS
#endif
#include <sys/types.h>
#ifdef AIXV3
#ifndef NBBY
#define NBBY 8
#endif
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sgtty.h>
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#include "spim.h"
#include "spim-utils.h"
#include "inst.h"
#include "mem.h"
#include "reg.h"
#include "parser.h"
#include "read-aout.h"
#include "sym-tbl.h"
#include "scanner.h"
#include "y.tab.h"

#ifdef CL_SPIM
#include "cl-tlb.h"
#include "cl-cache.h"
#include "cl-cycle.h"
#include "cl-except.h"
#include "mips-syscall.h"
#endif


/* Internal functions: */

#ifdef __STDC__
static void console_to_program (void);
static void console_to_spim (void);
static void flush_to_newline (void);
static long get_opt_int (void);
static int parse_spim_command (FILE *file, int redo);
static int print_reg (int reg_no, int type_code);
static int read_assembly_command (void);
static int str_prefix (char *s1, char *s2, int min_match);
static void top_level (void);
#else
static void console_to_program ();
static void console_to_spim ();
static void flush_to_newline ();
static long get_opt_int ();
static int parse_spim_command ();
static int print_reg ();
static int read_assembly_command ();
static int str_prefix ();
static void top_level ();
#endif


/* Exported Variables: */

/* Not local, but not export so all files don't need setjmp.h */
jmp_buf spim_top_level_env; /* For ^C */

int bare_machine;		/* Simulate bare instruction set */
int quiet;			/* No warning messages */
int source_file;		/* Program is source, not binary */
int message_out, console_out, console_in;
int mapped_io;			/* Non-zero => activate memory-mapped IO */
int pipe_out;
int cycle_level;		/* non-zero => cycle level mode */
mem_addr program_starting_address;
long initial_text_size;
long initial_data_size;
long initial_data_limit;
long initial_stack_size;
long initial_stack_limit;
long initial_k_text_size;
long initial_k_data_size;
long initial_k_data_limit;


/* Local variables: */

static int load_trap_handler = 1; /* Non-zero => load standard trap handler */
static int console_state_saved;
static struct sgttyb saved_console_state;



#ifdef __STDC__
int
main (int argc, char **argv)
#else
int
main (argc, argv)
     int argc;
     char **argv;
#endif
{
  int i;
  int assembly_file_read = 0, executable_file_read = 0;
#ifdef mips
  int executable_file_ptr = 0;
#endif

  console_out = (int) stdout;
  message_out = (int) stdout;

  /* Input comes directly (not through stdio): */
  console_in = 0;
  mapped_io = 0;

#ifdef CL_SPIM
  pipe_out = (int) stdout;

  /* cycle level defaults */
  cycle_level = 0;
  quiet = 1;
  tlb_on = 0;
  icache_on = 0;
  dcache_on = 0;
#endif

  write_startup_message ();

  for (i = 1; i < argc; i++)
    if (streq (argv [i], "-bare"))
      bare_machine = 1, quiet = 1;
    else if (streq (argv [i], "-asm"))
      bare_machine = 0;
    else if (streq (argv [i], "-trap"))
      load_trap_handler = 1;
    else if (streq (argv [i], "-notrap"))
      load_trap_handler = 0;
    else if (streq (argv [i], "-quiet"))
      quiet = 1;
    else if (streq (argv [i], "-noquiet"))
      quiet = 0;
    else if (streq (argv [i], "-file"))
      {
	if (!assembly_file_read)
	  {
	    initialize_world (load_trap_handler);
#ifdef CL_SPIM
	    cl_initialize_world (0);
#endif
	  }
	assembly_file_read |= !read_assembly_file (argv[++i]);
      }
    else if (streq (argv [i], "-mapped_io"))
      mapped_io = 1;
    else if (streq (argv [i], "-nomapped_io"))
      mapped_io = 0;
#ifdef mips
    else if (streq (argv [i], "-execute"))
      {
	executable_file_ptr = i;
	load_trap_handler = 0;
	if (executable_file_read)
	  error ("Can only load one executable file: %s not loaded\n",
		 argv[++i]);
	else
	  {
	    initialize_world (0);
#ifdef CL_SPIM
	    cl_initialize_world (0);
#endif
	    executable_file_read |= !read_aout_file (argv[++i]);
	  }
      }
#endif
    else if (streq (argv [i], "-stext"))
      initial_text_size = atoi (argv[++i]);
    else if (streq (argv [i], "-sdata"))
      initial_data_size = atoi (argv[++i]);
    else if (streq (argv [i], "-ldata"))
      initial_data_limit = atoi (argv[++i]);
    else if (streq (argv [i], "-sstack"))
      initial_stack_size = atoi (argv[++i]);
    else if (streq (argv [i], "-lstack"))
      initial_stack_limit = atoi (argv[++i]);

    else if (streq (argv [i], "-sktext"))
      initial_k_text_size = atoi (argv[++i]);
    else if (streq (argv [i], "-skdata"))
      initial_k_data_size = atoi (argv[++i]);
    else if (streq (argv [i], "-lkdata"))
      initial_k_data_limit = atoi (argv[++i]);

#ifdef CL_SPIM
    else if (streq (argv [i], "-cycle"))
      {
	cycle_level = 1;
	bare_machine = 1;
	load_trap_handler = 0;
      }
    else if (streq (argv [i], "-tlb"))
      tlb_on = 1;
    else if (streq (argv [i], "-notlb"))
      tlb_on = 0;
    else if (streq (argv [i], "-dcache"))
      dcache_on = 1;
    else if (streq (argv [i], "-nodcache"))
      dcache_on = 0;
    else if (streq (argv [i], "-icache"))
      icache_on = 1;
    else if (streq (argv [i], "-noicache"))
      icache_on = 0;
#endif
    else
      error ("usage: spim -bare/-asm -trap/-notrap -quiet/-noquiet -file/-execute -mapped_io/-nomapped_io <file>\n");


  if (!assembly_file_read && !executable_file_read)
    {
      initialize_world (load_trap_handler);
#ifdef CL_SPIM
      cl_initialize_world (0);
#endif
      top_level ();
    }
#ifdef mips
  else if (executable_file_read)
    {
      initialize_run_stack (1, &argv[executable_file_ptr]);
#ifdef CL_SPIM
      if (cycle_level == 1)
	{
	  cl_initialize_world (1);
	  cl_run_program(program_starting_address, DEFAULT_RUN_STEPS, !quiet);
	}
      else
#endif
	if (!setjmp (spim_top_level_env))
	  run_program (program_starting_address, DEFAULT_RUN_STEPS, 0, 0);
    }
#endif
  else if (assembly_file_read)
    {
      console_to_program ();
#ifdef CL_SPIM
      if (cycle_level == 1)
	{
	  cl_initialize_world (1);
	  cl_run_program(find_symbol_address(DEFAULT_RUN_LOCATION),
			 DEFAULT_RUN_STEPS, !quiet);
	}
      else
#endif
	if (!setjmp (spim_top_level_env))
	  run_program (find_symbol_address (DEFAULT_RUN_LOCATION),
		       DEFAULT_RUN_STEPS, 0, 0);
      console_to_spim ();
    }

  return (0);
}


/* Top-level read-eval-print loop for SPIM. */

#ifdef __STDC__
static void
top_level (void)
#else
static void
top_level ()
#endif
{
  int redo = 0;			/* Non-zero means reexecute last command */

  signal (SIGINT, control_c_seen);
  while (1)
    {
      if (!redo)
#ifdef CL_SPIM
	write_output (message_out, "(cl-spim) ");
#else
	write_output (message_out, "(spim) ");
#endif
      if (!setjmp (spim_top_level_env))
	redo = parse_spim_command (stdin, redo);
      else
	redo = 0;
      fflush (stdout);
      fflush (stderr);
    }
}


#ifdef __STDC__
void
control_c_seen (int arg)
#else
void
control_c_seen (arg)
int arg;
#endif
{
  console_to_spim ();
  write_output (message_out, "\nExecution interrupted\n");
  longjmp (spim_top_level_env, 1);
}


/* SPIM commands */

#define UNKNOWN_CMD		0
#define EXIT_CMD		1
#define READ_CMD		2
#define RUN_CMD			3
#define STEP_CMD		4
#define PRINT_CMD		5
#define PRINT_SYM_CMD		6
#define REINITIALIZE_CMD	7
#define ASM_CMD			8
#define REDO_CMD		9
#define NOP_CMD			10
#define HELP_CMD		11
#define CONTINUE_CMD		12
#define SET_BKPT_CMD		13
#define DELETE_BKPT_CMD		14
#define LIST_BKPT_CMD		15
#ifdef mips
#define EXECUTE_CMD		16
#endif
#ifdef CL_SPIM
#define INFO_CMD		17
#define HANDLE_CMD		18
#define SET_CMD			19
#endif


/* Parse a SPIM command from the FILE and execute it.  If REDO is non-zero,
   don't read a new command; just rexecute the previous one.
   Return non-zero if the command was to redo the previous command. */

#ifdef __STDC__
static int
parse_spim_command (FILE *file, int redo)
#else
static int
parse_spim_command (file, redo)
     FILE *file;
     int redo;
#endif
{
  static int prev_cmd = NOP_CMD; /* Default redo */
  static int prev_token;
  int cmd;

  initialize_scanner (file);
  initialize_parser ("<standard input>");
  switch (cmd = (redo ? prev_cmd : read_assembly_command ()))
    {
    case EXIT_CMD:
      console_to_spim ();
      exit (0);

    case READ_CMD:
      {
	int token = (redo ? prev_token : yylex ());

	if (!redo) flush_to_newline ();
	if (token == Y_STR)
	  {
	    read_assembly_file ((char *) yylval);
	    initialize_scanner (file); /* Reinitialize! */
	  }
	else
	  error ("Must supply a filename to read\n");
	prev_cmd = READ_CMD;
	return (0);
      }

    case RUN_CMD:
      {
	static mem_addr addr;

	addr = (redo ? addr : get_opt_int ());
	if (addr == 0)
	  addr = starting_address ();

	initialize_run_stack (0, 0);
	console_to_program ();
#ifdef CL_SPIM
	if (cycle_level)
	  {
	    int response;

	    if (addr == 0)
	      addr = program_starting_address;

	    if (cycle_running)
	      {
		write_output (message_out,
			      "Program is running.  Restart (y/n)[n] ? ");
		if ((response = yylex ()) != Y_ID ||
		    strcmp ((char *)yylval, "y"))
		  {
		    write_output (message_out, "ignored\n");
		  }
		else
		  {
		    kill_prog_fds ();
		    cl_initialize_world (1);
		    cl_run_program (addr, DEFAULT_RUN_STEPS, !quiet);
		  }
		console_to_spim ();
		if (response != Y_NL)
		  flush_to_newline ();
	      }
	    else
	      {
		cl_initialize_world (1);
		cl_run_program (addr, DEFAULT_RUN_STEPS, !quiet);
	      }
	  }
	else
#endif
	  {
	    if (addr)
	      if (run_program (addr, DEFAULT_RUN_STEPS, 0, 0))
		write_output (message_out, "Breakpoint encountered at 0x%08x\n",
			      PC);
	  }
	console_to_spim ();

	prev_cmd = RUN_CMD;
	return (0);
      }

    case CONTINUE_CMD:
      {
	if (PC != 0)
	  {
	    console_to_program ();
#ifdef CL_SPIM
	    if (cycle_level == 1 && cycle_running)
	      cl_run_program (PC, DEFAULT_RUN_STEPS, !quiet);
	    else
#endif
	      if (run_program (PC, DEFAULT_RUN_STEPS, 0, 1))
		write_output (message_out,
			      "Breakpoint encountered at 0x%08x\n",
			      PC);
	    console_to_spim ();
	  }
	prev_cmd = CONTINUE_CMD;
	return (0);
      }

    case STEP_CMD:
      {
	static int steps;
	mem_addr addr;

	steps = (redo ? steps : get_opt_int ());
	addr = starting_address ();

	if (steps == 0)
	  steps = 1;
	if (addr)
	  {
	    console_to_program ();
#ifdef CL_SPIM
	    if (cycle_level == 1)
	      {
		if (!cycle_running)
		  cl_initialize_world (1);
		cl_run_program (addr, steps, !quiet);
		if (quiet) print_pipeline ();
	      }
	    else
#endif
	      if (run_program (addr, steps, 1, 1))
		write_output (message_out,
			      "Breakpoint encountered at 0x%08x\n",
			      PC);
	    console_to_spim ();
	  }

	prev_cmd = STEP_CMD;
	return (0);
      }

    case PRINT_CMD:
      {
	int token = (redo ? prev_token : yylex ());
	static int loc;

	if (token == Y_REG)
	  {
	    if (redo) loc += 1;
	    else loc = yylval;
	    print_reg (loc, 0);
	  }
	else if (token == Y_FP_REG)
	  {
	    if (redo) loc += 2;
	    else loc = yylval;
	    print_reg (loc, 1);
	  }
	else if (token == Y_INT)
	  {
	    if (redo) loc += 4;
	    else loc = yylval;
	    print_mem (loc);
	  }
	else if (token == Y_ID)
	  {
	    if (!print_reg (yylval, 2))
	      {
		if (redo) loc += 4;
		else loc = find_symbol_address ((char *) yylval);

		if (loc != 0)
		  print_mem (loc);
		else
		  error ("Unknown label: %s\n", yylval);
	      }
	  }
	else
	  error ("Print what?\n");
	if (!redo) flush_to_newline ();
	prev_cmd = PRINT_CMD;
	prev_token = token;
	return (0);
      }

    case PRINT_SYM_CMD:
      print_symbols ();
      if (!redo) flush_to_newline ();
      prev_cmd = NOP_CMD;
      return (0);

    case REINITIALIZE_CMD:
      flush_to_newline ();
#ifdef CL_SPIM
      initialize_world (cycle_level ? 0 : load_trap_handler);
      cl_initialize_world (0);
#else
      initialize_world (load_trap_handler);
#endif
      write_startup_message ();
      prev_cmd = NOP_CMD;
      return (0);

    case ASM_CMD:
      yyparse ();
      prev_cmd = ASM_CMD;
      return (0);

    case REDO_CMD:
      return (1);

    case NOP_CMD:
      prev_cmd = NOP_CMD;
      return (0);

    case HELP_CMD:
      if (!redo) flush_to_newline ();
      write_output (message_out, "\nSPIM is a MIPS R2000 simulator.\n");
      write_output (message_out, "Its top-level commands are:\n");
      write_output (message_out, "exit  -- Exit from the simulator\n");
      write_output (message_out,
		    "read \"FILE\" -- Read FILE of assembly code into memory\n");
      write_output (message_out,
		    "load \"FILE\" -- Same as read\n");
#ifdef mips
      write_output (message_out,
		    "execute \"A.OUT\" -- Read A.OUT file into memory \n");
#endif
      write_output (message_out,
		    "run <ADDR> -- Start the program at optional ADDRESS\n");
      write_output (message_out,
		    "step <N> -- Step the program for N instructions\n");
      write_output (message_out,
		    "continue -- Continue program execution without stepping\n");
      write_output (message_out, "print $N -- Print register N\n");
      write_output (message_out,
		    "print $fN -- Print floating point register N\n");
      write_output (message_out,
		    "print ADDR -- Print contents of memory at ADDRESS\n");
      write_output (message_out,
		    "reinitialize -- Clear the memory and registers\n");
      write_output (message_out,
		    "breakpoint <ADDR> -- Set a breakpoint at address\n");
      write_output (message_out,
		    "delete <ADDR> -- Delete all breakpoints at address\n");
      write_output (message_out, "list -- List all breakpoints\n");
      write_output (message_out,
		    ". -- Rest of line is assembly instruction to put in memory\n");
      write_output (message_out, "<cr> -- Newline reexecutes previous command\n");
      write_output (message_out, "? -- Print this message\n");
#ifdef CL_SPIM
      write_output (message_out, "\n(In cycle level mode only...)\n");
      write_output (message_out,
		    "set [arg] -- cl-spim command to toggle arg on/off\n");
      write_output (message_out,
		    "info [arg] -- cl-spim command for info on arg\n");
      write_output (message_out,
		    "handle <signal name or #> [pass nopass print noprint \
stop nostop]\n");
#endif


      write_output (message_out,
		    "\nMost commands can be abbreviated to their unique prefix\n");
      write_output (message_out, "e.g., ex, re, l, ru, s, p\n\n");
      prev_cmd = HELP_CMD;
      return (0);

    case SET_BKPT_CMD:
    case DELETE_BKPT_CMD:
      {
	int token = (redo ? prev_token : yylex ());
	static mem_addr addr;

	if (!redo) flush_to_newline ();
	if (token == Y_INT)
	  addr = redo ? addr + 4 : yylval;
	else if (token == Y_ID)
	  addr = redo ? addr + 4 : find_symbol_address ((char *) yylval);
	else
	  error ("Must supply an address for breakpoint\n");
	if (cmd == SET_BKPT_CMD)
	  add_breakpoint (addr);
	else
	  delete_breakpoint (addr);
	prev_cmd = cmd;

#ifdef CL_SPIM
	if (cycle_level && addr == breakpoint_reinsert)
	  breakpoint_reinsert = 0;
#endif

	return (0);
      }

    case LIST_BKPT_CMD:
      if (!redo) flush_to_newline ();
      list_breakpoints ();
      prev_cmd = LIST_BKPT_CMD;
      return (0);

#ifdef mips
    case EXECUTE_CMD:
      {
	int token = (redo ? prev_token : yylex ());
	char *name = str_copy ((char *) yylval);

	if (!redo) flush_to_newline ();
	if (token == Y_STR)
	  {
	    initialize_world (0);
#ifdef CL_SPIM
	    cl_initialize_world (0);
#endif
	    read_aout_file (name);
	  }
	else
	  error ("Must supply a filename to read\n");
	prev_cmd = EXECUTE_CMD;
	return (0);
      }
#endif

#ifdef CL_SPIM
    case INFO_CMD:
      {
	int token = (redo ? prev_token : yylex ());

	if (token == Y_NL)
	  {
	    write_output (message_out, "Information available on...\n");
	    write_output (message_out, "\tstatus\n");
	    write_output (message_out, "(In cycle level mode only...)\n");
	    write_output (message_out, "\texceptions\n");
	    write_output (message_out, "\tpipeline\n");
	    write_output (message_out, "\tsignals\n");
	    write_output (message_out, "\tsyscalls\n");
	  }
	else if (token != Y_ID)
	  {
	    write_output (message_out,
			  "Unknown info type.  \"info\" for details.\n");
	    flush_to_newline ();
	  }
	else
	  {
	    if (str_prefix ((char *) yylval, "status", 6))
	      {
		if (!cycle_level)
		  write_output (message_out, "SPIM Mode:         Regular\n");
		else
		  {
		    write_output (message_out,
				  "SPIM Mode:         Cycle Level\n");
		    write_output (message_out,
				  "Instruction Cache: %s\n", icache_on ? "ON":"OFF");
		    write_output (message_out,
				  "Data Cache:        %s\n", dcache_on ? "ON":"OFF");
		    write_output (message_out,
				  "TLB:               %s\n", tlb_on    ? "ON":"OFF");
		  }
	      }
	    else if (!cycle_level)
	      write_output (message_out,"must be in cycle level mode for info other than \"info status\".\n");
	    else if (str_prefix ((char *) yylval, "pipeline", 4))
	      print_pipeline ();
	    else if (str_prefix ((char *) yylval, "signals", 3))
	      print_signal_status (ALL_SIGNALS);
	    else if (str_prefix ((char *) yylval, "exceptions", 3))
	      print_except_stats ();
	    else if (str_prefix ((char *) yylval, "syscalls", 3))
	      print_syscall_usage ();
	    else
	      write_output (message_out,
			    "Unknown info type. \"info\" for details.\n");
	    flush_to_newline ();
	  }
	prev_cmd = NOP_CMD;
	return (0);
      }

    case HANDLE_CMD:
      {
	int x = 0, flags, token = (redo ? prev_token : yylex ());

	if (!cycle_level)
	  write_output(message_out,"Handle only valid in cycle level spim.\n");
	else if (token == Y_NL)
	  write_output(message_out, "Handle needs signal name as argument.\n");
	else if (token != Y_ID && token != Y_INT)
	  {
	    write_output(message_out,
			 "Unrecognized signal name.  Type \"info\" signals for valid names.\n");
	    flush_to_newline ();
	  }
	else			/* find signal in table */
	  {
	    if (token == Y_INT)
	      x = yylval;
	    else
	      for (x = 0; x < NSIG; x++)
		if (!strcmp ((char *)yylval, siginfo[x].signame))
		  break;
	    if (x >= NSIG || x < 0)
	      write_output(message_out,
			   "Unrecognized signal name.  Type \"info\" signals for valid names.\n");
	    else
	      {
		while ((flags = yylex ()) == Y_ID)
		  {
		    if (!strcmp ((char *)yylval, "stop"))
		      SET_STOP(x, 1);
		    else if (!strcmp ((char *)yylval, "nostop"))
		      SET_STOP(x, 0);
		    else if (!strcmp ((char *)yylval, "pass"))
		      SET_PASS(x, 1);
		    else if (!strcmp ((char *)yylval, "nopass"))
		      SET_PASS(x, 0);
		    else if (!strcmp ((char *)yylval, "print"))
		      SET_PRINT(x, 1);
		    else if (!strcmp ((char *)yylval, "noprint"))
		      SET_PRINT(x, 0);
		    else
		      write_output (message_out,
				    "\"%s\" unrecognized, ignored\n",
				    (char *) yylval);
		  }
		if (flags != Y_ID && flags != Y_NL)
		  write_output (message_out, "\"%s\" unrecognized, ignored\n",
				(char *)yylval);
		print_signal_status (x);
	      }
	  }
	prev_cmd = NOP_CMD;
	return (0);
      }

    case SET_CMD:
      {
	int response, token = (redo ? prev_token : yylex ());

	if (token == Y_NL)
	  {
	    write_output (message_out, "Valid sets are...\n");
	    write_output (message_out, "\tcycle, icache, dcache, tlb\n");
	    write_output (message_out, "set's toggle these values on/off\n");
	    write_output (message_out, "\"info status\" for current values\n");
	  }
	else if (token != Y_ID)
	  {
	    write_output (message_out,
			  "Unknown set type.  \"set\" for details.\n");
	    flush_to_newline ();
	  }
	else {
	  flush_to_newline ();

	  if (str_prefix ((char *) yylval, "cycle", 5))
	    {
	      write_output (message_out,
			    "Turning cycle mode %s means program will have to be restarted.\nProceed (y/n)[n] ? ",
			    (cycle_level ? "off":"on"));
	      if ((response = yylex()) != Y_ID ||
		  strcmp ((char *)yylval, "y"))
		{
		  write_output (message_out, "ignored\n");
		}
	      else
		{
		  cycle_level = !cycle_level;
		  if (cycle_level)
		    {
		      bare_machine = 1;
		      quiet = 1;
		      load_trap_handler = 0;
		    }
		  cl_initialize_world (0);
		}
	      if (response != Y_NL)
		flush_to_newline ();
	    }

	  else if (!cycle_level)
	    write_output(message_out,
			 "only set cycle valid in regular mode.\n");

	  else if (str_prefix ((char *) yylval, "icache", 6))
	    {
	      if (cycle_running)
		{
		  write_output (message_out,
				"Program is running.  Turn cache %s now (y/n)[n] ? ",
				(icache_on ? "off" : "on"));
		  if ((response = yylex ()) != Y_ID ||
		      strcmp ((char *)yylval, "y"))
		    {
		      write_output (message_out, "ignored\n");
		    }
		  else
		    {
		      icache_on = !icache_on;
		      if (icache_on)
			cache_init (mem_system, INST_CACHE);
		    }
		  if (response != Y_NL)
		    flush_to_newline ();
		}
	      else
		{
		  icache_on = !icache_on;
		  write_output (message_out, "Instruction cache %s.\n",
				(icache_on ? "on":"off"));
		  if (icache_on)
		    cache_init (mem_system, INST_CACHE);
		}
	    }

	  else if (str_prefix ((char *) yylval, "dcache", 6))
	    {
	      if (cycle_running)
		{
		  write_output (message_out,
				"Program is running.  Turn cache %s now (y/n)[n] ? ",
				(dcache_on ? "off" : "on"));
		  if ((response = yylex ()) != Y_ID ||
		      strcmp ((char *)yylval, "y"))
		    {
		      write_output (message_out, "ignored\n");
		    }
		  else
		    {
		      dcache_on = !dcache_on;
		      if (dcache_on)
			cache_init (mem_system, DATA_CACHE);
		    }
		  if (response != Y_NL)
		    flush_to_newline ();
		}
	      else
		{
		  dcache_on = !dcache_on;
		  write_output (message_out, "Data cache %s.\n",
				(dcache_on ? "on":"off"));
		  if (dcache_on)
		    cache_init (mem_system, DATA_CACHE);
		}
	    }

	  else if (str_prefix ((char *) yylval, "tlb", 3))
	    {
	      if (cycle_running && (icache_on || dcache_on))
		write_output (message_out,
			      "Toggling the TLB invalidates cache values.\nReinitialize, then set TLB.\n");
	      else
		{
		  tlb_on = !tlb_on;
		  write_output (message_out, "TLB %s.\n",
				(tlb_on ? "on":"off"));
		  if (tlb_on)
		    tlb_init ();
		}
	    }
	  else
	    write_output (message_out,
			  "Unknown set type.  \"set\" for details.\n");

	}
	prev_cmd = NOP_CMD;
	return (0);
      }
#endif

    default:
      while (yylex () != Y_NL) ;
      error ("Unknown spim command\n");
      return (0);
    }
}


/* Read a SPIM command with the scanner and return its ennuemerated
   value. */

#ifdef __STDC__
static int
read_assembly_command (void)
#else
static int
read_assembly_command ()
#endif
{
  int token = yylex ();

  if (token == Y_NL)		/* Blank line means redo */
    return (REDO_CMD);
  else if (token != Y_ID)	/* Better be a string */
    return (UNKNOWN_CMD);
  else if (str_prefix ((char *) yylval, "exit", 2))
    return (EXIT_CMD);
  else if (str_prefix ((char *) yylval, "quit", 2))
    return (EXIT_CMD);
  else if (str_prefix ((char *) yylval, "print", 1))
    return (PRINT_CMD);
  else if (str_prefix ((char *) yylval, "print_symbol", 6))
    return (PRINT_SYM_CMD);
  else if (str_prefix ((char *) yylval, "run", 2))
    return (RUN_CMD);
  else if (str_prefix ((char *) yylval, "read", 2))
    return (READ_CMD);
  else if (str_prefix ((char *) yylval, "load", 2))
    return (READ_CMD);
  else if (str_prefix ((char *) yylval, "reinitialize", 6))
    return (REINITIALIZE_CMD);
  else if (str_prefix ((char *) yylval, "step", 1))
    return (STEP_CMD);
  else if (str_prefix ((char *) yylval, "help", 1))
    return (HELP_CMD);
  else if (str_prefix ((char *) yylval, "continue", 1))
    return (CONTINUE_CMD);
  else if (str_prefix ((char *) yylval, "breakpoint", 2))
    return (SET_BKPT_CMD);
  else if (str_prefix ((char *) yylval, "delete", 1))
    return (DELETE_BKPT_CMD);
  else if (str_prefix ((char *) yylval, "list", 2))
    return (LIST_BKPT_CMD);
#ifdef mips
  else if (str_prefix ((char *) yylval, "execute", 3))
    return (EXECUTE_CMD);
#endif
  else if (*(char *) yylval == '?')
    return (HELP_CMD);
  else if (*(char *) yylval == '.')
    return (ASM_CMD);
#ifdef CL_SPIM
  else if (str_prefix ((char *) yylval, "info", 4))
    return (INFO_CMD);
  else if (str_prefix ((char *) yylval, "handle", 6))
    return (HANDLE_CMD);
  else if (str_prefix ((char *) yylval, "set", 3))
    return (SET_CMD);
#endif
  else
    return (UNKNOWN_CMD);
}


/* Return non-nil if STRING1 is a (proper) prefix of STRING2. */

#ifdef __STDC__
static int
str_prefix (char *s1, char *s2, int min_match)
#else
static int
str_prefix (s1, s2, min_match)
     char *s1, *s2;
     int min_match;
#endif
{
  for ( ; *s1 == *s2 && *s1 != '\0'; s1 ++, s2 ++) min_match --;
  return (*s1 == '\0' && min_match <= 0);
}


/* Read and return an integer from the current line of input.  If the
   line doesn't contain an integer, return 0.  In either case, flush the
   rest of the line, including the newline. */

#ifdef __STDC__
static long
get_opt_int (void)
#else
static long
get_opt_int ()
#endif
{
  int token;

  if ((token = yylex ()) == Y_INT)
    {
      flush_to_newline ();
      return (yylval);
    }
  else if (token == Y_NL)
    return (0);
  else
    {
      flush_to_newline ();
      return (0);
    }
}


/* Flush the rest of the input line up to and including the next newline. */

#ifdef __STDC__
static void
flush_to_newline (void)
#else
static void
flush_to_newline ()
#endif
{
  while (yylex () != Y_NL) ;
}


/* Print register number N.  TYPE code indicate which register set to use.
   Return non-zero if register N was valid register string. */

#ifdef __STDC__
static int
print_reg (int reg_no, int type_code)
#else
static int
print_reg (reg_no, type_code)
     int reg_no;
     int type_code;
#endif
{
  switch (type_code)
    {
    case 0:
      write_output (message_out, "Reg %d = 0x%08x (%d)\n",
		 reg_no, R[reg_no], R[reg_no]);
      break;
    case 1:
      write_output (message_out, "FP reg %d = %f (double)\n",
		 reg_no, FPR_D (reg_no));
      write_output (message_out, "FP reg %d = %f (single)\n",
		 reg_no, FPR_S (reg_no));
      break;
    case 2:
      {
	char *rn = (char *) reg_no;
	char s[100];
	char *s1 = s;

	/* Conver to lower case */
	while (*rn != '\0' && s1 - s < 100)
	  *s1++ = tolower (*rn++);
	*s1 = '\0';
	/* Drop leading $ */
	if (s[0] == '$')
	  s1 = s + 1;
	else
	  s1 = s;

	if (streq (s1, "pc"))
	  write_output (message_out, "PC = 0x%08x (%d)\n", PC, PC);
	else if (streq (s1, "hi"))
	  write_output (message_out, "HI = 0x%08x (%d)\n", HI, HI);
	else if (streq (s1, "lo"))
	  write_output (message_out, "LO = 0x%08x (%d)\n", LO, LO);
	else if (streq (s1, "fpcond"))
	  write_output (message_out, "FpCond = 0x%08x (%d)\n", FpCond, FpCond);
	else if (streq (s1, "cause"))
	  write_output (message_out, "Cause = 0x%08x (%d)\n", Cause, Cause);
	else if (streq (s1, "epc"))
	  write_output (message_out, "EPC = 0x%08x (%d)\n", EPC, EPC);
	else if (streq (s1, "status"))
	  write_output (message_out, "Status = 0x%08x (%d)\n",
		     Status_Reg, Status_Reg);
	else if (streq (s1, "badvaddr"))
	  write_output (message_out, "BadVAddr = 0x%08x (%d)\n",
		     BadVAddr, BadVAddr);
	else if (streq (s1, "context"))
	  write_output (message_out, "Context = 0x%08x (%d)\n",
			Context, Context);
	else if (streq (s1, "prid"))
	  write_output (message_out, "PRId = 0x%08x (%d)\n", PRId, PRId);
	else
	  return (0);
	break;
      }
    }
  return (1);
}



/* Print an error message. */

#ifdef __STDC__
void
error (char *fmt, ...)
#else
/*VARARGS0*/
void
error (va_alist)
va_dcl
#endif
{
  va_list args;
#ifndef __STDC__
  char *fmt;
#endif

#ifdef __STDC__
  va_start (args, fmt);
#else
  va_start (args);
  fmt = va_arg (args, char *);
#endif

#if defined(mips) || defined(amiga)
  vfprintf (stderr, fmt, args);
#else
  _doprnt (fmt, args, stderr);
#endif
  va_end (args);
}


/* Print an error message and return to top level. */

#ifdef __STDC__
int
run_error (char *fmt, ...)
#else
/*VARARGS0*/
int
run_error (va_alist)
va_dcl
#endif
{
  va_list args;
#ifndef __STDC__
  char *fmt;
#endif

#ifdef __STDC__
  va_start (args, fmt);
#else
  va_start (args);
  fmt = va_arg (args, char *);
#endif

  console_to_spim ();

#if defined(mips) || defined(amiga)
  vfprintf (stderr, fmt, args);
#else
  _doprnt (fmt, args, stderr);
#endif
  va_end (args);
  longjmp (spim_top_level_env, 1);
  return (0);			/* So it can be used in expressions */
}



/* IO facilities: */

#ifdef __STDC__
void
write_output (long fp, char *fmt, ...)
#else
/*VARARGS0*/
void
write_output (va_alist)
va_dcl
#endif
{
  va_list args;
  FILE *f;
#ifndef __STDC__
  char *fmt;
  long fp;
#endif
  int restore_console_to_program = 0;

#ifdef __STDC__
  va_start (args, fmt);
  f = (FILE *) fp;		/* Not too portable... */
#else
  va_start (args);
  fp = va_arg (args, long);
  f = (FILE *) fp;		/* Not too portable... */
  fmt = va_arg (args, char *);
#endif

  if (console_state_saved)
    {
      restore_console_to_program = 1;
      console_to_spim ();
    }

#if defined(mips) || defined(amiga)
  if (f != 0)
    vfprintf (f, fmt, args);
  else
    vfprintf (stdout, fmt, args);
#else
  _doprnt (fmt, args, f);
#endif
  fflush (f);
  va_end (args);

  if (restore_console_to_program)
    console_to_program ();
}


/* Simulate the semantics of fgets (not gets) on Unix file. */

#ifdef __STDC__
void
read_input (char *str, int str_size)
#else
void
read_input (str, str_size)
     char *str;
     int str_size;
#endif
{
  char *ptr;
  int restore_console_to_program = 0;

  if (console_state_saved)
    {
      restore_console_to_program = 1;
      console_to_spim ();
    }

  ptr = str;
  str_size -= 1;		/* Reserve space for null */

  while (1)
    {
      char buf[1];
      read (console_in, buf, 1); /* Not in raw mode! */

      if (buf[0] == '\n')
	{
	  *ptr ++ = '\n';
	  *ptr = '\0';
	  break;
	}
      else
	{
	  *ptr ++ = buf[0];
	  *ptr = '\0';
	  str_size -= 1;
	  if (str_size == 0)
	    break;
	}
    }

  if (restore_console_to_program)
    console_to_program ();
}


/* Give the console to the program for IO. */

#ifdef __STDC__
static void
console_to_program (void)
#else
static void
console_to_program ()
#endif
{
  int flags;

  if (mapped_io && !console_state_saved)
    {
      ioctl (console_in, TIOCGETP, (char *) &saved_console_state);
      flags = saved_console_state.sg_flags;
      saved_console_state.sg_flags = (flags | RAW) & ~(CRMOD|ECHO);
      ioctl (console_in, TIOCSETP, (char *) &saved_console_state);
      saved_console_state.sg_flags = flags;
      console_state_saved = 1;
    }
}


/* Return the console to SPIM. */

#ifdef __STDC__
static void
console_to_spim (void)
#else
static void
console_to_spim ()
#endif
{
  if (mapped_io && console_state_saved)
    ioctl (console_in, TIOCSETP, (char *) &saved_console_state);
  console_state_saved = 0;
}


#ifdef __STDC__
int
console_input_available (void)
#else
int
console_input_available ()
#endif
{
  fd_set fdset;
  struct timeval timeout;

  if (mapped_io)
    {
      timeout.tv_sec = 0;
      timeout.tv_usec = 0;
      FD_ZERO (&fdset);
      FD_SET (console_in, &fdset);
      return (select (sizeof (fdset) * 8, &fdset, NULL, NULL, &timeout));
    }
  else
    return (0);
}


#ifdef __STDC__
char
get_console_char (void)
#else
char
get_console_char ()
#endif
{
  char buf;

  read (console_in, &buf, 1);

  if (buf == 3)			/* ^C */
    control_c_seen (0);
  return (buf);
}


#ifdef __STDC__
void
put_console_char (char c)
#else
void
put_console_char (c)
     char c;
#endif
{
  putc (c, (FILE *) console_out);
  fflush ((FILE *) console_out);
}
