/* SPIM S20 MIPS simulator. Terminal interface for SPIM simulator. Copyright (C) 1990 by James Larus (larus@cs.wisc.edu). SPIM 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 1, or (at your option) any later version. SPIM 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 GNU CC; see the file COPYING. If not, write to James R. Larus, Computer Sciences Department, University of Wisconsin--Madison, 1210 West Dayton Street, Madison, WI 53706, USA or to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Header: /home/aa/projects/spim/public_html/cvsroot/spimsal/spim.c,v 1.4 2000/01/11 23:24:45 brg Exp $ */ #include #include #include #include #include #include "spim.h" #include "inst.h" #include "mem.h" #include "reg.h" #include "sym_tbl.h" #include "y.tab.h" /* Imported functions: */ void initialize_scanner (FILE *); int yylex (void); int yyparse (void); void fix_echo(void); /* Internal functions: */ static void control_c_seen (int sig); static void flush_to_newline (void); static long get_opt_int (void); static int parse_spim_command (FILE *, int); static void print_reg (int, int); static int print_reg_name (char *); static int read_assembly_command (FILE *); static int str_prefix (const char *, const char *, int); static void top_level (void); /* Exported varialbes: */ char mess_buff[512]; int console_uses_stdin = 1; /* Set to 1 from spim and 0 for xspim */ /* Imported variables: */ mem_addr program_starting_address; /* First PC */ char *input_file_name; long initial_text_size, initial_data_size, initial_data_limit; long initial_stack_size, initial_stack_limit, initial_k_text_size; long initial_k_data_size, initial_k_data_limit; int source_file; /* Local variables: */ static int load_trap_handler = 1; /* Non-zero means load standard trap handler */ static jmp_buf spim_top_level_env; /* For ^C */ int main (int argc, char **argv) { int i; char *file_name = NULL, *ex_file_name = NULL; console_out = (int) stdout; message_out = (int) stdout; for (i = 1; i < argc; i++) if (streq (argv [i], "-bare")) bare_machine = 1, quiet = 1; else if (streq (argv [i], "-memio")) #ifndef NOMEMIO memio = 1; #else error("-memio option disabled. Recompile spim without -DNOMEMIO\n"); #endif /* NOMEMIO */ 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")) file_name = argv[++i]; 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]); else error ("usage: spim -bare/-asm -trap/-notrap -quiet/-noquiet -memio -file \n"); initialize_world (load_trap_handler); if ((file_name == NULL) && (ex_file_name == NULL)) top_level (); else { { if (!read_assembly_file (file_name)) if (!setjmp (spim_top_level_env)) run_program (starting_address(), DEFAULT_RUN_STEPS, 0, 0); } } return (0); } /* Top-level read-eval-print loop for SPIM. */ static void top_level (void) { int redo = 0; /* Non-zero means reexecute last command */ signal (SIGINT, control_c_seen); while (1) { if (!redo) write_output (message_out, "(spim) "); if (!setjmp (spim_top_level_env)) redo = parse_spim_command (stdin, redo); else redo = 0; fflush (stdout); fflush (stderr); } } static void control_c_seen (int sig) { write_output (message_out, "Execution interrupted\n"); fix_echo(); 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 #define DUMPNATIVE_TEXT_CMD 98 /* brg */ #define DUMP_TEXT_CMD 99 /* bh */ /* 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. */ static int parse_spim_command (FILE *file, int redo) { static int prev_cmd = NOP_CMD; /* Default redo */ static int prev_token; int cmd; initialize_scanner (file); switch (cmd = (redo ? prev_cmd : read_assembly_command (file))) { case EXIT_CMD: exit (0); case READ_CMD: { int token = (redo ? prev_token : yylex ()); if (!redo) flush_to_newline (); if (token == Y_STR) { read_assembly_file (yylval.cptr); 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 (); if (addr) if (run_program (addr, DEFAULT_RUN_STEPS, 0, 0)) { sprintf(mess_buff, "Breakpoint encountered at 0x%08x\n", PC); write_output (message_out, mess_buff); } prev_cmd = RUN_CMD; return (0); } case CONTINUE_CMD: { if (PC != 0) if (run_program (PC, DEFAULT_RUN_STEPS, 0, 1)) { sprintf(mess_buff, "Breakpoint encountered at 0x%08x\n", PC); write_output (message_out, mess_buff); } 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) if (run_program (addr, steps, 1, 1)) { sprintf(mess_buff, "Breakpoint encountered at 0x%08x\n", PC); write_output (message_out, mess_buff); } 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.ival; print_reg (loc, 0); } else if (token == Y_FP_REG) { if (redo) loc += 2; else loc = yylval.ival; print_reg (loc, 1); } else if (token == Y_INT) { if (redo) loc += 4; else loc = yylval.ival; print_mem (loc); } else if (token == Y_ID) { if (!print_reg_name (yylval.cptr)) { if (redo) loc += 4; else loc = find_symbol_address (yylval.cptr); if (loc != 0) print_mem (loc); else { sprintf(mess_buff, "Unknown label: %s\n", yylval.cptr); error (mess_buff); } } } 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: initialize_world (load_trap_handler); prev_cmd = NOP_CMD; return (0); case ASM_CMD: input_file_name = ""; 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"); write_output (message_out, "run -- Start the program at optional ADDRESS\n"); write_output (message_out, "step -- 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 -- Set a breakpoint at address\n"); write_output (message_out, "delete -- Delete all breakpoints at address\n"); write_output (message_out, "list -- List all breakpoints\n"); write_output (message_out, "dump [ \"FILE\" ] -- Dump binary code to spim.dump or FILE (network byte order)\n"); write_output (message_out, "dumpnative [ \"FILE\" ] -- same as dump, local machine byte order\n"); write_output (message_out, ". -- Rest of line is assembly instruction to put in memory\n"); write_output (message_out, " -- Newline reexecutes previous command\n"); write_output (message_out, "? -- Print this message\n"); 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.ival; else if (token == Y_ID) addr = redo ? addr + 4 : find_symbol_address (yylval.cptr); else error ("Must supply an address for breakpoint\n"); if (cmd == SET_BKPT_CMD) add_breakpoint (addr); else delete_breakpoint (addr); prev_cmd = cmd; return (0); } case LIST_BKPT_CMD: if (!redo) flush_to_newline (); list_breakpoints (); prev_cmd = LIST_BKPT_CMD; return (0); if (!redo) flush_to_newline (); { FILE *fp = fopen("spim.dump","w"); int i; long int code; extern mem_addr next_text_pc; for (i = 0; i < (next_text_pc - TEXT_BOT)>>2; i++) { code = inst_encode(text_seg[i]); fwrite(&code, 1, 4, fp); } fclose(fp); } prev_cmd = DUMP_TEXT_CMD; return (0); case DUMP_TEXT_CMD: /* bh */ case DUMPNATIVE_TEXT_CMD: /* brg */ { FILE *fp = NULL; char *filename = NULL; int i, token = (redo ? prev_token : yylex ()); long int code, words = 0; extern mem_addr next_text_pc; if (token == Y_STR) { filename = yylval.cptr; } else if (token == Y_NL) { filename = "spim.dump"; } else { fprintf(stderr,"usage: %s [ \"filename\" ]\n", (cmd == DUMP_TEXT_CMD ? "dump" : "dumpnative")); } fp = fopen(filename,"w"); for (i = 0; i < (next_text_pc - TEXT_BOT)>>2; i++) { code = inst_encode(text_seg[i]); if (cmd == DUMP_TEXT_CMD) { code = htonl(code); /* dump in network byte order -brg */ } fwrite(&code, 1, 4, fp); words++; fprintf(stderr,"Dumped %ld words to file %s\n",words,filename); } fclose(fp); } prev_cmd = cmd; return (0); default: while (yylex () != Y_NL) ; error ("Unknown spim command\n"); return (0); } } /* Read a SPIM command from the FILE and return its ennuemerated value. */ static int read_assembly_command (FILE *file) { 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 (yylval.cptr, "exit", 2)) return (EXIT_CMD); else if (str_prefix (yylval.cptr, "print", 1)) return (PRINT_CMD); else if (str_prefix (yylval.cptr, "print_symbol", 6)) return (PRINT_SYM_CMD); else if (str_prefix (yylval.cptr, "run", 2)) return (RUN_CMD); else if (str_prefix (yylval.cptr, "read", 2)) return (READ_CMD); else if (str_prefix (yylval.cptr, "load", 2)) return (READ_CMD); else if (str_prefix (yylval.cptr, "reinitialize", 6)) return (REINITIALIZE_CMD); else if (str_prefix (yylval.cptr, "step", 1)) return (STEP_CMD); else if (str_prefix (yylval.cptr, "help", 1)) return (HELP_CMD); else if (str_prefix (yylval.cptr, "continue", 1)) return (CONTINUE_CMD); else if (str_prefix (yylval.cptr, "breakpoint", 2)) return (SET_BKPT_CMD); else if (str_prefix (yylval.cptr, "delete", 1)) return (DELETE_BKPT_CMD); else if (str_prefix (yylval.cptr, "list", 2)) return (LIST_BKPT_CMD); else if (*(yylval.cptr) == '?') return (HELP_CMD); else if (str_prefix (yylval.cptr, "dump", 4)) /* bh */ return (DUMP_TEXT_CMD); else if (str_prefix (yylval.cptr, "dumpnative", 4)) /* brg */ return (DUMPNATIVE_TEXT_CMD); else if (*(yylval.cptr) == '.') return (ASM_CMD); else return (UNKNOWN_CMD); } /* Return non-nil if STRING1 is a (proper) prefix of STRING2. */ static int str_prefix (const char *s1, const char *s2, int min_match) { 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. */ static long get_opt_int (void) { int token; if ((token = yylex ()) == Y_INT) { flush_to_newline (); return (yylval.ival); } 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. */ static void flush_to_newline (void) { 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. */ static void print_reg (int reg_no, int type_code) { switch (type_code) { case 0: sprintf(mess_buff, "Reg %d = 0x%08x (%d)\n", reg_no, R[reg_no], R[reg_no]); write_output (message_out, mess_buff); break; case 1: sprintf(mess_buff, "FP reg %d = %f (double)\n", reg_no, FPR_D (reg_no)); write_output (message_out, mess_buff); sprintf(mess_buff, "FP reg %d = %f (single)\n", reg_no, FPR_S (reg_no)); write_output (message_out, mess_buff); break; } } static int print_reg_name (char *reg_no) { char *s = reg_no, *s1 = reg_no; /* Conver to lower case */ for ( ; *s != '\0'; s ++) *s = tolower (*s); /* Drop leading $ */ if (*s1 == '$') s1 += 1; if (streq (s1, "pc")) { sprintf(mess_buff, "PC = 0x%08x (%d)\n", PC, PC); write_output (message_out, mess_buff); } else if (streq (s1, "hi")) { sprintf(mess_buff, "HI = 0x%08x (%d)\n", HI, HI); write_output (message_out, mess_buff); } else if (streq (s1, "lo")) { sprintf(mess_buff, "LO = 0x%08x (%d)\n", LO, LO); write_output (message_out, mess_buff); } else if (streq (s1, "fpcond")) { sprintf(mess_buff, "FpCond = 0x%08x (%d)\n", FpCond, FpCond); write_output (message_out, mess_buff); } else if (streq (s1, "cause")) { sprintf(mess_buff, "Cause = 0x%08x (%d)\n", Cause, Cause); write_output (message_out, mess_buff); } else if (streq (s1, "epc")) { sprintf(mess_buff, "EPC = 0x%08x (%d)\n", EPC, EPC); write_output (message_out, mess_buff); } else if (streq (s1, "status")) { sprintf(mess_buff, "Status = 0x%08x (%d)\n", Status_Reg, Status_Reg); write_output (message_out, mess_buff); } else if (streq (s1, "badvaddr")) { sprintf(mess_buff, "BadVAddr = 0x%08x (%d)\n", BadVAddr, BadVAddr); write_output (message_out, mess_buff); } else if (streq (s1, "context")) { sprintf(mess_buff, "Context = 0x%08x (%d)\n", Context, Context); write_output (message_out, mess_buff); } else if (streq (s1, "prid")) { sprintf(mess_buff, "PRId = 0x%08x (%d)\n", PRId, PRId); write_output (message_out, mess_buff); } else return (0); } /* Print an error message. */ void error (char *string) /* Display only */ { fprintf (stderr, "%s", string); } /* Print an error message and return to top level. */ int run_error (char *string) { fprintf (stderr, "%s", string); fix_echo(); longjmp (spim_top_level_env, 1); return (0); /* So it can be used in expressions */ } void write_output (long f, char *string) { if (f != 0) { fprintf ((FILE *) f, "%s", string); fflush ((FILE *) f); } else fprintf (stdout, "%s", string); } void read_input (char *str, int n) { fgets (str, n, stdin); } #ifndef NOMEMIO #include int read_input_maybe(int which) { fd_set fds; char c; struct timeval time; if (which) return -1; /* No terminal */ time.tv_sec = 0; time.tv_usec = 0; FD_ZERO(&fds); FD_SET(0, &fds); select(3, &fds, NULL, NULL, &time); if (FD_ISSET(0, &fds)) { if (-1 == read(0, &c, 1)) { perror("read_input_maybe()"); exit(-1); } return c; } else return -1; } #endif /* NO MEMIO */ /* Only X needs this */ void PollInput() {};