/* * This file is part of libsidplayfp, a SID player engine. * * Copyright 2011-2015 Leandro Nini * 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 #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); } }