/* m6811_cpu.c -- 68HC11 CPU Emulation Copyright 1999, 2000 Free Software Foundation, Inc. Written by Stephane Carrez (stcarrez@worldnet.fr) This file is part of GDB, GAS, and the GNU binutils. GDB, GAS, and the GNU binutils are free software; you can redistribute them and/or modify them 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. GDB, GAS, and the GNU binutils are distributed in the hope that they 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 file; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "sim-main.h" #include "sim-assert.h" #include "sim-module.h" #include "sim-options.h" void cpu_free_frame (sim_cpu* cpu, struct cpu_frame *frame); enum { OPTION_CPU_RESET = OPTION_START, OPTION_EMUL_OS, OPTION_CPU_CONFIG, OPTION_CPU_MODE }; static DECLARE_OPTION_HANDLER (cpu_option_handler); static const OPTION cpu_options[] = { { {"cpu-reset", no_argument, NULL, OPTION_CPU_RESET }, '\0', NULL, "Reset the CPU", cpu_option_handler }, { {"emulos", no_argument, NULL, OPTION_EMUL_OS }, '\0', NULL, "Emulate some OS system calls (read, write, ...)", cpu_option_handler }, { {"cpu-config", required_argument, NULL, OPTION_CPU_CONFIG }, '\0', NULL, "Specify the initial CPU configuration register", cpu_option_handler }, { {NULL, no_argument, NULL, 0}, '\0', NULL, NULL, NULL } }; static SIM_RC cpu_option_handler (SIM_DESC sd, sim_cpu *cpu, int opt, char *arg, int is_command) { sim_cpu *cpu; int val; cpu = STATE_CPU (sd, 0); switch (opt) { case OPTION_CPU_RESET: sim_board_reset (sd); break; case OPTION_EMUL_OS: cpu->cpu_emul_syscall = 1; break; case OPTION_CPU_CONFIG: if (sscanf(arg, "0x%x", &val) == 1 || sscanf(arg, "%d", &val) == 1) { cpu->cpu_config = val; cpu->cpu_use_local_config = 1; } else cpu->cpu_use_local_config = 0; break; case OPTION_CPU_MODE: break; } return SIM_RC_OK; } /* Tentative to keep track of the cpu frame. */ struct cpu_frame* cpu_find_frame (sim_cpu *cpu, uint16 sp) { struct cpu_frame_list *flist; flist = cpu->cpu_frames; while (flist) { struct cpu_frame *frame; frame = flist->frame; while (frame) { if (frame->sp_low <= sp && frame->sp_high >= sp) { cpu->cpu_current_frame = flist; return frame; } frame = frame->up; } flist = flist->next; } return 0; } struct cpu_frame_list* cpu_create_frame_list (sim_cpu *cpu) { struct cpu_frame_list *flist; flist = (struct cpu_frame_list*) malloc (sizeof (struct cpu_frame_list)); flist->frame = 0; flist->next = cpu->cpu_frames; flist->prev = 0; if (flist->next) flist->next->prev = flist; cpu->cpu_frames = flist; cpu->cpu_current_frame = flist; return flist; } void cpu_remove_frame_list (sim_cpu *cpu, struct cpu_frame_list *flist) { struct cpu_frame *frame; if (flist->prev == 0) cpu->cpu_frames = flist->next; else flist->prev->next = flist->next; if (flist->next) flist->next->prev = flist->prev; frame = flist->frame; while (frame) { struct cpu_frame* up = frame->up; cpu_free_frame (cpu, frame); frame = up; } free (flist); } struct cpu_frame* cpu_create_frame (sim_cpu *cpu, uint16 pc, uint16 sp) { struct cpu_frame *frame; frame = (struct cpu_frame*) malloc (sizeof(struct cpu_frame)); frame->up = 0; frame->pc = pc; frame->sp_low = sp; frame->sp_high = sp; return frame; } void cpu_free_frame (sim_cpu *cpu, struct cpu_frame *frame) { free (frame); } uint16 cpu_frame_reg (sim_cpu *cpu, uint16 rn) { struct cpu_frame *frame; if (cpu->cpu_current_frame == 0) return 0; frame = cpu->cpu_current_frame->frame; while (frame) { if (rn == 0) return frame->sp_high; frame = frame->up; rn--; } return 0; } void cpu_call (sim_cpu *cpu, uint16 addr) { #if HAVE_FRAME uint16 pc = cpu->cpu_insn_pc; uint16 sp; struct cpu_frame_list *flist; struct cpu_frame* frame; struct cpu_frame* new_frame; #endif cpu_set_pc (cpu, addr); #if HAVE_FRAME sp = cpu_get_sp (cpu); cpu->cpu_need_update_frame = 0; flist = cpu->cpu_current_frame; if (flist == 0) flist = cpu_create_frame_list (cpu); frame = flist->frame; if (frame && frame->sp_low > sp) frame->sp_low = sp; new_frame = cpu_create_frame (cpu, pc, sp); new_frame->up = frame; flist->frame = new_frame; #endif } void cpu_update_frame (sim_cpu *cpu, int do_create) { #if HAVE_FRAME struct cpu_frame *frame; frame = cpu_find_frame (cpu, cpu_get_sp (cpu)); if (frame) { while (frame != cpu->cpu_current_frame->frame) { struct cpu_frame* up; up = cpu->cpu_current_frame->frame->up; cpu_free_frame (cpu, cpu->cpu_current_frame->frame); cpu->cpu_current_frame->frame = up; } return; } if (do_create) { cpu_create_frame_list (cpu); frame = cpu_create_frame (cpu, cpu_get_pc (cpu), cpu_get_sp (cpu)); cpu->cpu_current_frame->frame = frame; } #endif } void cpu_return (sim_cpu *cpu) { #if HAVE_FRAME uint16 sp = cpu_get_sp (cpu); struct cpu_frame *frame; struct cpu_frame_list *flist; cpu->cpu_need_update_frame = 0; flist = cpu->cpu_current_frame; if (flist && flist->frame && flist->frame->up) { frame = flist->frame->up; if (frame->sp_low <= sp && frame->sp_high >= sp) { cpu_free_frame (cpu, flist->frame); flist->frame = frame; return; } } cpu_update_frame (cpu, 1); #endif } void cpu_print_frame (SIM_DESC sd, sim_cpu *cpu) { struct cpu_frame* frame; int level = 0; if (cpu->cpu_current_frame == 0 || cpu->cpu_current_frame->frame == 0) { sim_io_printf (sd, "No frame.\n"); return; } sim_io_printf (sd, " # PC SP-L SP-H\n"); frame = cpu->cpu_current_frame->frame; while (frame) { sim_io_printf (sd, "%3d 0x%04x 0x%04x 0x%04x\n", level, frame->pc, frame->sp_low, frame->sp_high); frame = frame->up; level++; } } /* Set the stack pointer and re-compute the current frame. */ void cpu_set_sp (sim_cpu *cpu, uint16 val) { cpu->cpu_regs.sp = val; cpu_update_frame (cpu, 0); } int cpu_initialize (SIM_DESC sd, sim_cpu *cpu) { int result; sim_add_option_table (sd, 0, cpu_options); memset (&cpu->cpu_regs, 0, sizeof(cpu->cpu_regs)); cpu->cpu_absolute_cycle = 0; cpu->cpu_current_cycle = 0; cpu->cpu_emul_syscall = 1; cpu->cpu_running = 1; cpu->cpu_stop_on_interrupt = 0; cpu->cpu_frequency = 8 * 1000 * 1000; cpu->cpu_frames = 0; cpu->cpu_current_frame = 0; cpu->cpu_use_elf_start = 0; cpu->cpu_elf_start = 0; cpu->cpu_use_local_config = 0; cpu->cpu_config = M6811_NOSEC | M6811_NOCOP | M6811_ROMON | M6811_EEON; result = interrupts_initialize (cpu); cpu->cpu_is_initialized = 1; return result; } /* Reinitialize the processor after a reset. */ int cpu_reset (sim_cpu *cpu) { cpu->cpu_need_update_frame = 0; cpu->cpu_current_frame = 0; while (cpu->cpu_frames) cpu_remove_frame_list (cpu, cpu->cpu_frames); /* Initialize the config register. It is only initialized at reset time. */ memset (cpu->ios, 0, sizeof (cpu->ios)); cpu->ios[M6811_INIT] = 0x1; /* Output compare registers set to 0xFFFF. */ cpu->ios[M6811_TOC1_H] = 0xFF; cpu->ios[M6811_TOC1_L] = 0xFF; cpu->ios[M6811_TOC2_H] = 0xFF; cpu->ios[M6811_TOC2_L] = 0xFF; cpu->ios[M6811_TOC3_H] = 0xFF; cpu->ios[M6811_TOC4_L] = 0xFF; cpu->ios[M6811_TOC5_H] = 0xFF; cpu->ios[M6811_TOC5_L] = 0xFF; /* Setup the processor registers. */ memset (&cpu->cpu_regs, 0, sizeof(cpu->cpu_regs)); cpu->cpu_absolute_cycle = 0; cpu->cpu_current_cycle = 0; cpu->cpu_is_initialized = 0; /* Reinitialize the CPU operating mode. */ cpu->ios[M6811_HPRIO] = cpu->cpu_mode; return 0; } /* Reinitialize the processor after a reset. */ int cpu_restart (sim_cpu *cpu) { uint16 addr; /* Get CPU starting address depending on the CPU mode. */ if (cpu->cpu_use_elf_start == 0) { switch ((cpu->ios[M6811_HPRIO]) & (M6811_SMOD | M6811_MDA)) { /* Single Chip */ default: case 0 : addr = memory_read16 (cpu, 0xFFFE); break; /* Expanded Multiplexed */ case M6811_MDA: addr = memory_read16 (cpu, 0xFFFE); break; /* Special Bootstrap */ case M6811_SMOD: addr = 0; break; /* Factory Test */ case M6811_MDA | M6811_SMOD: addr = memory_read16 (cpu, 0xFFFE); break; } } else { addr = cpu->cpu_elf_start; } /* Setup the processor registers. */ cpu->cpu_insn_pc = addr; cpu->cpu_regs.pc = addr; cpu->cpu_regs.ccr = M6811_X_BIT | M6811_I_BIT | M6811_S_BIT; cpu->cpu_absolute_cycle = 0; cpu->cpu_is_initialized = 1; cpu->cpu_current_cycle = 0; cpu_call (cpu, addr); return 0; } void print_io_reg_desc (SIM_DESC sd, io_reg_desc *desc, int val, int mode) { while (desc->mask) { if (val & desc->mask) sim_io_printf (sd, "%s", mode == 0 ? desc->short_name : desc->long_name); desc++; } } void print_io_byte (SIM_DESC sd, const char *name, io_reg_desc *desc, uint8 val, uint16 addr) { sim_io_printf (sd, " %-9.9s @ 0x%04x 0x%02x ", name, addr, val); if (desc) print_io_reg_desc (sd, desc, val, 0); } void cpu_ccr_update_tst8 (sim_cpu *proc, uint8 val) { cpu_set_ccr_V (proc, 0); cpu_set_ccr_N (proc, val & 0x80 ? 1 : 0); cpu_set_ccr_Z (proc, val == 0 ? 1 : 0); } uint16 cpu_fetch_relbranch (sim_cpu *cpu) { uint16 addr = (uint16) cpu_fetch8 (cpu); if (addr & 0x0080) { addr |= 0xFF00; } addr += cpu->cpu_regs.pc; return addr; } /* Push all the CPU registers (when an interruption occurs). */ void cpu_push_all (sim_cpu *cpu) { cpu_push_uint16 (cpu, cpu->cpu_regs.pc); cpu_push_uint16 (cpu, cpu->cpu_regs.iy); cpu_push_uint16 (cpu, cpu->cpu_regs.ix); cpu_push_uint16 (cpu, cpu->cpu_regs.d); cpu_push_uint8 (cpu, cpu->cpu_regs.ccr); } /* Handle special instructions. */ void cpu_special (sim_cpu *cpu, enum M6811_Special special) { switch (special) { case M6811_RTI: { uint8 ccr; ccr = cpu_pop_uint8 (cpu); cpu_set_ccr (cpu, ccr); cpu_set_d (cpu, cpu_pop_uint16 (cpu)); cpu_set_x (cpu, cpu_pop_uint16 (cpu)); cpu_set_y (cpu, cpu_pop_uint16 (cpu)); cpu_set_pc (cpu, cpu_pop_uint16 (cpu)); cpu_return (cpu); break; } case M6811_WAI: /* In the ELF-start mode, we are in a special mode where the WAI corresponds to an exit. */ if (cpu->cpu_use_elf_start) { cpu_set_pc (cpu, cpu->cpu_insn_pc); sim_engine_halt (CPU_STATE (cpu), cpu, NULL, NULL_CIA, sim_exited, cpu_get_d (cpu)); return; } /* SCz: not correct... */ cpu_push_all (cpu); break; case M6811_SWI: interrupts_raise (&cpu->cpu_interrupts, M6811_INT_SWI); interrupts_process (&cpu->cpu_interrupts); break; case M6811_EMUL_SYSCALL: case M6811_ILLEGAL: if (cpu->cpu_emul_syscall) { uint8 op = memory_read8 (cpu, cpu_get_pc (cpu) - 1); if (op == 0x41) { cpu_set_pc (cpu, cpu->cpu_insn_pc); sim_engine_halt (CPU_STATE (cpu), cpu, NULL, NULL_CIA, sim_exited, cpu_get_d (cpu)); return; } else { emul_os (op, cpu); } return; } interrupts_raise (&cpu->cpu_interrupts, M6811_INT_ILLEGAL); interrupts_process (&cpu->cpu_interrupts); break; case M6811_TEST: { SIM_DESC sd; sd = CPU_STATE (cpu); /* Breakpoint instruction if we are under gdb. */ if (STATE_OPEN_KIND (sd) == SIM_OPEN_DEBUG) { cpu->cpu_regs.pc --; sim_engine_halt (CPU_STATE (cpu), cpu, 0, cpu_get_pc (cpu), sim_stopped, SIM_SIGTRAP); } /* else this is a nop but not in test factory mode. */ break; } } } void cpu_single_step (sim_cpu *cpu) { cpu->cpu_current_cycle = 0; cpu->cpu_insn_pc = cpu_get_pc (cpu); /* Handle the pending interrupts. If an interrupt is handled, treat this as an single step. */ if (interrupts_process (&cpu->cpu_interrupts)) { cpu->cpu_absolute_cycle += cpu->cpu_current_cycle; return; } /* printf("PC = 0x%04x\n", cpu_get_pc (cpu));*/ cpu_interp (cpu); cpu->cpu_absolute_cycle += cpu->cpu_current_cycle; } /* VARARGS */ void sim_memory_error (sim_cpu *cpu, SIM_SIGNAL excep, uint16 addr, const char *message, ...) { char buf[1024]; va_list args; va_start (args, message); vsprintf (buf, message, args); va_end (args); printf("%s\n", buf); cpu_memory_exception (cpu, excep, addr, buf); } void cpu_memory_exception (sim_cpu *cpu, SIM_SIGNAL excep, uint16 addr, const char *message) { if (cpu->cpu_running == 0) return; cpu_set_pc (cpu, cpu->cpu_insn_pc); sim_engine_halt (CPU_STATE (cpu), cpu, NULL, cpu_get_pc (cpu), sim_stopped, excep); #if 0 cpu->mem_exception = excep; cpu->fault_addr = addr; cpu->fault_msg = strdup (message); if (cpu->cpu_use_handler) { longjmp (&cpu->cpu_exception_handler, 1); } (* cpu->callback->printf_filtered) (cpu->callback, "Fault at 0x%04x: %s\n", addr, message); #endif } void cpu_info (SIM_DESC sd, sim_cpu *cpu) { sim_io_printf (sd, "CPU info:\n"); sim_io_printf (sd, " Absolute cycle: %s\n", cycle_to_string (cpu, cpu->cpu_absolute_cycle)); sim_io_printf (sd, " Syscall emulation: %s\n", cpu->cpu_emul_syscall ? "yes, via 0xcd " : "no"); sim_io_printf (sd, " Memory errors detection: %s\n", cpu->cpu_check_memory ? "yes" : "no"); sim_io_printf (sd, " Stop on interrupt: %s\n", cpu->cpu_stop_on_interrupt ? "yes" : "no"); }