/* Simulator for Xilinx MicroBlaze processor Copyright 2009, 2010, 2011 Free Software Foundation, Inc. This file is part of GDB, the GNU debugger. 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., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "sysdep.h" #include #include #include /* for byte ordering macros */ #include "bfd.h" #include "gdb/callback.h" #include "libiberty.h" #include "gdb/remote-sim.h" #include "sim-main.h" #include "sim-utils.h" #include "microblaze-dis.h" #ifndef NUM_ELEM #define NUM_ELEM(A) (sizeof (A) / sizeof (A)[0]) #endif static int target_big_endian = 1; static unsigned long heap_ptr = 0; static unsigned long stack_ptr = 0; host_callback *callback; unsigned long microblaze_extract_unsigned_integer (unsigned char *addr, int len) { unsigned long retval; unsigned char *p; unsigned char *startaddr = (unsigned char *)addr; unsigned char *endaddr = startaddr + len; if (len > (int) sizeof (unsigned long)) printf ("That operation is not available on integers of more than " "%d bytes.", sizeof (unsigned long)); /* Start at the most significant end of the integer, and work towards the least significant. */ retval = 0; if (!target_big_endian) { for (p = endaddr; p > startaddr;) retval = (retval << 8) | * -- p; } else { for (p = startaddr; p < endaddr;) retval = (retval << 8) | * p ++; } return retval; } void microblaze_store_unsigned_integer (unsigned char *addr, int len, unsigned long val) { unsigned char *p; unsigned char *startaddr = (unsigned char *)addr; unsigned char *endaddr = startaddr + len; if (!target_big_endian) { for (p = startaddr; p < endaddr;) { *p++ = val & 0xff; val >>= 8; } } else { for (p = endaddr; p > startaddr;) { *--p = val & 0xff; val >>= 8; } } } struct sim_state microblaze_state; int memcycles = 1; static SIM_OPEN_KIND sim_kind; static char *myname; static int issue_messages = 0; long int_sbrk (int inc_bytes) { long addr; addr = heap_ptr; heap_ptr += inc_bytes; if (issue_messages && heap_ptr > SP) fprintf (stderr, "Warning: heap_ptr overlaps stack!\n"); return addr; } static void /* INLINE */ wbat (word x, word v) { if (((uword)x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "byte write to 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; } else { unsigned char *p = CPU.memory + x; p[0] = v; } } static void /* INLINE */ wlat (word x, word v) { if (((uword)x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "word write to 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; } else { if ((x & 3) != 0) { if (issue_messages) fprintf (stderr, "word write to unaligned memory address: 0x%x\n", x); CPU.exception = SIGBUS; } else if (!target_big_endian) { unsigned char *p = CPU.memory + x; p[3] = v >> 24; p[2] = v >> 16; p[1] = v >> 8; p[0] = v; } else { unsigned char *p = CPU.memory + x; p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v; } } } static void /* INLINE */ what (word x, word v) { if (((uword)x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "short write to 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; } else { if ((x & 1) != 0) { if (issue_messages) fprintf (stderr, "short write to unaligned memory address: 0x%x\n", x); CPU.exception = SIGBUS; } else if (!target_big_endian) { unsigned char *p = CPU.memory + x; p[1] = v >> 8; p[0] = v; } else { unsigned char *p = CPU.memory + x; p[0] = v >> 8; p[1] = v; } } } /* Read functions. */ static int /* INLINE */ rbat (word x) { if (((uword)x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "byte read from 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; return 0; } else { unsigned char *p = CPU.memory + x; return p[0]; } } static int /* INLINE */ rlat (word x) { if (((uword) x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "word read from 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; return 0; } else { if ((x & 3) != 0) { if (issue_messages) fprintf (stderr, "word read from unaligned address: 0x%x\n", x); CPU.exception = SIGBUS; return 0; } else if (! target_big_endian) { unsigned char *p = CPU.memory + x; return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]; } else { unsigned char *p = CPU.memory + x; return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; } } } static int /* INLINE */ rhat (word x) { if (((uword)x) >= CPU.msize) { if (issue_messages) fprintf (stderr, "short read from 0x%x outside memory range\n", x); CPU.exception = SIGSEGV; return 0; } else { if ((x & 1) != 0) { if (issue_messages) fprintf (stderr, "short read from unaligned address: 0x%x\n", x); CPU.exception = SIGBUS; return 0; } else if (!target_big_endian) { unsigned char *p = CPU.memory + x; return (p[1] << 8) | p[0]; } else { unsigned char *p = CPU.memory + x; return (p[0] << 8) | p[1]; } } } #define SEXTB(x) (((x & 0xff) ^ (~ 0x7f)) + 0x80) #define SEXTW(y) ((int)((short)y)) static int IOMEM (int addr, int write, int value) { } /* Default to a 8 Mbyte (== 2^23) memory space. */ static int sim_memory_size = 1 << 23; #define MEM_SIZE_FLOOR 64 void sim_size (int size) { sim_memory_size = size; CPU.msize = sim_memory_size; if (CPU.memory) free (CPU.memory); CPU.memory = (unsigned char *) calloc (1, CPU.msize); if (!CPU.memory) { if (issue_messages) fprintf (stderr, "Not enough VM for simulation of %d bytes of RAM\n", CPU.msize); CPU.msize = 1; CPU.memory = (unsigned char *) calloc (1, 1); } } static void init_pointers () { if (CPU.msize != (sim_memory_size)) sim_size (sim_memory_size); } static void set_initial_gprs () { int i; long space; unsigned long memsize; init_pointers (); /* Set up machine just out of reset. */ PC = 0; MSR = 0; memsize = CPU.msize / (1024 * 1024); if (issue_messages > 1) fprintf (stderr, "Simulated memory of %d Mbytes (0x0 .. 0x%08x)\n", memsize, CPU.msize - 1); /* Clean out the GPRs */ for (i = 0; i < 32; i++) CPU.regs[i] = 0; CPU.insts = 0; CPU.cycles = 0; CPU.imm_enable = 0; } static void interrupt () { CPU.exception = SIGINT; } /* Functions so that trapped open/close don't interfere with the parent's functions. We say that we can't close the descriptors that we didn't open. exit() and cleanup() get in trouble here, to some extent. That's the price of emulation. */ unsigned char opened[100]; static void log_open (int fd) { if (fd < 0 || fd > NUM_ELEM (opened)) return; opened[fd] = 1; } static void log_close (int fd) { if (fd < 0 || fd > NUM_ELEM (opened)) return; opened[fd] = 0; } static int is_opened (int fd) { if (fd < 0 || fd > NUM_ELEM (opened)) return 0; return opened[fd]; } static void handle_trap1 () { } static void process_stub (int what) { /* These values should match those in libgloss/microblaze/syscalls.s. */ switch (what) { case 3: /* _read */ case 4: /* _write */ case 5: /* _open */ case 6: /* _close */ case 10: /* _unlink */ case 19: /* _lseek */ case 43: /* _times */ handle_trap1 (); break; default: if (issue_messages) fprintf (stderr, "Unhandled stub opcode: %d\n", what); break; } } static void util (unsigned what) { switch (what) { case 0: /* exit */ CPU.exception = SIGQUIT; break; case 1: /* printf */ { unsigned long a[6]; unsigned char *s; int i; for (s = (unsigned char *)a[0], i = 1 ; *s && i < 6 ; s++) if (*s == '%') i++; } break; case 2: /* scanf */ if (issue_messages) fprintf (stderr, "WARNING: scanf unimplemented\n"); break; case 3: /* utime */ break; case 0xFF: process_stub (CPU.regs[1]); break; default: if (issue_messages) fprintf (stderr, "Unhandled util code: %x\n", what); break; } } /* For figuring out whether we carried; addc/subc use this. */ static int iu_carry (unsigned long a, unsigned long b, int cin) { unsigned long x; x = (a & 0xffff) + (b & 0xffff) + cin; x = (x >> 16) + (a >> 16) + (b >> 16); x >>= 16; return (x != 0); } #define WATCHFUNCTIONS 1 #ifdef WATCHFUNCTIONS #define MAXWL 80 word WL[MAXWL]; char *WLstr[MAXWL]; int ENDWL=0; int WLincyc; int WLcyc[MAXWL]; int WLcnts[MAXWL]; int WLmax[MAXWL]; int WLmin[MAXWL]; word WLendpc; int WLbcyc; int WLW; #endif static int tracing = 0; void sim_resume (SIM_DESC sd, int step, int siggnal) { int needfetch; word inst; enum microblaze_instr op; void (*sigsave)(); int memops; int bonus_cycles; int insts; int w; int cycs; word WLhash; ubyte carry; int imm_unsigned; short ra, rb, rd; long immword; uword oldpc, newpc; short delay_slot_enable; short branch_taken; short num_delay_slot; /* UNUSED except as reqd parameter */ enum microblaze_instr_type insn_type; sigsave = signal (SIGINT, interrupt); CPU.exception = step ? SIGTRAP : 0; memops = 0; bonus_cycles = 0; insts = 0; do { /* Fetch the initial instructions that we'll decode. */ inst = rlat (PC & 0xFFFFFFFC); op = get_insn_microblaze (inst, &imm_unsigned, &insn_type, &num_delay_slot); if (op == invalid_inst) fprintf (stderr, "Unknown instruction 0x%04x", inst); if (tracing) fprintf (stderr, "%.4x: inst = %.4x ", PC, inst); rd = GET_RD; rb = GET_RB; ra = GET_RA; /* immword = IMM_W; */ oldpc = PC; delay_slot_enable = 0; branch_taken = 0; if (op == microblaze_brk) CPU.exception = SIGTRAP; else if (inst == MICROBLAZE_HALT_INST) { CPU.exception = SIGQUIT; insts += 1; bonus_cycles++; } else { switch(op) { #define INSTRUCTION(NAME, OPCODE, TYPE, ACTION) \ case NAME: \ ACTION; \ break; #include "microblaze.isa" #undef INSTRUCTION default: CPU.exception = SIGILL; fprintf (stderr, "ERROR: Unknown opcode\n"); } /* Make R0 consistent */ CPU.regs[0] = 0; /* Check for imm instr */ if (op == imm) IMM_ENABLE = 1; else IMM_ENABLE = 0; /* Update cycle counts */ insts ++; if (insn_type == memory_store_inst || insn_type == memory_load_inst) memops++; if (insn_type == mult_inst) bonus_cycles++; if (insn_type == barrel_shift_inst) bonus_cycles++; if (insn_type == anyware_inst) bonus_cycles++; if (insn_type == div_inst) bonus_cycles += 33; if ((insn_type == branch_inst || insn_type == return_inst) && branch_taken) { /* Add an extra cycle for taken branches */ bonus_cycles++; /* For branch instructions handle the instruction in the delay slot */ if (delay_slot_enable) { newpc = PC; PC = oldpc + INST_SIZE; inst = rlat (PC & 0xFFFFFFFC); op = get_insn_microblaze (inst, &imm_unsigned, &insn_type, &num_delay_slot); if (op == invalid_inst) fprintf (stderr, "Unknown instruction 0x%04x", inst); if (tracing) fprintf (stderr, "%.4x: inst = %.4x ", PC, inst); rd = GET_RD; rb = GET_RB; ra = GET_RA; /* immword = IMM_W; */ if (op == microblaze_brk) { if (issue_messages) fprintf (stderr, "Breakpoint set in delay slot " "(at address 0x%x) will not be honored\n", PC); /* ignore the breakpoint */ } else if (insn_type == branch_inst || insn_type == return_inst) { if (issue_messages) fprintf (stderr, "Cannot have branch or return instructions " "in delay slot (at address 0x%x)\n", PC); CPU.exception = SIGILL; } else { switch(op) { #define INSTRUCTION(NAME, OPCODE, TYPE, ACTION) \ case NAME: \ ACTION; \ break; #include "microblaze.isa" #undef INSTRUCTION default: CPU.exception = SIGILL; fprintf (stderr, "ERROR: Unknown opcode at 0x%x\n", PC); } /* Update cycle counts */ insts++; if (insn_type == memory_store_inst || insn_type == memory_load_inst) memops++; if (insn_type == mult_inst) bonus_cycles++; if (insn_type == barrel_shift_inst) bonus_cycles++; if (insn_type == anyware_inst) bonus_cycles++; if (insn_type == div_inst) bonus_cycles += 33; } /* Restore the PC */ PC = newpc; /* Make R0 consistent */ CPU.regs[0] = 0; /* Check for imm instr */ if (op == imm) IMM_ENABLE = 1; else IMM_ENABLE = 0; } else /* no delay slot: increment cycle count */ bonus_cycles++; } } if (tracing) fprintf (stderr, "\n"); } while (!CPU.exception); /* Hide away the things we've cached while executing. */ /* CPU.pc = pc; */ CPU.insts += insts; /* instructions done ... */ CPU.cycles += insts; /* and each takes a cycle */ CPU.cycles += bonus_cycles; /* and extra cycles for branches */ CPU.cycles += memops; /* and memop cycle delays */ signal (SIGINT, sigsave); } int sim_write (SIM_DESC sd, SIM_ADDR addr, const unsigned char *buffer, int size) { int i; init_pointers (); memcpy (&CPU.memory[addr], buffer, size); return size; } int sim_read (SIM_DESC sd, SIM_ADDR addr, unsigned char *buffer, int size) { int i; init_pointers (); memcpy (buffer, &CPU.memory[addr], size); return size; } int sim_store_register (SIM_DESC sd, int rn, unsigned char *memory, int length) { init_pointers (); if (rn < NUM_REGS + NUM_SPECIAL && rn >= 0) { if (length == 4) { /* misalignment safe */ long ival = microblaze_extract_unsigned_integer (memory, 4); if (rn < NUM_REGS) CPU.regs[rn] = ival; else CPU.spregs[rn-NUM_REGS] = ival; return 4; } else return 0; } else return 0; } int sim_fetch_register (SIM_DESC sd, int rn, unsigned char *memory, int length) { long ival; init_pointers (); if (rn < NUM_REGS + NUM_SPECIAL && rn >= 0) { if (length == 4) { if (rn < NUM_REGS) ival = CPU.regs[rn]; else ival = CPU.spregs[rn-NUM_REGS]; /* misalignment-safe */ microblaze_store_unsigned_integer (memory, 4, ival); return 4; } else return 0; } else return 0; } int sim_trace (SIM_DESC sd) { tracing = 1; sim_resume (sd, 0, 0); tracing = 0; return 1; } void sim_stop_reason (SIM_DESC sd, enum sim_stop *reason, int *sigrc) { if (CPU.exception == SIGQUIT) { *reason = sim_exited; *sigrc = RETREG; } else { *reason = sim_stopped; *sigrc = CPU.exception; } } int sim_stop (SIM_DESC sd) { CPU.exception = SIGINT; return 1; } void sim_info (SIM_DESC sd, int verbose) { #ifdef WATCHFUNCTIONS int w, wcyc; #endif callback->printf_filtered (callback, "\n\n# instructions executed %10d\n", CPU.insts); callback->printf_filtered (callback, "# cycles %10d\n", (CPU.cycles) ? CPU.cycles+2 : 0); #ifdef WATCHFUNCTIONS callback->printf_filtered (callback, "\nNumber of watched functions: %d\n", ENDWL); wcyc = 0; for (w = 1; w <= ENDWL; w++) { callback->printf_filtered (callback, "WL = %s %8x\n",WLstr[w],WL[w]); callback->printf_filtered (callback, " calls = %d, cycles = %d\n", WLcnts[w],WLcyc[w]); if (WLcnts[w] != 0) callback->printf_filtered (callback, " maxcpc = %d, mincpc = %d, avecpc = %d\n", WLmax[w],WLmin[w],WLcyc[w]/WLcnts[w]); wcyc += WLcyc[w]; } callback->printf_filtered (callback, "Total cycles for watched functions: %d\n",wcyc); #endif } struct aout { unsigned char sa_machtype[2]; unsigned char sa_magic[2]; unsigned char sa_tsize[4]; unsigned char sa_dsize[4]; unsigned char sa_bsize[4]; unsigned char sa_syms[4]; unsigned char sa_entry[4]; unsigned char sa_trelo[4]; unsigned char sa_drelo[4]; } aout; #define LONG(x) (((x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3]) #define SHORT(x) (((x)[0]<<8)|(x)[1]) SIM_DESC sim_open (SIM_OPEN_KIND kind, host_callback *cb, struct bfd *abfd, char **argv) { /* SIM_DESC sd = sim_state_alloc(kind, alloc);*/ int osize = sim_memory_size; myname = argv[0]; callback = cb; if (kind == SIM_OPEN_STANDALONE) issue_messages = 1; /* Discard and reacquire memory -- start with a clean slate. */ sim_size (1); /* small */ sim_size (osize); /* and back again */ set_initial_gprs (); /* Reset the GPR registers. */ return ((SIM_DESC) 1); } void sim_close (SIM_DESC sd, int quitting) { if (CPU.memory) { free(CPU.memory); CPU.memory = NULL; CPU.msize = 0; } } SIM_RC sim_load (SIM_DESC sd, char *prog, bfd *abfd, int from_tty) { /* Do the right thing for ELF executables; this turns out to be just about the right thing for any object format that: - we crack using BFD routines - follows the traditional UNIX text/data/bss layout - calls the bss section ".bss". */ extern bfd *sim_load_file (); /* ??? Don't know where this should live. */ bfd *prog_bfd; { bfd *handle; asection *s; int found_loadable_section = 0; bfd_vma max_addr = 0; handle = bfd_openr (prog, 0); if (!handle) { printf("``%s'' could not be opened.\n", prog); return SIM_RC_FAIL; } /* Makes sure that we have an object file, also cleans gets the section headers in place. */ if (!bfd_check_format (handle, bfd_object)) { /* wasn't an object file */ bfd_close (handle); printf ("``%s'' is not appropriate object file.\n", prog); return SIM_RC_FAIL; } for (s = handle->sections; s; s = s->next) { if (s->flags & SEC_ALLOC) { bfd_vma vma = 0; int size = bfd_get_section_size (s); if (size > 0) { vma = bfd_section_vma (handle, s); if (vma >= max_addr) { max_addr = vma + size; } } if (s->flags & SEC_LOAD) found_loadable_section = 1; } } if (!found_loadable_section) { /* No loadable sections */ bfd_close(handle); printf("No loadable sections in file %s\n", prog); return SIM_RC_FAIL; } sim_memory_size = (unsigned long) max_addr; /* Clean up after ourselves. */ bfd_close (handle); } /* from sh -- dac */ prog_bfd = sim_load_file (sd, myname, callback, prog, abfd, /* sim_kind == SIM_OPEN_DEBUG, */ 1, 0, sim_write); if (prog_bfd == NULL) return SIM_RC_FAIL; target_big_endian = bfd_big_endian (prog_bfd); PC = bfd_get_start_address (prog_bfd); if (abfd == NULL) bfd_close (prog_bfd); return SIM_RC_OK; } SIM_RC sim_create_inferior (SIM_DESC sd, struct bfd *prog_bfd, char **argv, char **env) { char **avp; int nargs = 0; int nenv = 0; int s_length; int l; unsigned long strings; unsigned long pointers; unsigned long hi_stack; /* Set the initial register set. */ l = issue_messages; issue_messages = 0; set_initial_gprs (); issue_messages = l; hi_stack = CPU.msize - 4; PC = bfd_get_start_address (prog_bfd); /* For now ignore all parameters to the program */ return SIM_RC_OK; } void sim_kill (SIM_DESC sd) { /* nothing to do */ } void sim_do_command (SIM_DESC sd, char * cmd) { /* Nothing there yet; it's all an error. */ if (cmd != NULL) { char ** simargv = buildargv (cmd); if (strcmp (simargv[0], "watch") == 0) { if ((simargv[1] == NULL) || (simargv[2] == NULL)) { fprintf (stderr, "Error: missing argument to watch cmd.\n"); return; } ENDWL++; WL[ENDWL] = strtol (simargv[2], NULL, 0); WLstr[ENDWL] = strdup (simargv[1]); fprintf (stderr, "Added %s (%x) to watchlist, #%d\n",WLstr[ENDWL], WL[ENDWL], ENDWL); } else if (strcmp (simargv[0], "dumpmem") == 0) { unsigned char * p; FILE * dumpfile; if (simargv[1] == NULL) fprintf (stderr, "Error: missing argument to dumpmem cmd.\n"); fprintf (stderr, "Writing dumpfile %s...",simargv[1]); dumpfile = fopen (simargv[1], "w"); p = CPU.memory; fwrite (p, CPU.msize-1, 1, dumpfile); fclose (dumpfile); fprintf (stderr, "done.\n"); } else if (strcmp (simargv[0], "clearstats") == 0) { CPU.cycles = 0; CPU.insts = 0; ENDWL = 0; } else if (strcmp (simargv[0], "verbose") == 0) { issue_messages = 2; } else { fprintf (stderr,"Error: \"%s\" is not a valid M.CORE simulator command.\n", cmd); } } else { fprintf (stderr, "M.CORE sim commands: \n"); fprintf (stderr, " watch \n"); fprintf (stderr, " dumpmem \n"); fprintf (stderr, " clearstats\n"); fprintf (stderr, " verbose\n"); } } void sim_set_callbacks (host_callback *ptr) { callback = ptr; }