BizHawk/BizHawk.Emulation/Computers/Commodore64/MOS/MOS6526.cs

703 lines
16 KiB
C#

using System;
namespace BizHawk.Emulation.Computers.Commodore64.MOS
{
// MOS technology 6526 "CIA"
//
// emulation notes:
// * CS, R/W and RS# pins are not emulated. (not needed)
// * A low RES pin is emulated via HardReset().
public class MOS6526 : Timer
{
// ------------------------------------
protected enum InMode
{
Phase2,
CNT,
TimerAUnderflow,
TimerAUnderflowCNT
}
protected enum OutMode
{
Pulse,
Toggle
}
protected enum RunMode
{
Continuous,
Oneshot
}
protected enum SPMode
{
Input,
Output
}
protected static byte[] PBOnBit = new byte[] { 0x40, 0x80 };
protected static byte[] PBOnMask = new byte[] { 0xBF, 0x7F };
// ------------------------------------
public Func<bool> ReadCNT;
public Func<bool> ReadFlag;
public Func<bool> ReadSP;
// ------------------------------------
protected bool alarmSelect;
protected Region chipRegion;
protected bool cntPos;
protected bool enableIntAlarm;
protected bool enableIntFlag;
protected bool enableIntSP;
protected bool[] enableIntTimer;
protected bool intAlarm;
protected bool intFlag;
protected bool intSP;
protected bool[] intTimer;
protected bool pinCnt;
protected bool pinCntLast;
protected bool pinPC;
protected bool pinSP;
protected byte sr;
protected int[] timerDelay;
protected InMode[] timerInMode;
protected OutMode[] timerOutMode;
protected bool[] timerPortEnable;
protected bool[] timerPulse;
protected RunMode[] timerRunMode;
protected SPMode timerSPMode;
protected byte[] tod;
protected byte[] todAlarm;
protected bool todAlarmPM;
protected int todCounter;
protected int todCounterLatch;
protected bool todIn;
protected bool todPM;
// ------------------------------------
public MOS6526(Region region)
{
chipRegion = region;
enableIntTimer = new bool[2];
intTimer = new bool[2];
timerDelay = new int[2];
timerInMode = new InMode[2];
timerOutMode = new OutMode[2];
timerPortEnable = new bool[2];
timerPulse = new bool[2];
timerRunMode = new RunMode[2];
tod = new byte[4];
todAlarm = new byte[4];
SetTodIn(chipRegion);
}
// ------------------------------------
public void ExecutePhase1()
{
// unsure if the timer actually operates in ph1
pinIRQ = !(
(intTimer[0] && enableIntTimer[0]) ||
(intTimer[1] && enableIntTimer[1]) ||
(intAlarm && enableIntAlarm) ||
(intSP && enableIntSP) ||
(intFlag && enableIntFlag)
);
}
public void ExecutePhase2()
{
{
bool sumCnt = ReadCNT();
cntPos |= (!pinCntLast && sumCnt);
pinCntLast = sumCnt;
pinPC = true;
TODRun();
if (timerPulse[0])
{
portA.Latch &= PBOnMask[0];
}
if (timerPulse[1])
{
portB.Latch &= PBOnMask[1];
}
if (timerDelay[0] == 0)
TimerRun(0);
else
timerDelay[0]--;
if (timerDelay[1] == 0)
TimerRun(1);
else
timerDelay[1]--;
intAlarm |= (
tod[0] == todAlarm[0] &&
tod[1] == todAlarm[1] &&
tod[2] == todAlarm[2] &&
tod[3] == todAlarm[3] &&
todPM == todAlarmPM);
cntPos = false;
underflow[0] = false;
underflow[1] = false;
}
}
public void HardReset()
{
HardResetInternal();
alarmSelect = false;
cntPos = false;
enableIntAlarm = false;
enableIntFlag = false;
enableIntSP = false;
enableIntTimer[0] = false;
enableIntTimer[1] = false;
intAlarm = false;
intFlag = false;
intSP = false;
intTimer[0] = false;
intTimer[1] = false;
sr = 0;
timerDelay[0] = 0;
timerDelay[1] = 0;
timerInMode[0] = InMode.Phase2;
timerInMode[1] = InMode.Phase2;
timerOn[0] = false;
timerOn[1] = false;
timerOutMode[0] = OutMode.Pulse;
timerOutMode[1] = OutMode.Pulse;
timerPortEnable[0] = false;
timerPortEnable[1] = false;
timerPulse[0] = false;
timerPulse[1] = false;
timerRunMode[0] = RunMode.Continuous;
timerRunMode[1] = RunMode.Continuous;
timerSPMode = SPMode.Input;
tod[0] = 0;
tod[1] = 0;
tod[2] = 0;
tod[3] = 0x12;
todAlarm[0] = 0;
todAlarm[1] = 0;
todAlarm[2] = 0;
todAlarm[3] = 0;
todCounter = todCounterLatch;
todIn = (chipRegion == Region.PAL);
todPM = false;
pinCnt = false;
pinPC = true;
}
private void SetTodIn(Region region)
{
switch (region)
{
case Region.NTSC:
todCounterLatch = 14318181 / 140;
todIn = false;
break;
case Region.PAL:
todCounterLatch = 17734472 / 180;
todIn = true;
break;
}
}
// ------------------------------------
private byte BCDAdd(byte i, byte j, out bool overflow)
{
{
int lo;
int hi;
int result;
lo = (i & 0x0F) + (j & 0x0F);
hi = (i & 0x70) + (j & 0x70);
if (lo > 0x09)
{
hi += 0x10;
lo += 0x06;
}
if (hi > 0x50)
{
hi += 0xA0;
}
overflow = hi >= 0x60;
result = (hi & 0x70) + (lo & 0x0F);
return (byte)(result & 0xFF);
}
}
private void TimerRun(int index)
{
{
if (timerOn[index])
{
int t = timer[index];
bool u = false;
{
switch (timerInMode[index])
{
case InMode.CNT:
// CNT positive
if (cntPos)
{
t--;
u = (t == 0);
intTimer[index] |= (t == 0);
}
break;
case InMode.Phase2:
// every clock
t--;
u = (t == 0);
intTimer[index] |= (t == 0);
break;
case InMode.TimerAUnderflow:
// every underflow[0]
if (underflow[0])
{
t--;
u = (t == 0);
intTimer[index] |= (t == 0);
}
break;
case InMode.TimerAUnderflowCNT:
// every underflow[0] while CNT high
if (underflow[0] && pinCnt)
{
t--;
u = (t == 0);
intTimer[index] |= (t == 0);
}
break;
}
// underflow?
if (u)
{
timerDelay[index]++;
t = timerLatch[index];
if (timerRunMode[index] == RunMode.Oneshot)
timerOn[index] = false;
if (timerPortEnable[index])
{
// force port B bit to output
portB.Direction |= PBOnBit[index];
switch (timerOutMode[index])
{
case OutMode.Pulse:
timerPulse[index] = true;
portB.Latch |= PBOnBit[index];
break;
case OutMode.Toggle:
portB.Latch ^= PBOnBit[index];
break;
}
}
}
underflow[index] = u;
timer[index] = t;
}
}
}
}
private void TODRun()
{
{
bool todV;
if (todCounter == 0)
{
todCounter = todCounterLatch;
tod[0] = BCDAdd(tod[0], 1, out todV);
if (tod[0] >= 10)
{
tod[0] = 0;
tod[1] = BCDAdd(tod[1], 1, out todV);
if (todV)
{
tod[1] = 0;
tod[2] = BCDAdd(tod[2], 1, out todV);
if (todV)
{
tod[2] = 0;
tod[3] = BCDAdd(tod[3], 1, out todV);
if (tod[3] > 12)
{
tod[3] = 1;
}
else if (tod[3] == 12)
{
todPM = !todPM;
}
}
}
}
}
todCounter--;
}
}
// ------------------------------------
public byte Peek(int addr)
{
return ReadRegister(addr & 0xF);
}
public void Poke(int addr, byte val)
{
WriteRegister((addr & 0xF), val);
}
public byte Read(int addr)
{
return Read(addr, 0xFF);
}
public byte Read(int addr, byte mask)
{
addr &= 0xF;
byte val;
switch (addr)
{
case 0x01:
val = ReadRegister(addr);
pinPC = false;
break;
case 0x0D:
val = ReadRegister(addr);
intTimer[0] = false;
intTimer[1] = false;
intAlarm = false;
intSP = false;
intFlag = false;
pinIRQ = true;
break;
default:
val = ReadRegister(addr);
break;
}
val &= mask;
return val;
}
public bool ReadCNTBuffer()
{
return pinCnt;
}
public bool ReadPCBuffer()
{
return pinPC;
}
private byte ReadRegister(int addr)
{
byte val = 0x00; //unused pin value
int timerVal;
switch (addr)
{
case 0x0:
val = (byte)(portA.ReadInput(ReadPortA()) & PortAMask);
break;
case 0x1:
val = (byte)(portB.ReadInput(ReadPortB()) & PortBMask);
break;
case 0x2:
val = portA.Direction;
break;
case 0x3:
val = portB.Direction;
break;
case 0x4:
timerVal = ReadTimerValue(0);
val = (byte)(timerVal & 0xFF);
break;
case 0x5:
timerVal = ReadTimerValue(0);
val = (byte)(timerVal >> 8);
break;
case 0x6:
timerVal = ReadTimerValue(1);
val = (byte)(timerVal & 0xFF);
break;
case 0x7:
timerVal = ReadTimerValue(1);
val = (byte)(timerVal >> 8);
break;
case 0x8:
val = tod[0];
break;
case 0x9:
val = tod[1];
break;
case 0xA:
val = tod[2];
break;
case 0xB:
val = tod[3];
break;
case 0xC:
val = sr;
break;
case 0xD:
val = (byte)(
(intTimer[0] ? 0x01 : 0x00) |
(intTimer[1] ? 0x02 : 0x00) |
(intAlarm ? 0x04 : 0x00) |
(intSP ? 0x08 : 0x00) |
(intFlag ? 0x10 : 0x00) |
(!pinIRQ ? 0x80 : 0x00)
);
break;
case 0xE:
val = (byte)(
(timerOn[0] ? 0x01 : 0x00) |
(timerPortEnable[0] ? 0x02 : 0x00) |
(todIn ? 0x80 : 0x00));
if (timerOutMode[0] == OutMode.Toggle)
val |= 0x04;
if (timerRunMode[0] == RunMode.Oneshot)
val |= 0x08;
if (timerInMode[0] == InMode.CNT)
val |= 0x20;
if (timerSPMode == SPMode.Output)
val |= 0x40;
break;
case 0xF:
val = (byte)(
(timerOn[1] ? 0x01 : 0x00) |
(timerPortEnable[1] ? 0x02 : 0x00) |
(alarmSelect ? 0x80 : 0x00));
if (timerOutMode[1] == OutMode.Toggle)
val |= 0x04;
if (timerRunMode[1] == RunMode.Oneshot)
val |= 0x08;
switch (timerInMode[1])
{
case InMode.CNT:
val |= 0x20;
break;
case InMode.TimerAUnderflow:
val |= 0x40;
break;
case InMode.TimerAUnderflowCNT:
val |= 0x60;
break;
}
break;
}
return val;
}
public bool ReadSPBuffer()
{
return pinSP;
}
private int ReadTimerValue(int index)
{
if (timerOn[index])
{
if (timer[index] == 0)
return timerLatch[index];
else
return timer[index];
}
else
{
return timer[index];
}
}
public void SyncState(Serializer ser)
{
Sync.SyncObject(ser, this);
}
public void Write(int addr, byte val)
{
Write(addr, val, 0xFF);
}
public void Write(int addr, byte val, byte mask)
{
addr &= 0xF;
val &= mask;
val |= (byte)(ReadRegister(addr) & ~mask);
switch (addr)
{
case 0x1:
WriteRegister(addr, val);
pinPC = false;
break;
case 0x5:
WriteRegister(addr, val);
if (!timerOn[0])
timer[0] = timerLatch[0];
break;
case 0x7:
WriteRegister(addr, val);
if (!timerOn[1])
timer[1] = timerLatch[1];
break;
case 0xE:
WriteRegister(addr, val);
if ((val & 0x10) != 0)
timer[0] = timerLatch[0];
break;
case 0xF:
WriteRegister(addr, val);
if ((val & 0x10) != 0)
timer[1] = timerLatch[1];
break;
default:
WriteRegister(addr, val);
break;
}
}
public void WriteRegister(int addr, byte val)
{
bool intReg;
switch (addr)
{
case 0x0:
portA.Latch = val;
break;
case 0x1:
portB.Latch = val;
break;
case 0x2:
portA.Direction = val;
break;
case 0x3:
portB.Direction = val;
break;
case 0x4:
timerLatch[0] &= 0xFF00;
timerLatch[0] |= val;
break;
case 0x5:
timerLatch[0] &= 0x00FF;
timerLatch[0] |= val << 8;
break;
case 0x6:
timerLatch[1] &= 0xFF00;
timerLatch[1] |= val;
break;
case 0x7:
timerLatch[1] &= 0x00FF;
timerLatch[1] |= val << 8;
break;
case 0x8:
if (alarmSelect)
todAlarm[0] = (byte)(val & 0xF);
else
tod[0] = (byte)(val & 0xF);
break;
case 0x9:
if (alarmSelect)
todAlarm[1] = (byte)(val & 0x7F);
else
tod[1] = (byte)(val & 0x7F);
break;
case 0xA:
if (alarmSelect)
todAlarm[2] = (byte)(val & 0x7F);
else
tod[2] = (byte)(val & 0x7F);
break;
case 0xB:
if (alarmSelect)
{
todAlarm[3] = (byte)(val & 0x1F);
todAlarmPM = ((val & 0x80) != 0);
}
else
{
tod[3] = (byte)(val & 0x1F);
todPM = ((val & 0x80) != 0);
}
break;
case 0xC:
sr = val;
break;
case 0xD:
intReg = ((val & 0x80) != 0);
if ((val & 0x01) != 0)
enableIntTimer[0] = intReg;
if ((val & 0x02) != 0)
enableIntTimer[1] = intReg;
if ((val & 0x04) != 0)
enableIntAlarm = intReg;
if ((val & 0x08) != 0)
enableIntSP = intReg;
if ((val & 0x10) != 0)
enableIntFlag = intReg;
break;
case 0xE:
if ((val & 0x01) != 0 && !timerOn[0])
timerDelay[0] = 2;
timerOn[0] = ((val & 0x01) != 0);
timerPortEnable[0] = ((val & 0x02) != 0);
timerOutMode[0] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse;
timerRunMode[0] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous;
timerInMode[0] = ((val & 0x20) != 0) ? InMode.CNT : InMode.Phase2;
timerSPMode = ((val & 0x40) != 0) ? SPMode.Output : SPMode.Input;
todIn = ((val & 0x80) != 0);
break;
case 0xF:
if ((val & 0x01) != 0 && !timerOn[1])
timerDelay[1] = 2;
timerOn[1] = ((val & 0x01) != 0);
timerPortEnable[1] = ((val & 0x02) != 0);
timerOutMode[1] = ((val & 0x04) != 0) ? OutMode.Toggle : OutMode.Pulse;
timerRunMode[1] = ((val & 0x08) != 0) ? RunMode.Oneshot : RunMode.Continuous;
switch (val & 0x60)
{
case 0x00: timerInMode[1] = InMode.Phase2; break;
case 0x20: timerInMode[1] = InMode.CNT; break;
case 0x40: timerInMode[1] = InMode.TimerAUnderflow; break;
case 0x60: timerInMode[1] = InMode.TimerAUnderflowCNT; break;
}
alarmSelect = ((val & 0x80) != 0);
break;
}
}
// ------------------------------------
}
}