mirror of
https://github.com/AppleWin/AppleWin.git
synced 2024-11-15 09:05:39 +00:00
273d07d83f
Differences compared to Mockingboard: . TIMER1 underflows to $FFFE before it's reloaded with latch. . TIMER1's LATCH=$0000 gets interpreted as $FFFF. . Partial TIMER2 support: IFR.T2 gets set on underflow. But T2 reads as 0x00FF. . AY register reads are unsupported (always read as 0). Config->Sound: show 'MEGA Audio' in drop-down menus only if it was specified on the cmd line.
729 lines
22 KiB
C++
729 lines
22 KiB
C++
/*
|
|
AppleWin : An Apple //e emulator for Windows
|
|
|
|
Copyright (C) 1994-1996, Michael O'Brien
|
|
Copyright (C) 1999-2001, Oliver Schmidt
|
|
Copyright (C) 2002-2005, Tom Charlesworth
|
|
Copyright (C) 2006-2021, Tom Charlesworth, Michael Pohoreski, Nick Westgate
|
|
|
|
AppleWin 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.
|
|
|
|
AppleWin 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 AppleWin; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* Description: Rockwell 6522 VIA emulation
|
|
*
|
|
* Author: Various
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
|
|
#include "6522.h"
|
|
#include "CardManager.h"
|
|
#include "Core.h"
|
|
#include "CPU.h"
|
|
#include "Memory.h"
|
|
#include "SynchronousEventManager.h"
|
|
#include "YamlHelper.h"
|
|
|
|
void SY6522::Reset(const bool powerCycle)
|
|
{
|
|
if (powerCycle)
|
|
{
|
|
memset(&m_regs, 0, sizeof(Regs));
|
|
m_regs.TIMER1_LATCH.w = 0xffff; // Some random value (but pick $ffff so it's deterministic)
|
|
// . NB. if it's too small (< ~$0007) then MB detection routines will fail!
|
|
}
|
|
|
|
CpuCreateCriticalSection(); // Reset() called by SY6522 global ctor, so explicitly create CPU's CriticalSection
|
|
Write(rDDRB, 0x00); // DDRB = 0x00: all pins are inputs
|
|
Write(rDDRA, 0x00); // DDRA = 0x00: all pins are inputs
|
|
Write(rACR, 0x00); // ACR = 0x00: T1 one-shot mode
|
|
Write(rPCR, 0x00); // PCR = 0x00: CB2/CA2 Control = Input (negative active edge)
|
|
Write(rIFR, 0x7f); // IFR = 0x7F: de-assert any IRQs
|
|
Write(rIER, 0x7f); // IER = 0x7F: disable all IRQs
|
|
|
|
StopTimer1();
|
|
StopTimer2();
|
|
|
|
m_timer1IrqDelay = m_timer2IrqDelay = 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
void SY6522::StartTimer1(void)
|
|
{
|
|
m_timer1Active = true;
|
|
}
|
|
|
|
// The assumption was that timer1 was only active if IER.TIMER1=1
|
|
// . Not true, since IFR can be polled (with IER.TIMER1=0)
|
|
void SY6522::StartTimer1_LoadStateV1(void)
|
|
{
|
|
if ((m_regs.IER & IxR_TIMER1) == 0x00)
|
|
return;
|
|
|
|
m_timer1Active = true;
|
|
}
|
|
|
|
void SY6522::StopTimer1(void)
|
|
{
|
|
m_timer1Active = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SY6522::StartTimer2(void)
|
|
{
|
|
m_timer2Active = true;
|
|
|
|
}
|
|
|
|
void SY6522::StopTimer2(void)
|
|
{
|
|
m_timer2Active = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Insert a new synchronous event whenever the 6522 timer's counter is written.
|
|
// . NB. it doesn't matter if the timer's interrupt enable (IER) is set or not
|
|
// - the state of IER is only important when the counter underflows - see: MB_SyncEventCallback()
|
|
USHORT SY6522::SetTimerSyncEvent(BYTE reg, USHORT timerLatch)
|
|
{
|
|
_ASSERT(reg == rT1CH || reg == rT2CH);
|
|
SyncEvent* syncEvent = reg == rT1CH ? m_syncEvent[0] : m_syncEvent[1];
|
|
|
|
// NB. This TIMER adjustment value gets subtracted when this current opcode completes, so no need to persist to save-state
|
|
const UINT opcodeCycleAdjust = GetOpcodeCyclesForWrite(reg);
|
|
|
|
if (syncEvent->m_active)
|
|
g_SynchronousEventMgr.Remove(syncEvent->m_id);
|
|
|
|
if (m_isMegaAudio)
|
|
{
|
|
if (reg == rT1CH && timerLatch == 0x0000)
|
|
timerLatch = 0xFFFF; // MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?)
|
|
syncEvent->SetCycles(timerLatch + kExtraMegaAudioTimerCycles + opcodeCycleAdjust); // MegaAudio asserts IRQ 1 cycle late!
|
|
}
|
|
else
|
|
{
|
|
syncEvent->SetCycles(timerLatch + kExtraTimerCycles + opcodeCycleAdjust);
|
|
}
|
|
g_SynchronousEventMgr.Insert(syncEvent);
|
|
|
|
// It doesn't matter if this overflows (ie. >0xFFFF), since on completion of current opcode it'll be corrected
|
|
return (USHORT)(timerLatch + opcodeCycleAdjust);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SY6522::UpdatePortAForHiZ(void)
|
|
{
|
|
BYTE ora = GetReg(SY6522::rORA);
|
|
ora |= GetReg(SY6522::rDDRA) ^ 0xff; // for any DDRA bits set as input (logical 0), then set them in ORA
|
|
SetRegORA(ora); // Empirically Phasor's 6522-AY8913 bus floats high (or pull-up?) if no AY chip-selected (so DDRA=0x00 will read 0xFF as input)
|
|
}
|
|
|
|
void SY6522::UpdateIFR(BYTE clr_ifr, BYTE set_ifr /*= 0*/)
|
|
{
|
|
m_regs.IFR &= ~clr_ifr;
|
|
m_regs.IFR |= set_ifr;
|
|
|
|
if (m_regs.IFR & m_regs.IER & 0x7F)
|
|
m_regs.IFR |= IFR_IRQ;
|
|
else
|
|
m_regs.IFR &= ~IFR_IRQ;
|
|
|
|
if (GetCardMgr().GetObj(m_slot)) // If called from MockingboardCard ctor, then CardManager::m_slot[slot] == NULL
|
|
GetCardMgr().GetMockingboardCardMgr().UpdateIRQ();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SY6522::Write(BYTE nReg, BYTE nValue)
|
|
{
|
|
switch (nReg)
|
|
{
|
|
case 0x00: // ORB
|
|
m_regs.ORB = nValue & m_regs.DDRB;
|
|
break;
|
|
case 0x01: // ORA
|
|
m_regs.ORA = nValue & m_regs.DDRA;
|
|
break;
|
|
case 0x02: // DDRB
|
|
m_regs.DDRB = nValue;
|
|
break;
|
|
case 0x03: // DDRA
|
|
m_regs.DDRA = nValue;
|
|
break;
|
|
case 0x04: // TIMER1L_COUNTER
|
|
case 0x06: // TIMER1L_LATCH
|
|
m_regs.TIMER1_LATCH.l = nValue;
|
|
break;
|
|
case 0x05: // TIMER1H_COUNTER
|
|
{
|
|
UpdateIFR(IxR_TIMER1); // Clear Timer1 Interrupt Flag
|
|
m_regs.TIMER1_LATCH.h = nValue;
|
|
m_regs.TIMER1_COUNTER.w = SetTimerSyncEvent(nReg, m_regs.TIMER1_LATCH.w);
|
|
StartTimer1();
|
|
}
|
|
break;
|
|
case 0x07: // TIMER1H_LATCH
|
|
UpdateIFR(IxR_TIMER1); // Clear Timer1 Interrupt Flag
|
|
m_regs.TIMER1_LATCH.h = nValue;
|
|
break;
|
|
case 0x08: // TIMER2L
|
|
m_regs.TIMER2_LATCH.l = nValue;
|
|
break;
|
|
case 0x09: // TIMER2H
|
|
{
|
|
UpdateIFR(IxR_TIMER2); // Clear Timer2 Interrupt Flag
|
|
m_regs.TIMER2_LATCH.h = nValue; // NB. Real 6522 doesn't have TIMER2_LATCH.h
|
|
m_regs.TIMER2_COUNTER.w = SetTimerSyncEvent(nReg, m_regs.TIMER2_LATCH.w);
|
|
StartTimer2();
|
|
}
|
|
break;
|
|
case 0x0a: // SERIAL_SHIFT
|
|
break;
|
|
case 0x0b: // ACR
|
|
m_regs.ACR = nValue;
|
|
break;
|
|
case 0x0c: // PCR - Used for Speech chip only
|
|
m_regs.PCR = nValue;
|
|
break;
|
|
case 0x0d: // IFR
|
|
// - Clear those bits which are set in the lower 7 bits.
|
|
// - Can't clear bit 7 directly.
|
|
UpdateIFR(nValue);
|
|
break;
|
|
case 0x0e: // IER
|
|
if (!(nValue & 0x80))
|
|
{
|
|
// Clear those bits which are set in the lower 7 bits.
|
|
nValue ^= 0x7F;
|
|
m_regs.IER &= nValue;
|
|
}
|
|
else
|
|
{
|
|
// Set those bits which are set in the lower 7 bits.
|
|
nValue &= 0x7F;
|
|
m_regs.IER |= nValue;
|
|
}
|
|
if (m_syncEvent[0])
|
|
m_syncEvent[0]->m_canAssertIRQ = (m_regs.IER & IxR_TIMER1) ? true : false;
|
|
if (m_syncEvent[1])
|
|
m_syncEvent[1]->m_canAssertIRQ = (m_regs.IER & IxR_TIMER2) ? true : false;
|
|
UpdateIFR(0);
|
|
break;
|
|
case 0x0f: // ORA_NO_HS
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void SY6522::UpdateTimer1(USHORT clocks)
|
|
{
|
|
if (CheckTimerUnderflow(m_regs.TIMER1_COUNTER.w, m_timer1IrqDelay, clocks))
|
|
m_timer1IrqDelay = OnTimer1Underflow(m_regs.TIMER1_COUNTER.w);
|
|
}
|
|
|
|
void SY6522::UpdateTimer2(USHORT clocks)
|
|
{
|
|
// No TIMER2 latch so "after timing out, the counter will continue to decrement"
|
|
CheckTimerUnderflow(m_regs.TIMER2_COUNTER.w, m_timer2IrqDelay, clocks);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool SY6522::CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHORT clocks)
|
|
{
|
|
if (clocks == 0)
|
|
return false;
|
|
|
|
int oldTimer = counter;
|
|
int timer = counter;
|
|
timer -= clocks;
|
|
counter = (USHORT)timer;
|
|
|
|
bool timerIrq = false;
|
|
|
|
if (timerIrqDelay) // Deal with any previous counter underflow which didn't yet result in an IRQ
|
|
{
|
|
_ASSERT(timerIrqDelay == 1);
|
|
timerIrqDelay = 0;
|
|
timerIrq = true;
|
|
// if LATCH is very small then could underflow for every opcode...
|
|
}
|
|
|
|
if (oldTimer >= 0 && timer < 0) // Underflow occurs for 0x0000 -> 0xFFFF
|
|
{
|
|
if (m_isMegaAudio)
|
|
{
|
|
if (timer <= -3) // TIMER = 0xFFFD (or less)
|
|
timerIrq = true;
|
|
else // TIMER = 0xFFFF or 0xFFFE
|
|
timerIrqDelay = 1; // ...so 1 or 2 cycles until IRQ
|
|
}
|
|
else
|
|
{
|
|
if (timer <= -2) // TIMER = 0xFFFE (or less)
|
|
timerIrq = true;
|
|
else // TIMER = 0xFFFF
|
|
timerIrqDelay = 1; // ...so 1 cycle until IRQ
|
|
}
|
|
|
|
}
|
|
|
|
return timerIrq;
|
|
}
|
|
|
|
int SY6522::OnTimer1Underflow(USHORT& counter)
|
|
{
|
|
int timer = (int)(short)(counter);
|
|
if (m_isMegaAudio)
|
|
{
|
|
const UINT timerLatch = m_regs.TIMER1_LATCH.w ? m_regs.TIMER1_LATCH.w : 0xFFFF; // MegaAudio && T1.LATCH=0: use 0xFFFF (or maybe 0x10000?)
|
|
while (timer < -2)
|
|
timer += (timerLatch + kExtraMegaAudioTimerCycles); // MegaAudio asserts IRQ 1 cycle late!
|
|
}
|
|
else
|
|
{
|
|
while (timer < -1)
|
|
timer += (m_regs.TIMER1_LATCH.w + kExtraTimerCycles); // GH#651: account for underflowed cycles / GH#652: account for extra 2 cycles
|
|
}
|
|
counter = (USHORT)timer;
|
|
return (timer < 0) ? 1 : 0; // timer1IrqDelay
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
USHORT SY6522::GetTimer1Counter(BYTE reg)
|
|
{
|
|
USHORT counter = m_regs.TIMER1_COUNTER.w; // NB. don't update the real T1C
|
|
int timerIrqDelay = m_timer1IrqDelay; // NB. don't update the real timer1IrqDelay
|
|
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg) - 1; // to compensate for the 4/5/6 cycle read opcode
|
|
if (CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust))
|
|
OnTimer1Underflow(counter);
|
|
return counter;
|
|
}
|
|
|
|
USHORT SY6522::GetTimer2Counter(BYTE reg)
|
|
{
|
|
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg) - 1; // to compensate for the 4/5/6 cycle read opcode
|
|
return m_regs.TIMER2_COUNTER.w - opcodeCycleAdjust;
|
|
}
|
|
|
|
bool SY6522::IsTimer1Underflowed(BYTE reg)
|
|
{
|
|
USHORT counter = m_regs.TIMER1_COUNTER.w; // NB. don't update the real T1C
|
|
int timerIrqDelay = m_timer1IrqDelay; // NB. don't update the real timer1IrqDelay
|
|
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg); // to compensate for the 4/5/6 cycle read opcode
|
|
return CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust);
|
|
}
|
|
|
|
bool SY6522::IsTimer2Underflowed(BYTE reg)
|
|
{
|
|
USHORT counter = m_regs.TIMER2_COUNTER.w; // NB. don't update the real T2C
|
|
int timerIrqDelay = m_timer2IrqDelay; // NB. don't update the real timer2IrqDelay
|
|
const UINT opcodeCycleAdjust = GetOpcodeCyclesForRead(reg); // to compensate for the 4/5/6 cycle read opcode
|
|
return CheckTimerUnderflow(counter, timerIrqDelay, opcodeCycleAdjust);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
BYTE SY6522::Read(BYTE nReg)
|
|
{
|
|
BYTE nValue = 0x00;
|
|
|
|
switch (nReg)
|
|
{
|
|
case 0x00: // ORB
|
|
nValue = m_regs.ORB;
|
|
break;
|
|
case 0x01: // ORA
|
|
nValue = m_regs.ORA;
|
|
break;
|
|
case 0x02: // DDRB
|
|
nValue = m_regs.DDRB;
|
|
break;
|
|
case 0x03: // DDRA
|
|
nValue = m_regs.DDRA;
|
|
break;
|
|
case 0x04: // TIMER1L_COUNTER
|
|
// NB. GH#701 (T1C:=0xFFFF, LDA T1C_L[4cy], A==0xFC)
|
|
nValue = GetTimer1Counter(nReg) & 0xff;
|
|
UpdateIFR(IxR_TIMER1);
|
|
break;
|
|
case 0x05: // TIMER1H_COUNTER
|
|
nValue = GetTimer1Counter(nReg) >> 8;
|
|
break;
|
|
case 0x06: // TIMER1L_LATCH
|
|
nValue = m_regs.TIMER1_LATCH.l;
|
|
break;
|
|
case 0x07: // TIMER1H_LATCH
|
|
nValue = m_regs.TIMER1_LATCH.h;
|
|
break;
|
|
case 0x08: // TIMER2L
|
|
nValue = GetTimer2Counter(nReg) & 0xff;
|
|
UpdateIFR(IxR_TIMER2);
|
|
if (m_isMegaAudio) nValue = 0xFF; // MegaAudio: Timer2 just reads as $00FF
|
|
break;
|
|
case 0x09: // TIMER2H
|
|
nValue = GetTimer2Counter(nReg) >> 8;
|
|
if (m_isMegaAudio) nValue = 0x00; // MegaAudio: Timer2 just reads as $00FF
|
|
break;
|
|
case 0x0a: // SERIAL_SHIFT
|
|
break;
|
|
case 0x0b: // ACR
|
|
nValue = m_regs.ACR;
|
|
break;
|
|
case 0x0c: // PCR
|
|
nValue = m_regs.PCR;
|
|
break;
|
|
case 0x0d: // IFR
|
|
nValue = m_regs.IFR;
|
|
if (m_timer1Active && IsTimer1Underflowed(nReg))
|
|
nValue |= IxR_TIMER1;
|
|
if (m_timer2Active && IsTimer2Underflowed(nReg))
|
|
nValue |= IxR_TIMER2;
|
|
break;
|
|
case 0x0e: // IER
|
|
nValue = 0x80 | m_regs.IER; // GH#567
|
|
if (m_isMegaAudio)
|
|
nValue &= 0x7F;
|
|
break;
|
|
case 0x0f: // ORA_NO_HS
|
|
nValue = m_regs.ORA;
|
|
break;
|
|
}
|
|
|
|
return nValue;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
|
|
UINT SY6522::GetOpcodeCyclesForRead(BYTE reg)
|
|
{
|
|
UINT opcodeCycles = 0;
|
|
BYTE opcode = 0;
|
|
bool abs16 = false;
|
|
bool abs16x = false;
|
|
bool abs16y = false;
|
|
bool indx = false;
|
|
bool indy = false;
|
|
|
|
const BYTE opcodeMinus3 = mem[(::regs.pc - 3) & 0xffff];
|
|
const BYTE opcodeMinus2 = mem[(::regs.pc - 2) & 0xffff];
|
|
|
|
if (((opcodeMinus2 & 0x0f) == 0x01) && ((opcodeMinus2 & 0x10) == 0x00)) // ora (zp,x), and (zp,x), ..., sbc (zp,x)
|
|
{
|
|
// NB. this is for read, so don't need to exclude 0x81 / sta (zp,x)
|
|
opcodeCycles = 6;
|
|
opcode = opcodeMinus2;
|
|
indx = true;
|
|
}
|
|
else if (((opcodeMinus2 & 0x0f) == 0x01) && ((opcodeMinus2 & 0x10) == 0x10)) // ora (zp),y, and (zp),y, ..., sbc (zp),y
|
|
{
|
|
// NB. this is for read, so don't need to exclude 0x91 / sta (zp),y
|
|
opcodeCycles = 5;
|
|
opcode = opcodeMinus2;
|
|
indy = true;
|
|
}
|
|
else if (((opcodeMinus2 & 0x0f) == 0x02) && ((opcodeMinus2 & 0x10) == 0x10) && GetMainCpu() == CPU_65C02) // ora (zp), and (zp), ..., sbc (zp) : 65C02-only
|
|
{
|
|
// NB. this is for read, so don't need to exclude 0x92 / sta (zp)
|
|
opcodeCycles = 5;
|
|
opcode = opcodeMinus2;
|
|
}
|
|
else
|
|
{
|
|
if ((((opcodeMinus3 & 0x0f) == 0x0D) && ((opcodeMinus3 & 0x10) == 0x00)) || // ora abs16, and abs16, ..., sbc abs16
|
|
(opcodeMinus3 == 0x2C) || // bit abs16
|
|
(opcodeMinus3 == 0xAC) || // ldy abs16
|
|
(opcodeMinus3 == 0xAE) || // ldx abs16
|
|
(opcodeMinus3 == 0xCC) || // cpy abs16
|
|
(opcodeMinus3 == 0xEC)) // cpx abs16
|
|
{
|
|
}
|
|
else if ((opcodeMinus3 == 0xBC) || // ldy abs16,x
|
|
((opcodeMinus3 == 0x3C) && GetMainCpu() == CPU_65C02)) // bit abs16,x : 65C02-only
|
|
{
|
|
abs16x = true;
|
|
}
|
|
else if ((opcodeMinus3 == 0xBE)) // ldx abs16,y
|
|
{
|
|
abs16y = true;
|
|
}
|
|
else if ((opcodeMinus3 & 0x10) == 0x10)
|
|
{
|
|
if ((opcodeMinus3 & 0x0f) == 0x0D) // ora abs16,x, and abs16,x, ..., sbc abs16,x
|
|
abs16x = true;
|
|
else if ((opcodeMinus3 & 0x0f) == 0x09) // ora abs16,y, and abs16,y, ..., sbc abs16,y
|
|
abs16y = true;
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(0);
|
|
opcodeCycles = 0;
|
|
return 0;
|
|
}
|
|
|
|
opcodeCycles = 4;
|
|
opcode = opcodeMinus3;
|
|
abs16 = true;
|
|
}
|
|
|
|
//
|
|
|
|
WORD addr16 = 0;
|
|
|
|
if (!abs16)
|
|
{
|
|
BYTE zp = mem[(::regs.pc - 1) & 0xffff];
|
|
if (indx) zp += ::regs.x;
|
|
addr16 = (mem[zp] | (mem[(zp + 1) & 0xff] << 8));
|
|
if (indy) addr16 += ::regs.y;
|
|
}
|
|
else
|
|
{
|
|
addr16 = mem[(::regs.pc - 2) & 0xffff] | (mem[(::regs.pc - 1) & 0xffff] << 8);
|
|
if (abs16y) addr16 += ::regs.y;
|
|
if (abs16x) addr16 += ::regs.x;
|
|
}
|
|
|
|
// Check we've reverse looked-up the 6502 opcode correctly
|
|
if ((addr16 & 0xF80F) != (0xC000 + reg))
|
|
{
|
|
_ASSERT(0);
|
|
return 0;
|
|
}
|
|
|
|
return opcodeCycles;
|
|
}
|
|
|
|
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
|
|
UINT SY6522::GetOpcodeCyclesForWrite(BYTE reg)
|
|
{
|
|
UINT opcodeCycles = 0;
|
|
BYTE opcode = 0;
|
|
bool abs16 = false;
|
|
|
|
const BYTE opcodeMinus3 = mem[(::regs.pc - 3) & 0xffff];
|
|
const BYTE opcodeMinus2 = mem[(::regs.pc - 2) & 0xffff];
|
|
|
|
if ((opcodeMinus3 == 0x8C) || // sty abs16
|
|
(opcodeMinus3 == 0x8D) || // sta abs16
|
|
(opcodeMinus3 == 0x8E)) // stx abs16
|
|
{ // Eg. FT demos: CHIP, MADEF, MAD2
|
|
opcodeCycles = 4;
|
|
opcode = opcodeMinus3;
|
|
abs16 = true;
|
|
}
|
|
else if ((opcodeMinus3 == 0x99) || // sta abs16,y
|
|
(opcodeMinus3 == 0x9D)) // sta abs16,x
|
|
{ // Eg. Paleotronic microTracker demo
|
|
opcodeCycles = 5;
|
|
opcode = opcodeMinus3;
|
|
abs16 = true;
|
|
}
|
|
else if (opcodeMinus2 == 0x81) // sta (zp,x)
|
|
{
|
|
opcodeCycles = 6;
|
|
opcode = opcodeMinus2;
|
|
}
|
|
else if (opcodeMinus2 == 0x91) // sta (zp),y
|
|
{ // Eg. FT demos: OMT, PLS
|
|
opcodeCycles = 6;
|
|
opcode = opcodeMinus2;
|
|
}
|
|
else if (opcodeMinus2 == 0x92 && GetMainCpu() == CPU_65C02) // sta (zp) : 65C02-only
|
|
{
|
|
opcodeCycles = 5;
|
|
opcode = opcodeMinus2;
|
|
}
|
|
else if (opcodeMinus3 == 0x9C && GetMainCpu() == CPU_65C02) // stz abs16 : 65C02-only
|
|
{
|
|
opcodeCycles = 4;
|
|
opcode = opcodeMinus3;
|
|
abs16 = true;
|
|
}
|
|
else if (opcodeMinus3 == 0x9E && GetMainCpu() == CPU_65C02) // stz abs16,x : 65C02-only
|
|
{
|
|
opcodeCycles = 5;
|
|
opcode = opcodeMinus3;
|
|
abs16 = true;
|
|
}
|
|
else
|
|
{
|
|
_ASSERT(0);
|
|
opcodeCycles = 0;
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
|
|
WORD addr16 = 0;
|
|
|
|
if (!abs16)
|
|
{
|
|
BYTE zp = mem[(::regs.pc - 1) & 0xffff];
|
|
if (opcode == 0x81) zp += ::regs.x;
|
|
addr16 = (mem[zp] | (mem[(zp + 1) & 0xff] << 8));
|
|
if (opcode == 0x91) addr16 += ::regs.y;
|
|
}
|
|
else
|
|
{
|
|
addr16 = mem[(::regs.pc - 2) & 0xffff] | (mem[(::regs.pc - 1) & 0xffff] << 8);
|
|
if (opcode == 0x99) addr16 += ::regs.y;
|
|
if (opcode == 0x9D || opcode == 0x9E) addr16 += ::regs.x;
|
|
}
|
|
|
|
// Check we've reverse looked-up the 6502 opcode correctly
|
|
if ((addr16 & 0xF80F) != (0xC000 + reg))
|
|
{
|
|
_ASSERT(0);
|
|
return 0;
|
|
}
|
|
|
|
return opcodeCycles;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
#define SS_YAML_KEY_SY6522 "SY6522"
|
|
// NB. No version - this is determined by the parent "Mockingboard C" or "Phasor" unit
|
|
|
|
#define SS_YAML_KEY_SY6522_REG_ORB "ORB"
|
|
#define SS_YAML_KEY_SY6522_REG_ORA "ORA"
|
|
#define SS_YAML_KEY_SY6522_REG_DDRB "DDRB"
|
|
#define SS_YAML_KEY_SY6522_REG_DDRA "DDRA"
|
|
#define SS_YAML_KEY_SY6522_REG_T1_COUNTER "Timer1 Counter"
|
|
#define SS_YAML_KEY_SY6522_REG_T1_LATCH "Timer1 Latch"
|
|
#define SS_YAML_KEY_SY6522_REG_T2_COUNTER "Timer2 Counter"
|
|
#define SS_YAML_KEY_SY6522_REG_T2_LATCH "Timer2 Latch"
|
|
#define SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT "Serial Shift"
|
|
#define SS_YAML_KEY_SY6522_REG_ACR "ACR"
|
|
#define SS_YAML_KEY_SY6522_REG_PCR "PCR"
|
|
#define SS_YAML_KEY_SY6522_REG_IFR "IFR"
|
|
#define SS_YAML_KEY_SY6522_REG_IER "IER"
|
|
|
|
#define SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY "Timer1 IRQ Delay"
|
|
#define SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY "Timer2 IRQ Delay"
|
|
#define SS_YAML_KEY_SY6522_TIMER1_ACTIVE "Timer1 Active"
|
|
#define SS_YAML_KEY_SY6522_TIMER2_ACTIVE "Timer2 Active"
|
|
|
|
void SY6522::SaveSnapshot(YamlSaveHelper& yamlSaveHelper)
|
|
{
|
|
YamlSaveHelper::Label label(yamlSaveHelper, "%s:\n", SS_YAML_KEY_SY6522);
|
|
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ORB, m_regs.ORB);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ORA, m_regs.ORA);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_DDRB, m_regs.DDRB);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_DDRA, m_regs.DDRA);
|
|
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_COUNTER, m_regs.TIMER1_COUNTER.w);
|
|
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T1_LATCH, m_regs.TIMER1_LATCH.w);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY, m_timer1IrqDelay); // v4
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_SY6522_TIMER1_ACTIVE, m_timer1Active); // v8
|
|
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_COUNTER, m_regs.TIMER2_COUNTER.w);
|
|
yamlSaveHelper.SaveHexUint16(SS_YAML_KEY_SY6522_REG_T2_LATCH, m_regs.TIMER2_LATCH.w);
|
|
yamlSaveHelper.SaveUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY, m_timer2IrqDelay); // v4
|
|
yamlSaveHelper.SaveBool(SS_YAML_KEY_SY6522_TIMER2_ACTIVE, m_timer2Active); // v8
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT, m_regs.SERIAL_SHIFT);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_ACR, m_regs.ACR);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_PCR, m_regs.PCR);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_IFR, m_regs.IFR);
|
|
yamlSaveHelper.SaveHexUint8(SS_YAML_KEY_SY6522_REG_IER, m_regs.IER);
|
|
// NB. No need to write ORA_NO_HS, since same data as ORA, just without handshake
|
|
}
|
|
|
|
void SY6522::LoadSnapshot(YamlLoadHelper& yamlLoadHelper, UINT version)
|
|
{
|
|
if (!yamlLoadHelper.GetSubMap(SS_YAML_KEY_SY6522))
|
|
throw std::runtime_error("Card: Expected key: " SS_YAML_KEY_SY6522);
|
|
|
|
m_regs.ORB = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORB);
|
|
m_regs.ORA = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ORA);
|
|
m_regs.DDRB = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_DDRB);
|
|
m_regs.DDRA = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_DDRA);
|
|
m_regs.TIMER1_COUNTER.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T1_COUNTER);
|
|
m_regs.TIMER1_LATCH.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T1_LATCH);
|
|
m_regs.TIMER2_COUNTER.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T2_COUNTER);
|
|
m_regs.TIMER2_LATCH.w = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_T2_LATCH);
|
|
m_regs.SERIAL_SHIFT = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_SERIAL_SHIFT);
|
|
m_regs.ACR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_ACR);
|
|
m_regs.PCR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_PCR);
|
|
m_regs.IFR = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_IFR);
|
|
m_regs.IER = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_REG_IER);
|
|
m_regs.ORA_NO_HS = 0; // Not saved
|
|
|
|
m_timer1IrqDelay = m_timer2IrqDelay = 0;
|
|
|
|
if (version >= 4)
|
|
{
|
|
m_timer1IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER1_IRQ_DELAY);
|
|
m_timer2IrqDelay = yamlLoadHelper.LoadUint(SS_YAML_KEY_SY6522_TIMER2_IRQ_DELAY);
|
|
}
|
|
|
|
if (version < 7)
|
|
{
|
|
// Assume t1_latch was never written to (so had the old default of 0x0000) - this now results in failure of Mockingboard detection!
|
|
if (m_regs.TIMER1_LATCH.w == 0x0000)
|
|
m_regs.TIMER1_LATCH.w = 0xFFFF; // Allow Mockingboard detection to succeed
|
|
}
|
|
|
|
if (version >= 8)
|
|
{
|
|
bool timer1Active = yamlLoadHelper.LoadBool(SS_YAML_KEY_SY6522_TIMER1_ACTIVE);
|
|
bool timer2Active = yamlLoadHelper.LoadBool(SS_YAML_KEY_SY6522_TIMER2_ACTIVE);
|
|
SetTimersActiveFromSnapshot(timer1Active, timer2Active, version);
|
|
}
|
|
|
|
yamlLoadHelper.PopMap();
|
|
}
|
|
|
|
void SY6522::SetTimersActiveFromSnapshot(bool timer1Active, bool timer2Active, UINT version)
|
|
{
|
|
m_timer1Active = timer1Active;
|
|
m_timer2Active = timer2Active;
|
|
|
|
//
|
|
|
|
if (version == 1)
|
|
{
|
|
StartTimer1_LoadStateV1(); // Attempt to start timer
|
|
}
|
|
else // version >= 2
|
|
{
|
|
if (IsTimer1Active())
|
|
StartTimer1(); // Attempt to start timer
|
|
}
|
|
|
|
if (IsTimer1Active())
|
|
{
|
|
SyncEvent* syncEvent = m_syncEvent[0];
|
|
syncEvent->SetCycles(GetRegT1C() + kExtraTimerCycles); // NB. use COUNTER, not LATCH
|
|
syncEvent->m_canAssertIRQ = (m_regs.IER & IxR_TIMER1) ? true : false;
|
|
g_SynchronousEventMgr.Insert(syncEvent);
|
|
}
|
|
if (IsTimer2Active())
|
|
{
|
|
SyncEvent* syncEvent = m_syncEvent[1];
|
|
syncEvent->SetCycles(GetRegT2C() + kExtraTimerCycles); // NB. use COUNTER, not LATCH
|
|
syncEvent->m_canAssertIRQ = (m_regs.IER & IxR_TIMER2) ? true : false;
|
|
g_SynchronousEventMgr.Insert(syncEvent);
|
|
}
|
|
}
|