This is c64cia1.c in view mode; [Download] [Up]
/*
* ../../../src/c64/c64cia1.c
* This file is generated from ../../../src/cia-tmpl.c and ../../../src/c64/c64cia1.def,
* Do not edit!
*/
/*
* cia-tmpl.c - Template file for MOS6526 (CIA) emulation.
*
* Written by
* André Fachat (fachat@physik.tu-chemnitz.de)
*
* Patches and improvements by
* Ettore Perazzoli (ettore@comm2000.it)
* Andreas Boose (boose@rzgw.rz.fh-hannover.de)
*
* 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.
*
*/
/*
* 03jun1997 a.fachat
* complete timer rewrite
*
* There now is a new function, update_cia1(). It computes all differences
* from one given state to a new state at rclk. Well, not everything. For
* the cascade mode (timer B counting timer A) as well as the shift register
* the interrupt function is being used.
*
* The update routine computes, completely independent from the interrupt
* stuff, the timer register values. The update routines are only called when
* a timer register is touched. int_*() is now called only _once_ per
* interrupt, and it is much smaller than the last version. It is disabled
* when the interrupt flag is not set (at least timer B. For timer A there
* must not be a shift register operation and not timer B counting timer A)
*
* The timer B when counting timer A and the Shift register interrupt flags
* may be set a few cycles too late due to late call of int_cia1t*() due to
* opcode execution time. This can be fixed by checking in the beginning of
* read_* and write_* if an int_* is scheduled and executing it before. Then
* the setting of the ICR could also be moved from update to int_*(). But
* the bug only affects the contents of the ICR. The interrupt is generated
* at the right time (hopefully).
*
* There is one HACK to make a game work: in update_cia1() a fix is done for
* Arkanoid. This game counts shift register bits (i.e. TA underflows) by
* setting TA to one-shot. The ICR is read just before the int_cia1ta()
* function is called, and the int bit is missed, so there is a check in
* update_cia1() (this is probably a fix and not a hack... :-)
*
* Still some work has to be put into the dump functions, to really show the
* state of the chip and not the state of the emulation. I.e. we need to
* compute the bits described in the emulator test suite.
*
*/
/*
* 29jun1998 a.fachat
*
* Implementing the peek function assumes that the READ_PA etc macros
* do not have side-effects, i.e. they can be called more than once
* at one clock cycle.
*
*/
#include "vice.h"
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "vmachine.h"
#include "cia.h"
#include "vicii.h"
#include "maincpu.h"
#include "resources.h"
#include "kbd.h"
#include "c64cia.h"
#ifdef HAVE_RS232
#include "rsuser.h"
#endif
#include "interrupt.h"
#undef CIA1_TIMER_DEBUG
#undef CIA1_IO_DEBUG
#define STORE_OFFSET 0
#define READ_OFFSET 0
#define CIAT_STOPPED 0
#define CIAT_RUNNING 1
#define CIAT_COUNTTA 2
#ifdef CIA1_TIMER_DEBUG
#define my_set_int(int_num, value, rclk) \
do { \
if (cia1_debugFlag) \
printf("set_int(rclk=%d, int=%d, d=%d pc=)\n", \
rclk,(int_num),(value)); \
maincpu_set_irq_clk((int_num), (value), (rclk)); \
if ((value)) \
cia1int |= 0x80; \
} while(0)
#else
#define my_set_int(int_num, value, rclk) \
do { \
maincpu_set_irq_clk((int_num), (value), (rclk)); \
if ((value)) \
cia1int |= 0x80; \
} while(0)
#endif
/*
* scheduling int_cia1t[ab] calls -
* warning: int_cia1ta uses maincpu_* stuff!
*/
#define my_set_tai_clk(clk) \
do { \
cia1_tai = clk; \
maincpu_set_alarm_clk(A_CIA1TA, clk); \
} while(0)
#define my_unset_tai() \
do { \
cia1_tai = -1; \
maincpu_unset_alarm(A_CIA1TA); \
} while(0)
#define my_set_tbi_clk(clk) \
do { \
cia1_tbi = clk; \
maincpu_set_alarm_clk(A_CIA1TB, clk); \
} while(0)
#define my_unset_tbi() \
do { \
cia1_tbi = -1; \
maincpu_unset_alarm(A_CIA1TB); \
} while(0)
/*
* Those routines setup the cia1t[ab]i clocks to a value above
* rclk and schedule the next int_cia1t[ab] alarm
*/
#define update_tai(rclk) \
do { \
if(cia1_tai < rclk) { \
int t = cia1int; \
cia1int = 0; \
int_cia1ta(rclk - cia1_tai); \
cia1int |= t; \
} \
} while(0)
#define update_tbi(rclk) \
do { \
if(cia1_tbi < rclk) { \
int t = cia1int; \
cia1int = 0; \
int_cia1tb(rclk - cia1_tbi); \
cia1int |= t; \
} \
} while(0)
/* global */
static BYTE cia1[16];
#if defined(CIA1_TIMER_DEBUG) || defined(CIA1_IO_DEBUG)
int cia1_debugFlag = 0;
#endif
/* local functions */
static int update_cia1(CLOCK rclk);
static void check_cia1todalarm(CLOCK rclk);
void cia1_dump(FILE * fp);
/*
* Local variables
*/
#define cia1ier cia1[CIA_ICR]
static int cia1int; /* Interrupt Flag register for cia 1 */
static CLOCK cia1rdi; /* real clock = clk-offset */
static CLOCK cia1_tau; /* when is the next underflow? */
static CLOCK cia1_tai; /* when is the next int_* scheduled? */
static unsigned int cia1_tal; /* latch value */
static unsigned int cia1_tac; /* counter value */
static unsigned int cia1_tat; /* timer A toggle bit */
static unsigned int cia1_tap; /* timer A port bit */
static int cia1_tas; /* timer state (CIAT_*) */
static CLOCK cia1_tbu; /* when is the next underflow? */
static CLOCK cia1_tbi; /* when is the next int_* scheduled? */
static unsigned int cia1_tbl; /* latch value */
static unsigned int cia1_tbc; /* counter value */
static unsigned int cia1_tbt; /* timer B toggle bit */
static unsigned int cia1_tbp; /* timer B port bit */
static int cia1_tbs; /* timer state (CIAT_*) */
static int cia1sr_bits; /* number of bits still to send * 2 */
static BYTE cia1todalarm[4];
static BYTE cia1todlatch[4];
static char cia1todstopped;
static char cia1todlatched;
static int cia1todticks = 100000; /* approx. a 1/10 sec. */
static BYTE cia1flag = 0;
/* ------------------------------------------------------------------------- */
/* CIA1 */
/* Flag: Are the 3 C128 extended rows enabled? */
static int extended_keyboard_rows_enabled;
/* Mask for the extended keyboard rows. */
static BYTE extended_keyboard_rows_mask;
void cia1_enable_extended_keyboard_rows(int flag)
{
extended_keyboard_rows_enabled = flag;
}
void cia1_set_extended_keyboard_rows_mask(BYTE value)
{
extended_keyboard_rows_mask = value;
}
inline static void check_cia1todalarm(CLOCK rclk)
{
if (!memcmp(cia1todalarm, cia1 + CIA_TOD_TEN, sizeof(cia1todalarm))) {
cia1int |= CIA_IM_TOD;
if (cia1[CIA_ICR] & CIA_IM_TOD) {
my_set_int(I_CIA1TOD, IK_IRQ, rclk);
}
}
}
static int update_cia1(CLOCK rclk)
{
int tmp = 0;
unsigned int ista = 0;
BYTE sif = (cia1int & cia1ier & 0x7f);
/* Tick when we virtually added an interrupt flag first. */
CLOCK added_int_clk = (cia1int & 0x80) ? rclk - 3 : CLOCK_MAX;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1: update: rclk=%d, tas=%d, tau=%d, tal=%u, ",
rclk, cia1_tas, cia1_tau, cia1_tal);
#endif
if (cia1_tas == CIAT_RUNNING) {
if (rclk < cia1_tau + 1) {
cia1_tac = cia1_tau - rclk;
tmp = 0;
} else {
if (cia1[CIA_CRA] & 0x08) {
tmp = 1;
if ((cia1ier & CIA_IM_TA)
&& (cia1_tau < added_int_clk))
added_int_clk = cia1_tau;
cia1_tau = 0;
my_unset_tai();
cia1_tac = cia1_tal;
cia1_tas = CIAT_STOPPED;
cia1[CIA_CRA] &= 0xfe;
/* this is a HACK for arkanoid... */
if (cia1sr_bits) {
cia1sr_bits--;
if(cia1sr_bits==16) {
#ifdef HAVE_RS232
if (rsuser_enabled) {
userport_serial_write_sr(cia1[CIA_SDR]);
}
#endif
}
if (!cia1sr_bits) {
cia1int |= CIA_IM_SDR;
if ((cia1ier & CIA_IM_SDR)
&& (cia1_tau < added_int_clk))
added_int_clk = cia1_tau;
}
}
} else {
tmp = (rclk - cia1_tau - 1) / (cia1_tal + 1);
cia1_tau += tmp * (cia1_tal + 1);
if ((cia1ier & CIA_IM_TA)
&& (cia1_tau < added_int_clk))
added_int_clk = cia1_tau;
cia1_tau += 1 * (cia1_tal + 1);
cia1_tac = cia1_tau - rclk;
}
if (cia1_tac == cia1_tal)
ista = 1;
cia1int |= CIA_IM_TA;
}
}
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("aic=%d, tac-> %u, tau-> %d\n tmp=%u, ", added_int_clk, cia1_tac, cia1_tau, tmp);
#endif
if (cia1[CIA_CRA] & 0x04) {
cia1_tap = cia1_tat;
} else {
cia1_tap = cia1_tac ? 0 : 1;
}
cia1_tbp = 0;
if (cia1_tbs == CIAT_RUNNING) {
if (rclk < cia1_tbu + 1) {
cia1_tbc = cia1_tbu - rclk;
} else {
if (cia1[CIA_CRB] & 0x08) {
tmp = 1;
if ((cia1ier & CIA_IM_TB) && (cia1_tbu < added_int_clk))
added_int_clk = cia1_tbu;
cia1_tbu = 0;
my_unset_tbi();
cia1_tbc = cia1_tbl;
cia1_tbs = CIAT_STOPPED;
cia1[CIA_CRB] &= 0xfe;
} else {
tmp = (rclk - cia1_tbu - 1) / (cia1_tbl + 1);
cia1_tbu += tmp * (cia1_tbl + 1);
if ((cia1ier & CIA_IM_TB) && (cia1_tbu < added_int_clk))
added_int_clk = cia1_tbu;
cia1_tbu += 1 * (cia1_tbl + 1);
cia1_tbc = cia1_tbu - rclk;
}
if (!cia1_tbc)
cia1_tbc = cia1_tbl;
cia1int |= CIA_IM_TB;
}
} else if (cia1_tbs == CIAT_COUNTTA) {
/* missing: set added_int */
if ((!cia1_tbc) && ista) {
cia1_tbp = 1;
cia1_tbc = cia1_tbl;
cia1int |= CIA_IM_TB;
}
}
if (cia1[CIA_CRB] & 0x04) {
cia1_tbp ^= cia1_tbt;
} else {
cia1_tbp = cia1_tbc ? 0 : 1;
}
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("tbc-> %u, tbu-> %d, int %02x ->",
cia1_tbc, cia1_tbu, cia1int);
#endif
/* have we changed the interrupt flags? */
if (sif != (cia1ier & cia1int & 0x7f)) {
/* if we do not read ICR, do standard operation */
if (rclk != cia1rdi) {
if (cia1ier & cia1int & 0x7f) {
/* sets bit 7 */
my_set_int(I_CIA1FL, IK_IRQ, rclk);
}
} else {
if (added_int_clk == cia1rdi) {
cia1int &= 0x7f;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1: TA Reading ICR at rclk=%d prevented IRQ\n",
rclk);
#endif
} else {
if (cia1ier & cia1int & 0x7f) {
/* sets bit 7 */
my_set_int(I_CIA1FL, IK_IRQ, rclk);
}
}
}
}
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("%02x\n", cia1int);
#endif
/* return true sif interrupt line is set at this clock time */
return (!sif) && (cia1int & cia1ier & 0x7f);
}
/* ------------------------------------------------------------------------- */
void reset_cia1(void)
{
int i;
for (i = 0; i < 16; i++)
cia1[i] = 0;
cia1rdi = 0;
cia1sr_bits = 0;
cia1_tac = cia1_tbc = 0xffff;
cia1_tal = cia1_tbl = 0xffff;
cia1_tas = CIAT_STOPPED;
cia1_tbs = CIAT_STOPPED;
cia1_tat = 0;
cia1_tbt = 0;
my_unset_tbi();
my_unset_tai();
memset(cia1todalarm, 0, sizeof(cia1todalarm));
cia1todlatched = 0;
cia1todstopped = 0;
maincpu_set_alarm(A_CIA1TOD, cia1todticks);
cia1int = 0;
}
void REGPARM2 store_cia1(ADDRESS addr, BYTE byte)
{
CLOCK rclk;
addr &= 0xf;
vic_ii_handle_pending_alarms(maincpu_num_write_cycles());
rclk = clk - STORE_OFFSET;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("store cia1[%02x] %02x @ clk=%d, pc=\n",
(int) addr, (int) byte, rclk);
#endif
switch (addr) {
case CIA_PRA: /* port A */
case CIA_DDRA:
cia1[addr] = byte;
break;
case CIA_PRB: /* port B */
case CIA_DDRB:
if ((cia1[CIA_CRA] | cia1[CIA_CRB]) & 0x02) {
update_cia1(rclk);
if (cia1[CIA_CRA] & 0x02) {
byte &= 0xbf;
if (cia1_tap)
byte |= 0x40;
}
if (cia1[CIA_CRB] & 0x02) {
byte &= 0x7f;
if (cia1_tbp)
byte |= 0x80;
}
}
{
static int old_lp = 0x10;
int new_lp;
cia1[addr] = byte;
/* Handle software-triggered light pen. */
new_lp = (cia1[CIA_PRB] | ~cia1[CIA_DDRB]) & 0x10;
if (new_lp != old_lp) {
vic_ii_trigger_light_pen(rclk);
old_lp = new_lp;
}
}
break;
/* This handles the timer latches. The kludgy stuff is an attempt
emulate the correct behavior when the latch is written to during
an underflow. */
case CIA_TAL:
update_tai(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk - 1);
if (cia1_tac == cia1_tal && cia1_tas == CIAT_RUNNING) {
cia1_tac = cia1_tal = (cia1_tal & 0xff00) | byte;
cia1_tau = rclk + cia1_tac;
update_cia1(rclk);
} else {
cia1_tal = (cia1_tal & 0xff00) | byte;
}
break;
case CIA_TBL:
update_tbi(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk - 1);
if (cia1_tbc == cia1_tbl && cia1_tbs == CIAT_RUNNING) {
cia1_tbc = cia1_tbl = (cia1_tbl & 0xff00) | byte;
cia1_tbu = rclk + cia1_tbc + 1;
update_cia1(rclk);
} else {
cia1_tbl = (cia1_tbl & 0xff00) | byte;
}
break;
case CIA_TAH:
update_tai(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk - 1);
if (cia1_tac == cia1_tal && cia1_tas == CIAT_RUNNING) {
cia1_tac = cia1_tal = (cia1_tal & 0x00ff) | (byte << 8);
cia1_tau = rclk + cia1_tac;
update_cia1(rclk);
} else {
cia1_tal = (cia1_tal & 0x00ff) | (byte << 8);
}
if (cia1_tas == CIAT_STOPPED)
cia1_tac = cia1_tal;
break;
case CIA_TBH:
update_tbi(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk - 1);
if (cia1_tbc == cia1_tbl && cia1_tbs == CIAT_RUNNING) {
cia1_tbc = cia1_tbl = (cia1_tbl & 0x00ff) | (byte << 8);
cia1_tbu = rclk + cia1_tbc + 1;
update_cia1(rclk);
} else {
cia1_tbl = (cia1_tbl & 0x00ff) | (byte << 8);
}
if (cia1_tbs == CIAT_STOPPED)
cia1_tbc = cia1_tbl;
break;
/*
* TOD clock is stopped by writing Hours, and restarted
* upon writing Tenths of Seconds.
*
* REAL: TOD register + (wallclock - cia1todrel)
* VIRT: TOD register + (cycles - begin)/cycles_per_sec
*/
case CIA_TOD_TEN: /* Time Of Day clock 1/10 s */
case CIA_TOD_HR: /* Time Of Day clock hour */
case CIA_TOD_SEC: /* Time Of Day clock sec */
case CIA_TOD_MIN: /* Time Of Day clock min */
/* Mask out undefined bits and flip AM/PM on hour 12
(Andreas Boose <boose@rzgw.rz.fh-hannover.de> 1997/10/11). */
if (addr == CIA_TOD_HR)
byte = ((byte & 0x1f) == 18) ? (byte & 0x9f) ^ 0x80 : byte & 0x9f;
if (cia1[CIA_CRB] & 0x80)
cia1todalarm[addr - CIA_TOD_TEN] = byte;
else {
if (addr == CIA_TOD_TEN)
cia1todstopped = 0;
if (addr == CIA_TOD_HR)
cia1todstopped = 1;
cia1[addr] = byte;
}
check_cia1todalarm(rclk);
break;
case CIA_SDR: /* Serial Port output buffer */
cia1[addr] = byte;
if ((cia1[CIA_CRA] & 0x40) == 0x40) {
if (cia1sr_bits <= 16) {
if(!cia1sr_bits) {
#ifdef HAVE_RS232
if (rsuser_enabled) {
userport_serial_write_sr(cia1[CIA_SDR]);
}
#endif
}
if(cia1sr_bits < 16) {
/* switch timer A alarm on again, if necessary */
update_cia1(rclk);
if (cia1_tau) {
my_set_tai_clk(cia1_tau + 1);
}
}
cia1sr_bits += 16;
#if defined (CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: start SDR rclk=%d\n", rclk);
#endif
}
}
break;
/* Interrupts */
case CIA_ICR: /* Interrupt Control Register */
update_cia1(rclk);
#if defined (CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1 set CIA_ICR: 0x%x\n", byte);
#endif
if (byte & CIA_IM_SET) {
cia1ier |= (byte & 0x7f);
} else {
cia1ier &= ~(byte & 0x7f);
}
/* This must actually be delayed one cycle! */
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf(" set icr: ifr & ier & 0x7f -> %02x, int=%02x\n",
cia1ier & cia1int & 0x7f, cia1int);
#endif
if (cia1ier & cia1int & 0x7f) {
my_set_int(I_CIA1FL, IK_IRQ, rclk);
}
if (cia1ier & (CIA_IM_TA + CIA_IM_TB)) {
if ((cia1ier & CIA_IM_TA) && cia1_tau) {
my_set_tai_clk(cia1_tau + 1);
}
if ((cia1ier & CIA_IM_TB) && cia1_tbu) {
my_set_tbi_clk(cia1_tbu + 1);
}
}
/* Control */
break;
case CIA_CRA: /* control register A */
update_tai(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk);
#if defined (CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1 set CIA_CRA: 0x%x (clk=%d, pc=, tal=%u, tac=%u)\n",
byte, rclk, /*program_counter,*/ cia1_tal, cia1_tac);
#endif
/* bit 7 tod frequency */
/* bit 6 serial port mode */
/* bit 4 force load */
if (byte & 0x10) {
cia1_tac = cia1_tal;
if (cia1_tas == CIAT_RUNNING) {
cia1_tau = rclk + cia1_tac + 2;
my_set_tai_clk(cia1_tau + 1);
}
}
/* bit 3 timer run mode */
/* bit 2 & 1 timer output to PB6 */
/* bit 0 start/stop timer */
/* bit 5 timer count mode */
if ((byte & 1) && !(cia1[CIA_CRA] & 1))
cia1_tat = 1;
if ((byte ^ cia1[addr]) & 0x21) {
if ((byte & 0x21) == 0x01) { /* timer just started */
cia1_tas = CIAT_RUNNING;
cia1_tau = rclk + (cia1_tac + 1) + ((byte & 0x10) >> 4);
my_set_tai_clk(cia1_tau + 1);
} else { /* timer just stopped */
cia1_tas = CIAT_STOPPED;
cia1_tau = 0;
/* 1 cycle delay for counter stop. */
if (!(byte & 0x10)) {
/* 1 cycle delay for counter stop. This must only happen
if we are not forcing load at the same time (i.e. bit
4 in `byte' is zero). */
if (cia1_tac > 0)
cia1_tac--;
}
my_unset_tai();
}
}
#if defined (CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf(" -> tas=%d, tau=%d\n", cia1_tas, cia1_tau);
#endif
cia1[addr] = byte & 0xef; /* remove strobe */
break;
case CIA_CRB: /* control register B */
update_tbi(rclk); /* schedule alarm in case latch value is changed */
update_cia1(rclk);
#if defined (CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1 set CIA_CRB: 0x%x (clk=%d, pc=, tbl=%u, tbc=%u)\n",
byte, rclk, cia1_tbl, cia1_tbc);
#endif
/* bit 7 set alarm/tod clock */
/* bit 4 force load */
if (byte & 0x10) {
cia1_tbc = cia1_tbl;
if (cia1_tbs == CIAT_RUNNING) {
cia1_tbu = rclk + cia1_tbc + 2;
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d force load: set tbu alarm to %d\n", rclk, cia1_tbu);
#endif
my_set_tbi_clk(cia1_tbu + 1);
}
}
/* bit 3 timer run mode */
/* bit 2 & 1 timer output to PB6 */
/* bit 0 stbrt/stop timer */
/* bit 5 & 6 timer count mode */
if ((byte & 1) && !(cia1[CIA_CRB] & 1))
cia1_tbt = 1;
if ((byte ^ cia1[addr]) & 0x61) {
if ((byte & 0x61) == 0x01) { /* timer just started */
cia1_tbu = rclk + (cia1_tbc + 1) + ((byte & 0x10) >> 4);
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d start timer: set tbu alarm to %d\n", rclk, cia1_tbu);
#endif
my_set_tbi_clk(cia1_tbu + 1);
cia1_tbs = CIAT_RUNNING;
} else { /* timer just stopped */
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d stop timer: set tbu alarm\n", rclk);
#endif
my_unset_tbi();
cia1_tbu = 0;
if (!(byte & 0x10)) {
/* 1 cycle delay for counter stop. This must only happen
if we are not forcing load at the same time (i.e. bit
4 in `byte' is zero). */
if (cia1_tbc > 0)
cia1_tbc--;
}
/* this should actually read (byte & 0x61), but as CNT is high
by default, bit 0x20 is a `don't care' bit */
if ((byte & 0x41) == 0x41) {
cia1_tbs = CIAT_COUNTTA;
update_cia1(rclk);
/* switch timer A alarm on if necessary */
if (cia1_tau) {
my_set_tai_clk(cia1_tau + 1);
}
} else {
cia1_tbs = CIAT_STOPPED;
}
}
}
cia1[addr] = byte & 0xef; /* remove strobe */
break;
default:
cia1[addr] = byte;
} /* switch */
}
/* ------------------------------------------------------------------------- */
BYTE REGPARM1 read_cia1(ADDRESS addr)
{
#if defined( CIA1_TIMER_DEBUG )
BYTE read_cia1_(ADDRESS addr);
BYTE tmp = read_cia1_(addr);
if (cia1_debugFlag)
printf("read cia1[%x] returns %02x @ clk=%d, pc=\n",
addr, tmp, clk - READ_OFFSET);
return tmp;
}
BYTE read_cia1_(ADDRESS addr)
{
#endif
static BYTE byte;
CLOCK rclk;
addr &= 0xf;
vic_ii_handle_pending_alarms(0);
rclk = clk - READ_OFFSET;
switch (addr) {
case CIA_PRA: /* port A */
{
BYTE val = cia1[CIA_PRA] | ~cia1[CIA_DDRA];
BYTE msk = (cia1[CIA_PRB] | ~cia1[CIA_DDRB]) & ~joy[2];
BYTE m;
int i;
for (m = 0x1, i = 0; i < 8; m <<= 1, i++)
if (!(msk & m))
val &= ~rev_keyarr[i];
byte = val & ~joy[2];
}
return byte;
break;
case CIA_PRB: /* port B */
{
BYTE val = ~cia1[CIA_DDRB];
BYTE msk = (cia1[CIA_PRA] | ~cia1[CIA_DDRA]) & ~joy[1];
BYTE m;
int i;
for (m = 0x1, i = 0; i < 8; m <<= 1, i++)
if (!(msk & m))
val &= ~keyarr[i];
if (extended_keyboard_rows_enabled)
for (m = 0x1, i = 8; i < 11; m <<= 1, i++)
if (!(extended_keyboard_rows_mask & m))
val &= ~keyarr[i];
byte = (val | (cia1[CIA_PRB] & cia1[CIA_DDRB])) & ~joy[1];
}
if ((cia1[CIA_CRA] | cia1[CIA_CRB]) & 0x02) {
update_cia1(rclk);
if (cia1[CIA_CRA] & 0x02) {
byte &= 0xbf;
if (cia1_tap)
byte |= 0x40;
}
if (cia1[CIA_CRB] & 0x02) {
byte &= 0x7f;
if (cia1_tbp)
byte |= 0x80;
}
}
return byte;
break;
/* Timers */
case CIA_TAL: /* timer A low */
update_cia1(rclk);
return ((cia1_tac ? cia1_tac : cia1_tal) & 0xff);
case CIA_TAH: /* timer A high */
update_cia1(rclk);
return ((cia1_tac ? cia1_tac : cia1_tal) >> 8) & 0xff;
case CIA_TBL: /* timer B low */
update_cia1(rclk);
return cia1_tbc & 0xff;
case CIA_TBH: /* timer B high */
update_cia1(rclk);
return (cia1_tbc >> 8) & 0xff;
/*
* TOD clock is latched by reading Hours, and released
* upon reading Tenths of Seconds. The counter itself
* keeps ticking all the time.
* Also note that this latching is different from the input one.
*/
case CIA_TOD_TEN: /* Time Of Day clock 1/10 s */
case CIA_TOD_SEC: /* Time Of Day clock sec */
case CIA_TOD_MIN: /* Time Of Day clock min */
case CIA_TOD_HR: /* Time Of Day clock hour */
if (!cia1todlatched)
memcpy(cia1todlatch, cia1 + CIA_TOD_TEN, sizeof(cia1todlatch));
if (addr == CIA_TOD_TEN)
cia1todlatched = 0;
if (addr == CIA_TOD_HR)
cia1todlatched = 1;
return cia1[addr];
case CIA_SDR: /* Serial Port Shift Register */
#if 0 /*def DEBUG */
cia1_dump(stdout);
/* little hack .... */
{
int i;
printf("\nmaincpu_ints:");
for (i = 0; i < NUMOFINT; i++) {
printf(" %d", maincpu_int_status.pending_int[i]);
}
printf("\n");
}
#endif
return (cia1[addr]);
/* Interrupts */
case CIA_ICR: /* Interrupt Flag Register */
{
BYTE t = 0;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1 read intfl: rclk=%d, alarm_ta=%d, alarm_tb=%d\n",
rclk, maincpu_int_status.alarm_clk[A_CIA1TA],
maincpu_int_status.alarm_clk[A_CIA1TB]);
#endif
cia1rdi = rclk;
t = cia1int; /* we clean cia1int anyway, so make int_* */
cia1int = 0; /* believe it is already */
if (rclk >= cia1_tai)
int_cia1ta(rclk - cia1_tai);
if (rclk >= cia1_tbi)
int_cia1tb(rclk - cia1_tbi);
cia1int |= t; /* some bits can be set -> or with old value */
update_cia1(rclk);
t = cia1int | cia1flag;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1 read intfl gives cia1int=%02x -> %02x @"
" PC=, sr_bits=%d, clk=%d, ta=%d, tb=%d\n",
cia1int, t, cia1sr_bits, clk,
(cia1_tac ? cia1_tac : cia1_tal),
cia1_tbc);
#endif
cia1flag = 0;
cia1int = 0;
my_set_int(I_CIA1FL, 0, rclk);
return (t);
}
} /* switch */
return (cia1[addr]);
}
BYTE REGPARM1 peek_cia1(ADDRESS addr)
{
/* This code assumes that update_cia1 is a projector - called at
* the same cycle again it doesn't change anything. This way
* it does not matter if we call it from peek first in the monitor
* and probably the same cycle again when the CPU runs on...
*/
CLOCK rclk;
addr &= 0xf;
vic_ii_handle_pending_alarms(0);
rclk = clk - READ_OFFSET;
switch (addr) {
/*
* TOD clock is latched by reading Hours, and released
* upon reading Tenths of Seconds. The counter itself
* keeps ticking all the time.
* Also note that this latching is different from the input one.
*/
case CIA_TOD_TEN: /* Time Of Day clock 1/10 s */
case CIA_TOD_SEC: /* Time Of Day clock sec */
case CIA_TOD_MIN: /* Time Of Day clock min */
case CIA_TOD_HR: /* Time Of Day clock hour */
if (!cia1todlatched)
memcpy(cia1todlatch, cia1 + CIA_TOD_TEN, sizeof(cia1todlatch));
return cia1[addr];
/* Interrupts */
case CIA_ICR: /* Interrupt Flag Register */
{
BYTE t = 0;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1 read intfl: rclk=%d, alarm_ta=%d, alarm_tb=%d\n",
rclk, maincpu_int_status.alarm_clk[A_CIA1TA],
maincpu_int_status.alarm_clk[A_CIA1TB]);
#endif
cia1rdi = rclk;
t = cia1int; /* we clean cia1int anyway, so make int_* */
cia1int = 0; /* believe it is already */
if (rclk >= cia1_tai)
int_cia1ta(rclk - cia1_tai);
if (rclk >= cia1_tbi)
int_cia1tb(rclk - cia1_tbi);
cia1int |= t; /* some bits can be set -> or with old value */
update_cia1(rclk);
t = cia1int | cia1flag;
#ifdef CIA1_TIMER_DEBUG
if (cia1_debugFlag)
printf("CIA1 read intfl gives cia1int=%02x -> %02x @"
" PC=, sr_bits=%d, clk=%d, ta=%d, tb=%d\n",
cia1int, t, cia1sr_bits, clk,
(cia1_tac ? cia1_tac : cia1_tal),
cia1_tbc);
#endif
/*
cia1flag = 0;
cia1int = 0;
my_set_int(I_CIA1FL, 0, rclk);
*/
return (t);
}
default:
break;
} /* switch */
return read_cia1(addr);
}
/* ------------------------------------------------------------------------- */
int int_cia1ta(long offset)
{
CLOCK rclk = clk - offset;
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: int_cia1ta(rclk = %u, tal = %u, cra=%02x\n",
rclk, cia1_tal, cia1[CIA_CRA]);
#endif
cia1_tat = (cia1_tat + 1) & 1;
if ((cia1_tas == CIAT_RUNNING) && !(cia1[CIA_CRA] & 8)) {
/* if we do not need alarm, no PB6, no shift register, and not timer B
counting timer A, then we can savely skip alarms... */
if ( ( (cia1ier & CIA_IM_TA) &&
(!(cia1int & 0x80)) )
|| (cia1[CIA_CRA] & 0x42)
|| (cia1_tbs == CIAT_COUNTTA)) {
if(offset > cia1_tal+1) {
my_set_tai_clk(
clk - (offset % (cia1_tal+1)) + cia1_tal + 1 );
} else {
my_set_tai_clk(rclk + cia1_tal + 1 );
}
} else {
/* cia1_tai = rclk + cia1_tal +1; - now keeps tai */
/* printf("cia1 unset alarm: clk=%d, rclk=%d, rdi=%d -> tai=%d\n",
clk, rclk, cia1rdi, cia1_tai); */
maincpu_unset_alarm(A_CIA1TA); /* do _not_ clear cia1_tai */
}
} else {
#if 0
cia1_tas = CIAT_STOPPED;
cia1[CIA_CRA] &= 0xfe; /* clear run flag. Correct? */
cia1_tau = 0;
#endif
my_unset_tai();
}
if (cia1[CIA_CRA] & 0x40) {
if (cia1sr_bits) {
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d SDR: timer A underflow, bits=%d\n",
rclk, cia1sr_bits);
#endif
if (!(--cia1sr_bits)) {
cia1int |= CIA_IM_SDR;
}
if(cia1sr_bits == 16) {
#ifdef HAVE_RS232
if (rsuser_enabled) {
userport_serial_write_sr(cia1[CIA_SDR]);
}
#endif
}
}
}
if (cia1_tbs == CIAT_COUNTTA) {
if (!cia1_tbc) {
cia1_tbc = cia1_tbl;
cia1_tbu = rclk;
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: timer B underflow when counting timer A occured, rclk=%d!\n", rclk);
#endif
cia1int |= CIA_IM_TB;
my_set_tbi_clk(rclk);
} else {
cia1_tbc--;
}
}
/* CIA_IM_TA is not set here, as it can be set in update(), reset
by reading the ICR and then set again here because of delayed
calling of int() */
if ((IK_IRQ == IK_NMI && cia1rdi != rclk - 1)
|| (IK_IRQ == IK_IRQ && cia1rdi < rclk - 1)) {
if ((cia1int | CIA_IM_TA) & cia1ier & 0x7f) {
my_set_int(I_CIA1FL, IK_IRQ, rclk);
}
}
return 0;
}
/*
* Timer B can run in 2 (4) modes
* cia1[f] & 0x60 == 0x00 count system 02 pulses
* cia1[f] & 0x60 == 0x40 count timer A underflows
* cia1[f] & 0x60 == 0x20 | 0x60 count CNT pulses => counter stops
*/
int int_cia1tb(long offset)
{
CLOCK rclk = clk - offset;
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: timer B int_cia1tb(rclk=%d, tbs=%d)\n", rclk, cia1_tbs);
#endif
cia1_tbt = (cia1_tbt + 1) & 1;
/* running and continous, then next alarm */
if (cia1_tbs == CIAT_RUNNING) {
if (!(cia1[CIA_CRB] & 8)) {
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d cia1tb: set tbu alarm to %d\n", rclk, rclk + cia1_tbl + 1);
#endif
/* if no interrupt flag we can safely skip alarms */
if (cia1ier & CIA_IM_TB) {
if(offset > cia1_tbl+1) {
my_set_tbi_clk(
clk - (offset % (cia1_tbl+1)) + cia1_tbl + 1);
} else {
my_set_tbi_clk(rclk + cia1_tbl + 1);
}
} else {
/* cia1_tbi = rclk + cia1_tbl + 1; */
maincpu_unset_alarm(A_CIA1TB);
}
} else {
#if 0
cia1_tbs = CIAT_STOPPED;
cia1[CIA_CRB] &= 0xfe; /* clear start bit */
cia1_tbu = 0;
#endif /* 0 */
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d cia1tb: unset tbu alarm\n", rclk);
#endif
my_unset_tbi();
}
} else {
if (cia1_tbs == CIAT_COUNTTA) {
if ((cia1[CIA_CRB] & 8)) {
cia1_tbs = CIAT_STOPPED;
cia1[CIA_CRB] &= 0xfe; /* clear start bit */
cia1_tbu = 0;
}
}
cia1_tbu = 0;
my_unset_tbi();
#if defined(CIA1_TIMER_DEBUG)
if (cia1_debugFlag)
printf("CIA1: rclk=%d cia1tb: unset tbu alarm\n", rclk);
#endif
}
if ((IK_IRQ == IK_NMI && cia1rdi != rclk - 1)
|| (IK_IRQ == IK_IRQ && cia1rdi < rclk - 1)) {
if ((cia1int | CIA_IM_TB) & cia1ier & 0x7f) {
my_set_int(I_CIA1FL, IK_IRQ, rclk);
}
}
return 0;
}
/* ------------------------------------------------------------------------- */
void cia1_set_flag(void)
{
cia1int |= CIA_IM_FLG;
if (cia1[CIA_ICR] & CIA_IM_FLG) {
my_set_int(I_CIA1FL, IK_IRQ, clk);
}
}
void cia1_set_sdr(BYTE data)
{
cia1[CIA_SDR] = data;
cia1int |= CIA_IM_SDR;
if (cia1[CIA_ICR] & CIA_IM_SDR) {
my_set_int(I_CIA1FL, IK_IRQ, clk);
}
}
/* ------------------------------------------------------------------------- */
int int_cia1tod(long offset)
{
int t, pm;
CLOCK rclk = clk - offset;
#ifdef DEBUG
if (cia1_debugFlag)
printf("CIA1: TOD timer event (1/10 sec tick), tod=%02x:%02x,%02x.%x\n",
cia1[CIA_TOD_HR], cia1[CIA_TOD_MIN], cia1[CIA_TOD_SEC],
cia1[CIA_TOD_TEN]);
#endif
/* set up new int */
maincpu_set_alarm(A_CIA1TOD, cia1todticks);
if (!cia1todstopped) {
/* inc timer */
t = bcd2byte(cia1[CIA_TOD_TEN]);
t++;
cia1[CIA_TOD_TEN] = byte2bcd(t % 10);
if (t >= 10) {
t = bcd2byte(cia1[CIA_TOD_SEC]);
t++;
cia1[CIA_TOD_SEC] = byte2bcd(t % 60);
if (t >= 60) {
t = bcd2byte(cia1[CIA_TOD_MIN]);
t++;
cia1[CIA_TOD_MIN] = byte2bcd(t % 60);
if (t >= 60) {
pm = cia1[CIA_TOD_HR] & 0x80;
t = bcd2byte(cia1[CIA_TOD_HR] & 0x1f);
if (!t)
pm ^= 0x80; /* toggle am/pm on 0:59->1:00 hr */
t++;
t = t % 12 | pm;
cia1[CIA_TOD_HR] = byte2bcd(t);
}
}
}
#ifdef DEBUG
if (cia1_debugFlag)
printf("CIA1: TOD after event :tod=%02x:%02x,%02x.%x\n",
cia1[CIA_TOD_HR], cia1[CIA_TOD_MIN], cia1[CIA_TOD_SEC],
cia1[CIA_TOD_TEN]);
#endif
/* check alarm */
check_cia1todalarm(rclk);
}
return 0;
}
/* -------------------------------------------------------------------------- */
void cia1_prevent_clk_overflow(CLOCK sub)
{
update_tai(clk);
update_tbi(clk);
update_cia1(clk);
if(cia1_tai && (cia1_tai != -1))
cia1_tai -= sub;
if(cia1_tbi && (cia1_tbi != -1))
cia1_tbi -= sub;
if (cia1_tau)
cia1_tau -= sub;
if (cia1_tbu)
cia1_tbu -= sub;
if (cia1rdi > sub)
cia1rdi -= sub;
else
cia1rdi = 0;
}
#if 0
void cia1_dump(FILE * fp)
{
update_cia1(clk);
fprintf(fp, "[CIA1]\n");
fprintf(fp, "PA %d %d\n", cia1[CIA_PRA], cia1[CIA_DDRA]);
fprintf(fp, "PB %d %d\n", cia1[CIA_PRB], cia1[CIA_DDRB]);
fprintf(fp, "TA %u %u %d [$%02x = ", cia1_tac, cia1_tal,
cia1[CIA_CRA], cia1[CIA_CRA]);
fprintf(fp, "%s%s%s%s%s]\n",
(cia1[CIA_CRA] & 1) ? "running " : "stopped ",
(cia1[CIA_CRA] & 8) ? "one-shot " : "continous ",
(cia1[CIA_CRA] & 16) ? "force-load " : "",
(cia1[CIA_CRA] & 32) ? "cnt " : "phi2 ",
(cia1[CIA_CRA] & 64) ? "sr_out " : "sr_in ");
fprintf(fp, "TB %u %u %d [$%02x = ", cia1_tbc, cia1_tbl,
cia1[CIA_CRB], cia1[CIA_CRB]);
fprintf(fp, "%s%s%s%s]\n",
(cia1[CIA_CRB] & 1) ? "running " : "stopped ",
(cia1[CIA_CRB] & 8) ? "one-shot " : "continous ",
(cia1[CIA_CRB] & 16) ? "force-load " : "",
(cia1[CIA_CRB] & 32)
? ((cia1[CIA_CRB] & 64) ? "timerA+cnt" : "cnt ")
: ((cia1[CIA_CRB] & 64) ? "timerA" : "phi2 ")
);
fprintf(fp, "ICR %u %u %u", cia1int, cia1ier,
maincpu_int_status.pending_int[I_CIA1FL]);
fprintf(fp, " [fl= %s%s%s%s%s] ",
(cia1int & 1) ? "TA " : "",
(cia1int & 2) ? "TB " : "",
(cia1int & 4) ? "Alarm " : "",
(cia1int & 8) ? "SDR " : "",
(cia1int & 16) ? "Flag " : "");
fprintf(fp, "[mask= %s%s%s%s%s]\n",
(cia1ier & 1) ? "TA " : "",
(cia1ier & 2) ? "TB " : "",
(cia1ier & 4) ? "Alarm " : "",
(cia1ier & 8) ? "SDR " : "",
(cia1ier & 16) ? "Flag " : "");
fprintf(fp, "SR %d %d\n", cia1[CIA_SDR], cia1sr_bits);
fprintf(fp, "TOD %d %d %d %d\n", cia1[CIA_TOD_HR],
cia1[CIA_TOD_MIN], cia1[CIA_TOD_SEC], cia1[CIA_TOD_SEC]);
}
void cia1_undump_line(char *s)
{
unsigned int d1, d2, d3, d4;
if (s == strstr(s, "PA")) {
sscanf(s + 2, "%u %u", &d1, &d2);
cia1[CIA_PRA] = d1;
cia1[CIA_DDRA] = d2;
store_cia1(CIA_PRA, cia1[CIA_PRA]);
} else if (s == strstr(s, "PB")) {
sscanf(s + 2, "%u %u", &d1, &d2);
cia1[CIA_PRB] = d1;
cia1[CIA_DDRB] = d2;
store_cia1(CIA_PRB, cia1[CIA_PRB]);
} else if (s == strstr(s, "TA")) {
sscanf(s + 2, "%u %u %u", &cia1_tac, &cia1_tal, &d1);
cia1[CIA_CRA] = d1;
if ((cia1[CIA_CRA] & 0x21) == 0x01) {
cia1_tau = clk + cia1_tac;
cia1_tas = CIAT_RUNNING;
my_set_tai_clk(cia1_tau + 1);
} else {
cia1_tau = 0;
cia1_tas = CIAT_STOPPED;
}
} else if (s == strstr(s, "TB")) {
sscanf(s + 2, "%u %u %u", &cia1_tbc, &cia1_tbl, &d1);
cia1[CIA_CRB] = d1;
if ((cia1[CIA_CRB] & 0x61) == 0x01) {
cia1_tbu = clk + cia1_tbc;
cia1_tbs = CIAT_RUNNING;
my_set_tbi_clk(cia1_tbu + 1);
} else {
cia1_tbu = 0;
if ((cia1[CIA_CRB] & 0x61) == 0x41) {
cia1_tbs = CIAT_COUNTTA;
} else {
cia1_tbs = CIAT_STOPPED;
}
}
} else if (s == strstr(s, "ICR")) {
sscanf(s + 3, "%d %d", &d1, &d2);
cia1int = d1;
cia1ier = d2;
if (cia1int & cia1ier & 0x7f) {
my_set_int(I_CIA1FL, IK_IRQ, rclk);
} else {
my_set_int(I_CIA1FL, 0, rclk);
}
} else if (s == strstr(s, "SR")) {
sscanf(s + 2, "%d %d", &d1, &cia1sr_bits);
cia1[CIA_SDR] = d1;
} else if (s == strstr(s, "TOD")) {
sscanf(s + 3, "%u %u %u %u", &d1, &d2, &d3, &d4);
cia1[CIA_TOD_HR] = d1;
cia1[CIA_TOD_MIN] = d2;
cia1[CIA_TOD_SEC] = d3;
cia1[CIA_TOD_TEN] = d4;
} else {
printf("unknown dump format line for CIA1: \n%s\n", s);
}
}
#endif
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.