AppleWin/source/6522.cpp
TomCh 910313f176
Fix edge-case for 6522 Timer write (#1333, PR #1334)
Refactor GetOpcodeCyclesForRead()/Write() to make then consistent & consolidate common code.
2024-10-15 21:26:38 +01:00

732 lines
23 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!
m_isBusDriven = false;
}
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::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
case 0x0f: // ORA_NO_HS
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;
}
}
//-----------------------------------------------------------------------------
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: // IRB
nValue = m_regs.ORB | (m_regs.DDRB ^ 0xff); // Input bits read back as 1's (GH#1260)
if (m_isMegaAudio) nValue = 0x00; // MegaAudio: IRB just reads as $00
break;
case 0x01: // IRA
case 0x0f: // IRA_NO_HS
nValue = m_regs.ORA | (m_isBusDriven ? 0x00 : (m_regs.DDRA ^ 0xff)); // NB. Inputs bits driven by AY8913 if in PSG READ mode
if (m_isMegaAudio) nValue = 0x00; // MegaAudio: IRA just reads as $00
break;
case 0x02: // DDRB
nValue = m_regs.DDRB;
if (m_bad6522)
nValue &= ~1; // DDRB.b0 = 0 (for testing mb-audit)
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;
}
return nValue;
}
//-----------------------------------------------------------------------------
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
UINT SY6522::GetOpcodeCyclesForRead(BYTE reg)
{
UINT zpOpcodeCycles = 0, opcodeCycles = 0;
BYTE zpOpcode = 0, opcode = 0; // these double-up as flags to indicate validity
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];
// Check 2-byte opcodes
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)
zpOpcodeCycles = 6;
zpOpcode = 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
zpOpcodeCycles = 5;
zpOpcode = 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)
zpOpcodeCycles = 5;
zpOpcode = opcodeMinus2;
}
// Check 3-byte opcodes
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
{
opcodeCycles = 4;
opcode = opcodeMinus3;
}
else if ((opcodeMinus3 == 0xBC) || // ldy abs16,x
((opcodeMinus3 == 0x3C) && GetMainCpu() == CPU_65C02)) // bit abs16,x : 65C02-only
{
opcodeCycles = 4;
opcode = opcodeMinus3;
abs16x = true;
}
else if ((opcodeMinus3 == 0xBE)) // ldx abs16,y
{
opcodeCycles = 4;
opcode = opcodeMinus3;
abs16y = true;
}
else if ((opcodeMinus3 & 0x10) == 0x10)
{
if ((opcodeMinus3 & 0x0f) == 0x0D) // ora abs16,x, and abs16,x, ..., sbc abs16,x
{
opcodeCycles = 4;
opcode = opcodeMinus3;
abs16x = true;
}
else if ((opcodeMinus3 & 0x0f) == 0x09) // ora abs16,y, and abs16,y, ..., sbc abs16,y
{
opcodeCycles = 4;
opcode = opcodeMinus3;
abs16y = true;
}
}
//
if (!opcode && !zpOpcode) // Unsupported opcode
{
_ASSERT(0);
return 0;
}
return GetOpcodeCycles(reg, zpOpcodeCycles, opcodeCycles, zpOpcode, opcode, abs16x, abs16y, indx, indy);
}
// TODO: RMW opcodes: dec,inc,asl,lsr,rol,ror (abs16 & abs16,x) + 65C02 trb,tsb (abs16)
UINT SY6522::GetOpcodeCyclesForWrite(BYTE reg)
{
UINT zpOpcodeCycles = 0, opcodeCycles = 0;
BYTE zpOpcode = 0, opcode = 0; // these double-up as flags to indicate validity
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];
// Check 2-byte opcodes
if (opcodeMinus2 == 0x81) // sta (zp,x)
{
zpOpcodeCycles = 6;
zpOpcode = opcodeMinus2;
indx = true;
}
else if (opcodeMinus2 == 0x91) // sta (zp),y
{ // Eg. FT demos: OMT, PLS
zpOpcodeCycles = 6;
zpOpcode = opcodeMinus2;
indy = true;
}
else if (opcodeMinus2 == 0x92 && GetMainCpu() == CPU_65C02) // sta (zp) : 65C02-only
{
zpOpcodeCycles = 5;
zpOpcode = opcodeMinus2;
}
// Check 3-byte opcodes
if ((opcodeMinus3 == 0x8C) || // sty abs16
(opcodeMinus3 == 0x8D) || // sta abs16
(opcodeMinus3 == 0x8E)) // stx abs16
{ // Eg. FT demos: CHIP, MADEF, MAD2
opcodeCycles = 4;
opcode = opcodeMinus3;
}
else if (opcodeMinus3 == 0x99) // sta abs16,y
{
opcodeCycles = 5;
opcode = opcodeMinus3;
abs16y = true;
}
else if (opcodeMinus3 == 0x9D) // sta abs16,x
{ // Eg. Paleotronic microTracker demo
opcodeCycles = 5;
opcode = opcodeMinus3;
abs16x = true;
}
else if (opcodeMinus3 == 0x9C && GetMainCpu() == CPU_65C02) // stz abs16 : 65C02-only
{
opcodeCycles = 4;
opcode = opcodeMinus3;
}
else if (opcodeMinus3 == 0x9E && GetMainCpu() == CPU_65C02) // stz abs16,x : 65C02-only
{
opcodeCycles = 5;
opcode = opcodeMinus3;
abs16x = true;
}
//
if (!opcode && !zpOpcode) // Unsupported opcode
{
_ASSERT(0);
return 0;
}
return GetOpcodeCycles(reg, zpOpcodeCycles, opcodeCycles, zpOpcode, opcode, abs16x, abs16y, indx, indy);
}
UINT SY6522::GetOpcodeCycles(BYTE reg, UINT zpOpcodeCycles, UINT opcodeCycles,
BYTE zpOpcode, BYTE opcode,
bool abs16x, bool abs16y, bool indx, bool indy)
{
WORD zpAddr16 = 0, addr16 = 0;
if (zpOpcode)
{
BYTE zp = mem[(::regs.pc - 1) & 0xffff];
if (indx) zp += ::regs.x;
zpAddr16 = (mem[zp] | (mem[(zp + 1) & 0xff] << 8));
if (indy) zpAddr16 += ::regs.y;
}
if (opcode)
{
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
const bool isZpAddrValid = (zpAddr16 & 0xF80F) == (0xC000 + reg);
const bool isAbs16AddrValid = (addr16 & 0xF80F) == (0xC000 + reg);
if ( (isZpAddrValid && isAbs16AddrValid)
|| (!isZpAddrValid && !isAbs16AddrValid) )
{
_ASSERT(0);
return 0;
}
return isZpAddrValid ? zpOpcodeCycles : 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);
}
}