This is 1541cpu.c in view mode; [Download] [Up]
/* * 1541cpu.c - Emulation of the 6502 processor in the Commodore 1541 floppy * disk drive. * * Written by * Ettore Perazzoli (ettore@comm2000.it) * * This file is part of VICE, the Versatile Commodore Emulator. * See README for copyright notice. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA. * */ #define __1541__ #include "vice.h" #include <stdio.h> #include "types.h" #include "true1541.h" #include "via.h" #include "interrupt.h" #include "ui.h" #include "resources.h" #include "viad.h" #include "6510core.h" #include "misc.h" #include "mon.h" /* -------------------------------------------------------------------------- */ /* Define this to enable tracing of 1541 instructions. Warning: this slows it down! */ /* #define TRACE */ /* Force `TRACE' in unstable versions. */ #if 0 && defined UNSTABLE && !defined TRACE #define TRACE #endif #ifdef TRACE /* Flag: do we trace instructions while they are executed? */ int true1541_traceflg; #endif /* Global clock counter. */ CLOCK true1541_clk = 0L; /* Interrupt/alarm status. */ struct cpu_int_status true1541_int_status; /* Value of clk for the last time true1541_cpu_execute() was called. */ static CLOCK last_clk; /* Number of cycles in excess we executed last time true1541_execute() was called. */ static CLOCK last_exc_cycles; /* This is non-zero each time a Read-Modify-Write instructions that accesses memory is executed. We can emulate the RMW bug of the 6502 this way. */ int true1541_rmw_flag = 0; /* Information about the last executed opcode. */ opcode_info_t true1541_last_opcode_info; /* Public copy of the registers. */ mos6510_regs_t true1541_cpu_regs; /* Monitor interface. */ monitor_interface_t true1541_monitor_interface = { /* Pointer to the registers of the CPU. */ &true1541_cpu_regs, /* Pointer to the alarm/interrupt status. */ &true1541_int_status, /* Pointer to the machine's clock counter. */ &true1541_clk, /* Pointer to a function that writes to memory. */ true1541_read, /* Pointer to a function that reads from memory. */ true1541_store, /* Pointer to a function to disable/enable watchpoint checking. */ true1541_toggle_watchpoints }; /* ------------------------------------------------------------------------- */ /* This defines the memory access for the 1541 CPU. */ typedef BYTE REGPARM1 true1541_read_func_t(ADDRESS); typedef void REGPARM2 true1541_store_func_t(ADDRESS, BYTE); static true1541_read_func_t *read_func[0x41]; static true1541_store_func_t *store_func[0x41]; static true1541_read_func_t *read_func_watch[0x41]; static true1541_store_func_t *store_func_watch[0x41]; static true1541_read_func_t *read_func_nowatch[0x41]; static true1541_store_func_t *store_func_nowatch[0x41]; #define LOAD(a) (read_func[(a) >> 10]((ADDRESS)(a))) #define LOAD_ZERO(a) (true1541_ram[(a) & 0xff]) #define LOAD_ADDR(a) (LOAD(a) | (LOAD((a) + 1) << 8)) #define LOAD_ZERO_ADDR(a) (LOAD_ZERO(a) | (LOAD_ZERO((a) + 1) << 8)) #define STORE(a, b) (store_func[(a) >> 10]((ADDRESS)(a), (BYTE)(b))) #define STORE_ZERO(a, b) (true1541_ram[(a) & 0xff] = (b)) static BYTE REGPARM1 true1541_read_ram(ADDRESS address) { return true1541_ram[address & 0x7ff]; } static void REGPARM2 true1541_store_ram(ADDRESS address, BYTE value) { true1541_ram[address & 0x7ff] = value; } static BYTE REGPARM1 true1541_read_rom(ADDRESS address) { return true1541_rom[address & 0x3fff]; } static BYTE REGPARM1 true1541_read_free(ADDRESS address) { return address >> 8; } static void REGPARM2 true1541_store_free(ADDRESS address, BYTE value) { return; } /* This defines the watchpoint memory access for the 1541 CPU. */ static BYTE REGPARM1 true1541_read_watch(ADDRESS address) { mon_watch_push_load_addr(address, e_disk_space); return read_func_nowatch[address>>10](address); } static void REGPARM2 true1541_store_watch(ADDRESS address, BYTE value) { mon_watch_push_store_addr(address, e_disk_space); store_func_nowatch[address>>10](address, value); } #define JUMP(addr) \ do { \ reg_pc = (addr); \ if (reg_pc < 0x1800) { \ bank_base = true1541_ram; \ if (reg_pc > 0x7ff) \ bank_base -= 0x7ff; \ } else if (reg_pc >= 0xc000) \ bank_base = true1541_rom - 0xc000; \ else \ bank_base = NULL; \ } while (0) #define pagezero (true1541_ram) #define pageone (true1541_ram + 0x100) /* ------------------------------------------------------------------------- */ /* This is the external interface for memory access. */ BYTE REGPARM1 true1541_read(ADDRESS address) { return read_func[address >> 10](address); } void REGPARM2 true1541_store(ADDRESS address, BYTE value) { store_func[address >> 10](address, value); } /* ------------------------------------------------------------------------- */ /* This table is used to approximate the sync between the main and the 1541 CPUs, since the two clock rates are different. */ #define MAX_TICKS 0x1000 static unsigned long clk_conv_table[MAX_TICKS + 1]; static unsigned long clk_mod_table[MAX_TICKS + 1]; void true1541_cpu_set_sync_factor(unsigned int sync_factor) { unsigned long i; for (i = 0; i <= MAX_TICKS; i++) { unsigned long tmp = i * (unsigned long)sync_factor; clk_conv_table[i] = tmp / 0x10000; clk_mod_table[i] = tmp % 0x10000; } } /* ------------------------------------------------------------------------- */ static void reset(void) { int preserve_monitor; preserve_monitor = true1541_int_status.global_pending_int & IK_MONITOR; printf("1541 CPU: RESET\n"); cpu_int_status_init(&true1541_int_status, TRUE1541_NUMOFALRM, TRUE1541_NUMOFINT, &true1541_last_opcode_info); true1541_int_status.alarm_handler[A_VIAD1T1] = int_viaD1t1; true1541_int_status.alarm_handler[A_VIAD1T2] = int_viaD1t2; true1541_int_status.alarm_handler[A_VIAD2T1] = int_viaD2t1; true1541_int_status.alarm_handler[A_VIAD2T2] = int_viaD2t2; true1541_clk = 6; reset_viaD1(); reset_viaD2(); if (preserve_monitor) monitor_trap_on(&true1541_int_status); } static void mem_init(void) { int i; for (i = 0; i < 0x41; i++) { read_func_watch[i] = true1541_read_watch; store_func_watch[i] = true1541_store_watch; read_func_nowatch[i] = true1541_read_free; store_func_nowatch[i] = true1541_store_free; } for (i = 0x30; i < 0x40; i++) { read_func_nowatch[i] = true1541_read_rom; } read_func_nowatch[0x0] = read_func_nowatch[0x1] = read_func_nowatch[0x40] = true1541_read_ram; store_func_nowatch[0x0] = store_func_nowatch[0x1] = store_func_nowatch[0x40] = true1541_store_ram; read_func_nowatch[0x6] = read_viaD1; store_func_nowatch[0x6] = store_viaD1; read_func_nowatch[0x7] = read_viaD2; store_func_nowatch[0x7] = store_viaD2; memcpy(read_func, read_func_nowatch, sizeof(true1541_read_func_t *) * 0x41); memcpy(store_func, store_func_nowatch, sizeof(true1541_store_func_t *) * 0x41); } void true1541_toggle_watchpoints(int flag) { if (flag) { memcpy(read_func, read_func_watch, sizeof(true1541_read_func_t *) * 0x41); memcpy(store_func, store_func_watch, sizeof(true1541_store_func_t *) * 0x41); } else { memcpy(read_func, read_func_nowatch, sizeof(true1541_read_func_t *) * 0x41); memcpy(store_func, store_func_nowatch, sizeof(true1541_store_func_t *) * 0x41); } } void true1541_cpu_reset(void) { int preserve_monitor; true1541_clk = 0; last_clk = 0; last_exc_cycles = 0; preserve_monitor = true1541_int_status.global_pending_int & IK_MONITOR; cpu_int_status_init(&true1541_int_status, TRUE1541_NUMOFALRM, TRUE1541_NUMOFINT, &true1541_last_opcode_info); if (preserve_monitor) monitor_trap_on(&true1541_int_status); true1541_trigger_reset(); } void true1541_cpu_init(void) { mem_init(); true1541_cpu_reset(); } inline void true1541_cpu_wake_up(void) { /* FIXME: this value could break some programs, or be way too high for others. Maybe we should put it into a user-definable resource. */ if (clk - last_clk > 0xffffff && true1541_clk > 934639) { printf("1541: skipping cycles.\n"); last_clk = clk; } } inline void true1541_cpu_sleep(void) { /* Currently does nothing. But we might need this hook some day. */ } /* Make sure the 1541 clock counters never overflow; return nonzero if they have been decremented to prevent overflow. */ CLOCK true1541_cpu_prevent_clk_overflow(CLOCK sub) { CLOCK our_sub; /* First, get in sync with what the main CPU has done. */ last_clk -= sub; /* Then, check our own clock counters, and subtract from them if they are going to overflow. The `baseval' is 1 because we don't need the number of cycles subtracted to be multiple of a particular value (unlike the main CPU). */ our_sub = prevent_clk_overflow(&true1541_int_status, &true1541_clk, 1); if (our_sub > 0) { viaD1_prevent_clk_overflow(sub); viaD2_prevent_clk_overflow(sub); } /* Let the caller know what we have done. */ return our_sub; } /* -------------------------------------------------------------------------- */ /* Execute up to the current main CPU clock value. This automatically calculates the corresponding number of clock ticks in the drive. */ void true1541_cpu_execute(void) { static BYTE *bank_base; static CLOCK cycle_accum; static int old_reg_pc; CLOCK cycles; /* #Define the variables for the CPU registers. In the 1541, there is no exporting/importing and we just use global variables. This also makes it possible to let the monitor access the CPU status without too much headache. */ #define reg_a true1541_cpu_regs.reg_a #define reg_x true1541_cpu_regs.reg_x #define reg_y true1541_cpu_regs.reg_y #define reg_pc true1541_cpu_regs.reg_pc #define reg_sp true1541_cpu_regs.reg_sp #define reg_p true1541_cpu_regs.reg_p #define flag_z true1541_cpu_regs.flag_z #define flag_n true1541_cpu_regs.flag_n true1541_cpu_wake_up(); if (old_reg_pc != reg_pc) { /* Update `bank_base'. */ JUMP(reg_pc); old_reg_pc = reg_pc; } cycles = clk - last_clk; while (cycles > 0) { CLOCK stop_clk; if (cycles > MAX_TICKS) { stop_clk = (true1541_clk + clk_conv_table[MAX_TICKS] - last_exc_cycles); cycle_accum += clk_mod_table[MAX_TICKS]; cycles -= MAX_TICKS; } else { stop_clk = (true1541_clk + clk_conv_table[cycles] - last_exc_cycles); cycle_accum += clk_mod_table[cycles]; cycles = 0; } if (cycle_accum >= 0x10000) { cycle_accum -= 0x10000; stop_clk++; } while (true1541_clk < stop_clk) { #ifdef IO_AREA_WARNING #warning IO_AREA_WARNING if (!bank_base) printf ("Executing from I/O area at $%04X: " "$%02X $%02X $%04X at clk %ld\n", reg_pc, p0, p1, p2, clk); #endif /* Include the 6502/6510 CPU emulation core. */ #define CLK true1541_clk #define RMW_FLAG true1541_rmw_flag #define PAGE_ONE (true1541_ram + 0x100) #define LAST_OPCODE_INFO (true1541_last_opcode_info) #define TRACEFLG true1541_traceflg #define CPU_INT_STATUS true1541_int_status /* FIXME: We should activate the monitor here. */ #define JAM() \ do { \ ui_jam_dialog(" " CPU_STR ": JAM at $%04X ", reg_pc); \ DO_INTERRUPT(IK_RESET); \ } while (0) #define ROM_TRAP_ALLOWED() 1 #define ROM_TRAP_HANDLER() \ true1541_trap_handler() #define CALLER e_disk_space #include "6510core.c" } last_exc_cycles = true1541_clk - stop_clk; } last_clk = clk; old_reg_pc = reg_pc; true1541_cpu_sleep(); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.