ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/true1541/1541cpu.c

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.