Split 6522 out of MB code and into own class (PR #1028)

NB. Update CpuInitialize() & CriticalSection creation:
. call CpuInitialize() on WM_CREATE to create CriticalSection
. needed as MB_Initialize() needs CriticalSection
This commit is contained in:
TomCh 2022-02-05 18:48:36 +00:00 committed by GitHub
parent 53ab72ce13
commit f7c6ef397c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1121 additions and 897 deletions

View File

@ -641,6 +641,14 @@
<Filter
Name="Emulator"
>
<File
RelativePath=".\source\6522.cpp"
>
</File>
<File
RelativePath=".\source\6522.h"
>
</File>
<File
RelativePath=".\source\6821.cpp"
>

View File

@ -29,6 +29,7 @@
<ItemGroup>
<ClInclude Include="resource\resource.h" />
<ClInclude Include="resource\winres.h" />
<ClInclude Include="source\6522.h" />
<ClInclude Include="source\6821.h" />
<ClInclude Include="source\AY8910.h" />
<ClInclude Include="source\Card.h" />
@ -143,6 +144,7 @@
<Text Include="docs\Wishlist.txt" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="source\6522.cpp" />
<ClCompile Include="source\6821.cpp" />
<ClCompile Include="source\AY8910.cpp" />
<ClCompile Include="source\Card.cpp" />

View File

@ -244,6 +244,9 @@
<ClCompile Include="Source\VidHD.cpp">
<Filter>Source Files\Video</Filter>
</ClCompile>
<ClCompile Include="source\6522.cpp">
<Filter>Source Files\Emulator</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="source\CommonVICE\6510core.h">
@ -561,6 +564,9 @@
<ClInclude Include="Source\VidHD.h">
<Filter>Source Files\Video</Filter>
</ClInclude>
<ClInclude Include="source\6522.h">
<Filter>Source Files\Emulator</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="resource\Applewin.bmp">

690
source/6522.cpp Normal file
View File

@ -0,0 +1,690 @@
/*
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 "Core.h"
#include "CPU.h"
#include "Memory.h"
#include "SynchronousEventManager.h"
#include "YamlHelper.h"
// TODO: remove these (just including for now to allow TU to compile)
extern UINT g_nMBTimerDevice;
static const UINT kTIMERDEVICE_INVALID = -1;
static const UINT kAY8910Number = 0; // needed?
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!
}
Write(rACR, 0x00); // ACR = 0x00: T1 one-shot mode
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;
if (m_regs.IER & IxR_TIMER1) // Using 6522 interrupt
g_nMBTimerDevice = kAY8910Number;
else if (m_regs.ACR & ACR_RM_FREERUNNING) // Polling 6522 IFR (GH#496)
g_nMBTimerDevice = kAY8910Number;
}
// 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;
g_nMBTimerDevice = kAY8910Number;
}
void SY6522::StopTimer1(void)
{
m_timer1Active = false;
g_nMBTimerDevice = kTIMERDEVICE_INVALID;
}
//-----------------------------------------------------------------------------
void SY6522::StartTimer2(void)
{
m_timer2Active = true;
// NB. Can't mimic StartTimer1() as that would stomp on global state
// TODO: Switch to per-device state
}
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);
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);
}
//-----------------------------------------------------------------------------
extern void MB_UpdateIRQ(void);
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;
MB_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;
}
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 (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);
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 == -1) ? 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)
{
// FIXME: "m_regs.TIMER2_COUNTER.w >= 0" is always true! So function returns true when GetTimer2Counter() returns with b15==1
return m_regs.TIMER2_COUNTER.w >= 0 && (short)GetTimer2Counter(reg) < 0;
}
//-----------------------------------------------------------------------------
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);
break;
case 0x09: // TIMER2H
nValue = GetTimer2Counter(nReg) >> 8;
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
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
g_SynchronousEventMgr.Insert(syncEvent);
}
if (IsTimer2Active())
{
SyncEvent* syncEvent = m_syncEvent[1];
syncEvent->SetCycles(GetRegT2C() + kExtraTimerCycles); // NB. use COUNTER, not LATCH
g_SynchronousEventMgr.Insert(syncEvent);
}
}

142
source/6522.h Normal file
View File

@ -0,0 +1,142 @@
#pragma once
class SY6522
{
public:
SY6522(void)
{
for (UINT i = 0; i < kNumTimersPer6522; i++)
m_syncEvent[i] = NULL;
}
~SY6522(void)
{
}
void InitSyncEvents(class SyncEvent* event0, class SyncEvent* event1)
{
m_syncEvent[0] = event0;
m_syncEvent[1] = event1;
}
void Reset(const bool powerCycle);
void StartTimer1(void);
void StopTimer1(void);
bool IsTimer1Active(void) { return m_timer1Active; }
void StopTimer2(void);
bool IsTimer2Active(void) { return m_timer2Active; }
void UpdateIFR(BYTE clr_ifr, BYTE set_ifr = 0);
void UpdateTimer1(USHORT clocks);
void UpdateTimer2(USHORT clocks);
enum { rORB = 0, rORA, rDDRB, rDDRA, rT1CL, rT1CH, rT1LL, rT1LH, rT2CL, rT2CH, rSR, rACR, rPCR, rIFR, rIER, rORA_NO_HS, SIZE_6522_REGS };
BYTE GetReg(BYTE reg)
{
switch (reg)
{
case rDDRA: return m_regs.DDRA;
case rORA: return m_regs.ORA;
case rACR: return m_regs.ACR;
case rPCR: return m_regs.PCR;
case rIFR: return m_regs.IFR;
}
_ASSERT(0);
return 0;
}
USHORT GetRegT1C(void) { return m_regs.TIMER1_COUNTER.w; }
USHORT GetRegT2C(void) { return m_regs.TIMER2_COUNTER.w; }
void GetRegs(BYTE regs[SIZE_6522_REGS]) { memcpy(&regs[0], (BYTE*)&m_regs, SIZE_6522_REGS); } // For debugger
void SetRegORA(BYTE reg) { m_regs.ORA = reg; }
BYTE Read(BYTE nReg);
void Write(BYTE nReg, BYTE nValue);
void SaveSnapshot(class YamlSaveHelper& yamlSaveHelper);
void LoadSnapshot(class YamlLoadHelper& yamlLoadHelper, UINT version);
void SetTimersActiveFromSnapshot(bool timer1Active, bool timer2Active, UINT version);
// ACR
static const BYTE ACR_RUNMODE = 1 << 6;
static const BYTE ACR_RM_ONESHOT = 0 << 6;
static const BYTE ACR_RM_FREERUNNING = 1 << 6;
// IFR & IER:
static const BYTE IxR_SSI263 = 1 << 1;
static const BYTE IxR_VOTRAX = 1 << 4;
static const BYTE IxR_TIMER2 = 1 << 5;
static const BYTE IxR_TIMER1 = 1 << 6;
static const BYTE IFR_IRQ = 1 << 7;
static const UINT kExtraTimerCycles = 2; // Rockwell, Fig.16: period = N+2 cycles
static const UINT kNumTimersPer6522 = 2;
private:
USHORT SetTimerSyncEvent(BYTE reg, USHORT timerLatch);
USHORT GetTimer1Counter(BYTE reg);
USHORT GetTimer2Counter(BYTE reg);
bool IsTimer1Underflowed(BYTE reg);
bool IsTimer2Underflowed(BYTE reg);
bool CheckTimerUnderflow(USHORT& counter, int& timerIrqDelay, const USHORT clocks);
int OnTimer1Underflow(USHORT& counter);
UINT GetOpcodeCyclesForRead(BYTE reg);
UINT GetOpcodeCyclesForWrite(BYTE reg);
void StartTimer2(void);
void StartTimer1_LoadStateV1(void);
#pragma pack(push)
#pragma pack(1) // Ensure 'struct Regs' is packed so that GetRegs() can just do a memcpy()
struct IWORD
{
union
{
struct
{
BYTE l;
BYTE h;
};
USHORT w;
};
};
struct Regs
{
BYTE ORB; // $00 - Port B
BYTE ORA; // $01 - Port A (with handshaking)
BYTE DDRB; // $02 - Data Direction Register B
BYTE DDRA; // $03 - Data Direction Register A
IWORD TIMER1_COUNTER; // $04 - Read counter (L) / Write latch (L)
// $05 - Read / Write & initiate count (H)
IWORD TIMER1_LATCH; // $06 - Read / Write & latch (L)
// $07 - Read / Write & latch (H)
IWORD TIMER2_COUNTER; // $08 - Read counter (L) / Write latch (L)
// $09 - Read counter (H) / Write latch (H)
BYTE SERIAL_SHIFT; // $0A
BYTE ACR; // $0B - Auxiliary Control Register
BYTE PCR; // $0C - Peripheral Control Register
BYTE IFR; // $0D - Interrupt Flag Register
BYTE IER; // $0E - Interrupt Enable Register
BYTE ORA_NO_HS; // $0F - Port A (without handshaking)
//
IWORD TIMER2_LATCH; // Doesn't exist in 6522
};
#pragma pack(pop)
Regs m_regs;
int m_timer1IrqDelay;
int m_timer2IrqDelay;
bool m_timer1Active;
bool m_timer2Active;
class SyncEvent* m_syncEvent[kNumTimersPer6522];
};

View File

@ -557,17 +557,6 @@ void CpuWrite(USHORT addr, BYTE value, ULONG uExecutedCycles)
//===========================================================================
void CpuDestroy ()
{
if (g_bCritSectionValid)
{
DeleteCriticalSection(&g_CriticalSection);
g_bCritSectionValid = false;
}
}
//===========================================================================
// Description:
// Call this when an IO-reg is accessed & accurate cycle info is needed
// NB. Safe to call multiple times from the same IO function handler (as 'nExecutedCycles - g_nCyclesExecuted' will be zero the 2nd time)
@ -631,9 +620,10 @@ DWORD CpuExecute(const DWORD uCycles, const bool bVideoUpdate)
// >0 : Do multi-opcode emulation
const DWORD uExecutedCycles = InternalCpuExecute(uCycles, bVideoUpdate);
// NB. Required for normal-speed (even though 6522 is updated after every opcode), as may've finished on IRQ()
MB_UpdateCycles(uExecutedCycles); // Update 6522s (NB. Do this before updating g_nCumulativeCycles below)
// NB. Ensures that 6522 regs are up-to-date for any potential save-state
// Update 6522s (NB. Do this before updating g_nCumulativeCycles below)
// . Ensures that 6522 regs are up-to-date for any potential save-state
// . SyncEvent will trigger the 6522 TIMER1/2 underflow on the correct cycle
MB_UpdateCycles(uExecutedCycles);
const UINT nRemainingCycles = uExecutedCycles - g_nCyclesExecuted;
g_nCumulativeCycles += nRemainingCycles;
@ -643,15 +633,24 @@ DWORD CpuExecute(const DWORD uCycles, const bool bVideoUpdate)
//===========================================================================
void CpuInitialize ()
// Called from RepeatInitialization():
// 1) FrameCreateWindow() -> WM_CREATE
// - done to init g_CriticalSection
// - but can't call CpuReset() as mem == NULL
// 2) MemInitialize() -> MemReset()
void CpuInitialize(bool reset)
{
CpuDestroy();
regs.a = regs.x = regs.y = regs.ps = 0xFF;
regs.a = regs.x = regs.y = 0xFF;
regs.sp = 0x01FF;
CpuReset(); // Init's ps & pc. Updates sp
if (reset)
CpuReset();
if (!g_bCritSectionValid)
{
InitializeCriticalSection(&g_CriticalSection);
g_bCritSectionValid = true;
}
InitializeCriticalSection(&g_CriticalSection);
g_bCritSectionValid = true;
CpuIrqReset();
CpuNmiReset();
@ -661,6 +660,34 @@ void CpuInitialize ()
//===========================================================================
void CpuDestroy()
{
if (g_bCritSectionValid)
{
DeleteCriticalSection(&g_CriticalSection);
g_bCritSectionValid = false;
}
}
//===========================================================================
void CpuReset()
{
// 7 cycles
regs.ps = (regs.ps | AF_INTERRUPT) & ~AF_DECIMAL;
regs.pc = *(WORD*)(mem + 0xFFFC);
regs.sp = 0x0100 | ((regs.sp - 3) & 0xFF);
regs.bJammed = 0;
g_irqDefer1Opcode = false;
SetActiveCpu(GetMainCpu());
z80_reset();
}
//===========================================================================
void CpuSetupBenchmark ()
{
regs.a = 0;
@ -713,6 +740,7 @@ void CpuIrqAssert(eIRQSRC Device)
void CpuIrqDeassert(eIRQSRC Device)
{
_ASSERT(g_bCritSectionValid);
if (g_bCritSectionValid) EnterCriticalSection(&g_CriticalSection);
g_bmIRQ &= ~(1<<Device);
if (g_bCritSectionValid) LeaveCriticalSection(&g_CriticalSection);
@ -749,23 +777,6 @@ void CpuNmiDeassert(eIRQSRC Device)
//===========================================================================
void CpuReset()
{
// 7 cycles
regs.ps = (regs.ps | AF_INTERRUPT) & ~AF_DECIMAL;
regs.pc = * (WORD*) (mem+0xFFFC);
regs.sp = 0x0100 | ((regs.sp - 3) & 0xFF);
regs.bJammed = 0;
g_irqDefer1Opcode = false;
SetActiveCpu( GetMainCpu() );
z80_reset();
}
//===========================================================================
#define SS_YAML_KEY_CPU_TYPE "Type"
#define SS_YAML_KEY_REGA "A"
#define SS_YAML_KEY_REGX "X"

View File

@ -32,7 +32,7 @@ void CpuDestroy ();
void CpuCalcCycles(ULONG nExecutedCycles);
DWORD CpuExecute(const DWORD uCycles, const bool bVideoUpdate);
ULONG CpuGetCyclesThisVideoFrame(ULONG nExecutedCycles);
void CpuInitialize ();
void CpuInitialize(bool reset);
void CpuSetupBenchmark ();
void CpuIrqReset();
void CpuIrqAssert(eIRQSRC Device);

View File

@ -1926,7 +1926,7 @@ void MemReset()
// INITIALIZE & RESET THE CPU
// . Do this after ROM has been copied back to mem[], so that PC is correctly init'ed from 6502's reset vector
CpuInitialize();
CpuInitialize(true);
//Sets Caps Lock = false (Pravets 8A/C only)
z80_reset(); // NB. Also called above in CpuInitialize()

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,6 @@
enum PHASOR_MODE {PH_Mockingboard=0, PH_UNDEF1, PH_UNDEF2, PH_UNDEF3, PH_UNDEF4, PH_Phasor/*=5*/, PH_UNDEF6, PH_EchoPlus/*=7*/};
// IFR & IER:
#define IxR_SSI263 (1<<1)
#define IxR_VOTRAX (1<<4)
#define IxR_TIMER2 (1<<5)
#define IxR_TIMER1 (1<<6)
void MB_Initialize();
void MB_Reinitialize();
void MB_Destroy();
@ -32,6 +25,7 @@ DWORD MB_GetVolume();
void MB_SetVolume(DWORD dwVolume, DWORD dwVolumeMax);
void MB_Get6522IrqDescription(std::string& desc);
void MB_UpdateIRQ(void);
UINT64 MB_GetLastCumulativeCycles(void);
void MB_UpdateIFR(BYTE nDevice, BYTE clr_mask, BYTE set_mask);
BYTE MB_GetPCR(BYTE nDevice);

View File

@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "StdAfx.h"
#include "6522.h"
#include "Core.h"
#include "CPU.h"
#include "Log.h"
@ -293,7 +294,7 @@ void SSI263::Votrax_Write(BYTE value)
m_isVotraxPhoneme = true;
// !A/R: Acknowledge receipt of phoneme data (signal goes from high to low)
MB_UpdateIFR(m_device, IxR_VOTRAX, 0);
MB_UpdateIFR(m_device, SY6522::IxR_VOTRAX, 0);
// NB. Don't set reg0.DUR, as SC01's phoneme duration doesn't change with pitch (empirically determined from MAME's SC01 emulation)
//m_durationPhoneme = value; // Set reg0.DUR = I1:0 (inflection or pitch)
@ -720,7 +721,7 @@ void SSI263::SetSpeechIRQ(void)
if (m_cardMode == PH_Mockingboard)
{
if ((MB_GetPCR(m_device) & 1) == 0) // CA1 Latch/Input = 0 (Negative active edge)
MB_UpdateIFR(m_device, 0, IxR_SSI263);
MB_UpdateIFR(m_device, 0, SY6522::IxR_SSI263);
if (MB_GetPCR(m_device) == 0x0C) // CA2 Control = b#110 (Low output)
m_currentMode &= ~1; // Clear SSI263's D7 pin (cleared by 6522's PCR CA1/CA2 handshake)
@ -744,7 +745,7 @@ void SSI263::SetSpeechIRQ(void)
{
// !A/R: Time-out of old phoneme (signal goes from low to high)
MB_UpdateIFR(m_device, 0, IxR_VOTRAX);
MB_UpdateIFR(m_device, 0, SY6522::IxR_VOTRAX);
m_isVotraxPhoneme = false;
}

View File

@ -62,20 +62,7 @@ struct SS_CARD_EMPTY
/////////////////////////////////////////////////////////////////////////////////
struct IWORD
{
union
{
struct
{
BYTE l;
BYTE h;
};
USHORT w;
};
};
struct SY6522
struct SY6522A
{
BYTE ORB; // $00 - Port B
BYTE ORA; // $01 - Port A (with handshaking)
@ -88,10 +75,10 @@ struct SY6522
// $07 - Read / Write & latch (H)
// $08 - Read counter (L) / Write latch (L)
// $09 - Read counter (H) / Write latch (H)
IWORD TIMER1_COUNTER;
IWORD TIMER1_LATCH;
IWORD TIMER2_COUNTER;
IWORD TIMER2_LATCH;
USHORT TIMER1_COUNTER;
USHORT TIMER1_LATCH;
USHORT TIMER2_COUNTER;
USHORT TIMER2_LATCH;
int timer1IrqDelay;
int timer2IrqDelay;
//

View File

@ -112,7 +112,7 @@ struct SS_CARD_DISK2
struct MB_Unit_v1
{
SY6522 RegsSY6522;
SY6522A RegsSY6522;
BYTE RegsAY8910[16];
SSI263A RegsSSI263;
BYTE nAYCurrentRegister;

View File

@ -544,9 +544,6 @@ int APIENTRY WinMain(HINSTANCE passinstance, HINSTANCE, LPSTR lpCmdLine, int)
g_cmdLine.setFullScreen = g_bRestartFullScreen ? 1 : 0;
g_bRestartFullScreen = false;
MB_Reset(true);
LogFileOutput("Main: MB_Reset()\n");
CMouseInterface* pMouseCard = GetCardMgr().GetMouseCard();
if (pMouseCard)
{

View File

@ -1001,6 +1001,9 @@ LRESULT Win32Frame::WndProc(
DIMouse::DirectInputInit(window);
LogFileOutput("WM_CREATE: DIMouse::DirectInputInit()\n");
CpuInitialize(false); // NB. Creates CriticalSection that's needed by MB_Initialize()
LogFileOutput("WM_CREATE: CpuInitialize()\n");
MB_Initialize();
LogFileOutput("WM_CREATE: MB_Initialize()\n");