cog/Frameworks/libsidplay/sidplay-residfp-code/.svn/pristine/1a/1a7956f46b32b08b8ca5e953474...

337 lines
6.9 KiB
Plaintext
Raw Normal View History

/*
* This file is part of libsidplayfp, a SID player engine.
*
* Copyright 2011-2015 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
{
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.handleSerialPort();
}
void TimerB::underFlow()
{
parent.underflowB();
}
void InterruptSource6526A::trigger(uint8_t interruptMask)
{
InterruptSource::trigger(interruptMask);
if (interruptMasked() && interruptTriggered())
{
triggerInterrupt();
parent.interrupt(true);
}
}
uint8_t InterruptSource6526A::clear()
{
if (!interruptTriggered())
{
parent.interrupt(false);
}
return InterruptSource::clear();
}
void InterruptSource6526::trigger(uint8_t interruptMask)
{
InterruptSource::trigger(interruptMask);
if (interruptMasked() && interruptTriggered())
{
schedule();
}
}
uint8_t InterruptSource6526::clear()
{
if (scheduled)
{
eventScheduler.cancel(*this);
scheduled = false;
}
if (!interruptTriggered())
{
parent.interrupt(false);
}
return InterruptSource::clear();
}
void InterruptSource6526::event()
{
triggerInterrupt();
parent.interrupt(true);
scheduled = false;
}
void InterruptSource6526::reset()
{
InterruptSource::reset();
scheduled = false;
}
const char *MOS6526::credits()
{
return
"MOS6526/6526A (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-2015 Leandro Nini\n";
}
MOS6526::MOS6526(EventScheduler &scheduler) :
eventScheduler(scheduler),
pra(regs[PRA]),
prb(regs[PRB]),
ddra(regs[DDRA]),
ddrb(regs[DDRB]),
timerA(scheduler, *this),
timerB(scheduler, *this),
interruptSource(new InterruptSource6526(scheduler, *this)),
tod(scheduler, *this, regs),
serialPort(interruptSource.get()),
bTickEvent("CIA B counts A", *this, &MOS6526::bTick)
{
reset();
}
void MOS6526::handleSerialPort()
{
if (regs[CRA] & 0x40)
{
serialPort.handle(regs[SDR]);
}
}
void MOS6526::reset()
{
memset(regs, 0, sizeof(regs));
serialPort.reset();
// Reset timers
timerA.reset();
timerB.reset();
// Reset interruptSource
interruptSource->reset();
// Reset tod
tod.reset();
triggerScheduled = false;
eventScheduler.cancel(bTickEvent);
}
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:
return interruptSource->clear();
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)
serialPort.setBuffered();
break;
case ICR:
interruptSource->set(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::bTick()
{
timerB.cascade();
}
void MOS6526::underflowA()
{
interruptSource->trigger(InterruptSource::INTERRUPT_UNDERFLOW_A);
if ((regs[CRB] & 0x41) == 0x41)
{
if (timerB.started())
{
eventScheduler.schedule(bTickEvent, 0, EVENT_CLOCK_PHI2);
}
}
}
void MOS6526::underflowB()
{
interruptSource->trigger(InterruptSource::INTERRUPT_UNDERFLOW_B);
}
void MOS6526::todInterrupt()
{
interruptSource->trigger(InterruptSource::INTERRUPT_ALARM);
}
}