/* Simulator for Xilinx MicroBlaze processor Copyright 2009-2015 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 3 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, see . */ #include "config.h" #include #include #include #include #include "bfd.h" #include "gdb/callback.h" #include "libiberty.h" #include "gdb/remote-sim.h" #include "sim-main.h" #include "sim-options.h" #include "microblaze-dis.h" #define target_big_endian (CURRENT_TARGET_BYTE_ORDER == BIG_ENDIAN) static 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 " "%zu 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; } static 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; } } } static void set_initial_gprs (SIM_CPU *cpu) { int i; long space; /* Set up machine just out of reset. */ PC = 0; MSR = 0; /* 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 int tracing = 0; void sim_engine_run (SIM_DESC sd, int next_cpu_nr, /* ignore */ int nr_cpus, /* ignore */ int siggnal) /* ignore */ { SIM_CPU *cpu = STATE_CPU (sd, 0); int needfetch; word inst; enum microblaze_instr op; 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; memops = 0; bonus_cycles = 0; insts = 0; while (1) { /* Fetch the initial instructions that we'll decode. */ inst = MEM_RD_WORD (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) sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_stopped, SIM_SIGTRAP); else if (inst == MICROBLAZE_HALT_INST) { insts += 1; bonus_cycles++; sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_exited, RETREG); } else { switch(op) { #define INSTRUCTION(NAME, OPCODE, TYPE, ACTION) \ case NAME: \ ACTION; \ break; #include "microblaze.isa" #undef INSTRUCTION default: sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_signalled, SIM_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 = MEM_RD_WORD (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 (STATE_VERBOSE_P (sd)) 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 (STATE_VERBOSE_P (sd)) fprintf (stderr, "Cannot have branch or return instructions " "in delay slot (at address 0x%x)\n", PC); sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_signalled, SIM_SIGILL); } else { switch(op) { #define INSTRUCTION(NAME, OPCODE, TYPE, ACTION) \ case NAME: \ ACTION; \ break; #include "microblaze.isa" #undef INSTRUCTION default: sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_signalled, SIM_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"); if (sim_events_tick (sd)) sim_events_process (sd); } /* 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 */ } static int microblaze_reg_store (SIM_CPU *cpu, int rn, unsigned char *memory, int length) { 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; } static int microblaze_reg_fetch (SIM_CPU *cpu, int rn, unsigned char *memory, int length) { long ival; 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; } void sim_info (SIM_DESC sd, int verbose) { SIM_CPU *cpu = STATE_CPU (sd, 0); host_callback *callback = STATE_CALLBACK (sd); 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); } static sim_cia microblaze_pc_get (sim_cpu *cpu) { return cpu->microblaze_cpu.spregs[0]; } static void microblaze_pc_set (sim_cpu *cpu, sim_cia pc) { cpu->microblaze_cpu.spregs[0] = pc; } static void free_state (SIM_DESC sd) { if (STATE_MODULES (sd) != NULL) sim_module_uninstall (sd); sim_cpu_free_all (sd); sim_state_free (sd); } SIM_DESC sim_open (SIM_OPEN_KIND kind, host_callback *cb, struct bfd *abfd, char **argv) { int i; SIM_DESC sd = sim_state_alloc (kind, cb); SIM_ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER); /* The cpu data is kept in a separately allocated chunk of memory. */ if (sim_cpu_alloc_all (sd, 1, /*cgen_cpu_max_extra_bytes ()*/0) != SIM_RC_OK) { free_state (sd); return 0; } if (sim_pre_argv_init (sd, argv[0]) != SIM_RC_OK) { free_state (sd); return 0; } /* getopt will print the error message so we just have to exit if this fails. FIXME: Hmmm... in the case of gdb we need getopt to call print_filtered. */ if (sim_parse_args (sd, argv) != SIM_RC_OK) { free_state (sd); return 0; } /* Check for/establish the a reference program image. */ if (sim_analyze_program (sd, (STATE_PROG_ARGV (sd) != NULL ? *STATE_PROG_ARGV (sd) : NULL), abfd) != SIM_RC_OK) { free_state (sd); return 0; } /* Configure/verify the target byte order and other runtime configuration options. */ if (sim_config (sd) != SIM_RC_OK) { sim_module_uninstall (sd); return 0; } if (sim_post_argv_init (sd) != SIM_RC_OK) { /* Uninstall the modules to avoid memory leaks, file descriptor leaks, etc. */ sim_module_uninstall (sd); return 0; } /* CPU specific initialization. */ for (i = 0; i < MAX_NR_PROCESSORS; ++i) { SIM_CPU *cpu = STATE_CPU (sd, i); CPU_REG_FETCH (cpu) = microblaze_reg_fetch; CPU_REG_STORE (cpu) = microblaze_reg_store; CPU_PC_FETCH (cpu) = microblaze_pc_get; CPU_PC_STORE (cpu) = microblaze_pc_set; set_initial_gprs (cpu); } /* Default to a 8 Mbyte (== 2^23) memory space. */ sim_do_commandf (sd, "memory-size 0x800000"); return sd; } SIM_RC sim_create_inferior (SIM_DESC sd, struct bfd *prog_bfd, char **argv, char **env) { SIM_CPU *cpu = STATE_CPU (sd, 0); PC = bfd_get_start_address (prog_bfd); return SIM_RC_OK; }