cog/Frameworks/libsidplay/sidplay-residfp-code/.svn/pristine/3f/3f109bf00d12d3439a8efd5108e...

343 lines
7.2 KiB
Plaintext

/*
* This file is part of libsidplayfp, a SID player engine.
*
* Copyright 2011-2014 Leandro Nini <drfiemost@users.sourceforge.net>
* Copyright 2009-2014 VICE Project
* Copyright 2007-2010 Antti Lankila
* Copyright 2000 Simon White
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "mos6526.h"
#include <cstring>
#include "sidendian.h"
namespace libsidplayfp
{
enum
{
INTERRUPT_NONE = 0,
INTERRUPT_UNDERFLOW_A = 1 << 0,
INTERRUPT_UNDERFLOW_B = 1 << 1,
INTERRUPT_ALARM = 1 << 2,
INTERRUPT_SP = 1 << 3,
INTERRUPT_FLAG = 1 << 4,
INTERRUPT_REQUEST = 1 << 7
};
enum
{
PRA = 0,
PRB = 1,
DDRA = 2,
DDRB = 3,
TAL = 4,
TAH = 5,
TBL = 6,
TBH = 7,
TOD_TEN = 8,
TOD_SEC = 9,
TOD_MIN = 10,
TOD_HR = 11,
SDR = 12,
ICR = 13,
IDR = 13,
CRA = 14,
CRB = 15
};
void TimerA::underFlow()
{
parent->underflowA();
}
void TimerA::serialPort()
{
parent->serialPort();
}
void TimerB::underFlow()
{
parent->underflowB();
}
const char *MOS6526::credit =
{
"MOS6526 (CIA) Emulation:\n"
"\tCopyright (C) 2001-2004 Simon White\n"
"\tCopyright (C) 2007-2010 Antti S. Lankila\n"
"\tCopyright (C) 2009-2014 VICE Project\n"
"\tCopyright (C) 2011-2014 Leandro Nini\n"
};
MOS6526::MOS6526(EventContext *context) :
event_context(*context),
pra(regs[PRA]),
prb(regs[PRB]),
ddra(regs[DDRA]),
ddrb(regs[DDRB]),
timerA(context, this),
timerB(context, this),
tod(context, this, regs),
idr(0),
bTickEvent("CIA B counts A", *this, &MOS6526::bTick),
triggerEvent("Trigger Interrupt", *this, &MOS6526::trigger)
{
reset();
}
void MOS6526::serialPort()
{
if (regs[CRA] & 0x40)
{
if (sdr_count)
{
if (--sdr_count == 0)
{
trigger(INTERRUPT_SP);
}
}
if (sdr_count == 0 && sdr_buffered)
{
sdr_out = regs[SDR];
sdr_buffered = false;
sdr_count = 16;
// Output rate 8 bits at ta / 2
}
}
}
void MOS6526::clear()
{
if (idr & INTERRUPT_REQUEST)
interrupt(false);
idr = 0;
}
void MOS6526::reset()
{
sdr_out = 0;
sdr_count = 0;
sdr_buffered = false;
// Clear off any IRQs
clear();
icr = idr = 0;
memset(regs, 0, sizeof(regs));
// Reset timers
timerA.reset();
timerB.reset();
// Reset tod
tod.reset();
triggerScheduled = false;
event_context.cancel(bTickEvent);
event_context.cancel(triggerEvent);
}
uint8_t MOS6526::read(uint_least8_t addr)
{
addr &= 0x0f;
timerA.syncWithCpu();
timerA.wakeUpAfterSyncWithCpu();
timerB.syncWithCpu();
timerB.wakeUpAfterSyncWithCpu();
switch (addr)
{
case PRA: // Simulate a serial port
return (regs[PRA] | ~regs[DDRA]);
case PRB:{
uint8_t data = regs[PRB] | ~regs[DDRB];
// Timers can appear on the port
if (regs[CRA] & 0x02)
{
data &= 0xbf;
if (timerA.getPb(regs[CRA]))
data |= 0x40;
}
if (regs[CRB] & 0x02)
{
data &= 0x7f;
if (timerB.getPb(regs[CRB]))
data |= 0x80;
}
return data;}
case TAL:
return endian_16lo8(timerA.getTimer());
case TAH:
return endian_16hi8(timerA.getTimer());
case TBL:
return endian_16lo8(timerB.getTimer());
case TBH:
return endian_16hi8(timerB.getTimer());
case TOD_TEN:
case TOD_SEC:
case TOD_MIN:
case TOD_HR:
return tod.read(addr - TOD_TEN);
case IDR:{
if (triggerScheduled)
{
event_context.cancel(triggerEvent);
triggerScheduled = false;
}
// Clear IRQs, and return interrupt
// data register
const uint8_t ret = idr;
clear();
return ret;}
case CRA:
return (regs[CRA] & 0xee) | (timerA.getState() & 1);
case CRB:
return (regs[CRB] & 0xee) | (timerB.getState() & 1);
default:
return regs[addr];
}
}
void MOS6526::write(uint_least8_t addr, uint8_t data)
{
addr &= 0x0f;
timerA.syncWithCpu();
timerB.syncWithCpu();
const uint8_t oldData = regs[addr];
regs[addr] = data;
switch (addr)
{
case PRA:
case DDRA:
portA();
break;
case PRB:
case DDRB:
portB();
break;
case TAL:
timerA.latchLo(data);
break;
case TAH:
timerA.latchHi(data);
break;
case TBL:
timerB.latchLo(data);
break;
case TBH:
timerB.latchHi(data);
break;
case TOD_TEN:
case TOD_SEC:
case TOD_MIN:
case TOD_HR:
tod.write(addr - TOD_TEN, data);
break;
case SDR:
if (regs[CRA] & 0x40)
sdr_buffered = true;
break;
case ICR:
if (data & 0x80)
{
icr |= data & ~INTERRUPT_REQUEST;
trigger(INTERRUPT_NONE);
}
else
{
icr &= ~data;
}
break;
case CRA:{
if ((data & 1) && !(oldData & 1))
{
// Reset the underflow flipflop for the data port
timerA.setPbToggle(true);
}
timerA.setControlRegister(data);
break;}
case CRB:{
if ((data & 1) && !(oldData & 1))
{
// Reset the underflow flipflop for the data port
timerB.setPbToggle(true);
}
timerB.setControlRegister(data | (data & 0x40) >> 1);
break;}
}
timerA.wakeUpAfterSyncWithCpu();
timerB.wakeUpAfterSyncWithCpu();
}
void MOS6526::trigger()
{
idr |= INTERRUPT_REQUEST;
interrupt(true);
triggerScheduled = false;
}
void MOS6526::trigger(uint8_t interruptMask)
{
idr |= interruptMask;
if ((icr & idr) && !(idr & INTERRUPT_REQUEST))
{
if (!triggerScheduled)
{
// Schedules an IRQ asserting state transition for next cycle.
event_context.schedule(triggerEvent, 1, EVENT_CLOCK_PHI1);
triggerScheduled = true;
}
}
}
void MOS6526::bTick()
{
timerB.cascade();
}
void MOS6526::underflowA()
{
trigger(INTERRUPT_UNDERFLOW_A);
if ((regs[CRB] & 0x41) == 0x41)
{
if (timerB.started())
{
event_context.schedule(bTickEvent, 0, EVENT_CLOCK_PHI2);
}
}
}
void MOS6526::underflowB()
{
trigger(INTERRUPT_UNDERFLOW_B);
}
void MOS6526::todInterrupt()
{
trigger(INTERRUPT_ALARM);
}
}