BizHawk/BizHawk.Emulation.Cores/Computers/Commodore64/MOS/MOS6526-2.cs

449 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BizHawk.Common;
#pragma warning disable 0169
#pragma warning disable 0414
#pragma warning disable 0649
// thanks to these fine folks for their research on this buggy as hell chip:
// Simon White (s_a_white@email.com)
// Antti S. Lankila "alankila"
// Andreas Boose (viceteam@t-online.de)
// Alexander Bluhm (mam96ehy@studserv.uni-leipzig.de)
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
{
sealed public partial class MOS6526_2
{
sealed private class CiaTimer
{
public const int TIMER_CR_START = 0x01;
public const int TIMER_STEP = 0x04;
public const int TIMER_CR_ONESHOT = 0x08;
public const int TIMER_CR_FLOAD = 0x10;
public const int TIMER_PHI2IN = 0x20;
public const int TIMER_MASK = TIMER_CR_START | TIMER_CR_ONESHOT | TIMER_CR_FLOAD | TIMER_PHI2IN;
public const int TIMER_COUNT2 = 0x100;
public const int TIMER_COUNT3 = 0x200;
public const int TIMER_ONESHOT0 = 0x800;
public const int TIMER_ONESHOT = 0x80000;
public const int TIMER_LOAD1 = 0x1000;
public const int TIMER_LOAD = 0x100000;
public const int TIMER_OUT = 0x40000000 << 1;
const int WANTED = TIMER_CR_START | TIMER_PHI2IN | TIMER_COUNT2 | TIMER_COUNT3;
const int UNWANTED = TIMER_OUT | TIMER_CR_FLOAD | TIMER_LOAD1 | TIMER_LOAD;
const int UNWANTED1 = TIMER_CR_START | TIMER_PHI2IN;
const int UNWANTED2 = TIMER_CR_START | TIMER_STEP;
int adj;
bool toggle;
public int state;
int lastControlValue;
int timer;
int latch;
bool pbToggle;
int ciaEventPauseTime;
bool phi1tod;
bool phi1run;
int cycleSkippingEvent = -1;
int nextTick = 0;
Action serialPort;
Action underFlow;
public CiaTimer(Action serialPortCallback, Action underFlowCallback)
{
this.serialPort = serialPortCallback;
this.underFlow = underFlowCallback;
}
public void clock()
{
if (timer != 0 && (state & TIMER_COUNT3) != 0)
{
timer--;
}
adj = state & (TIMER_CR_START | TIMER_CR_ONESHOT | TIMER_PHI2IN);
if ((state & (TIMER_CR_START | TIMER_PHI2IN)) == (TIMER_CR_START | TIMER_PHI2IN))
{
adj |= TIMER_COUNT2;
}
if ((state & TIMER_COUNT2) != 0 || (state & (TIMER_STEP | TIMER_CR_START)) == (TIMER_STEP | TIMER_CR_START))
{
adj |= TIMER_COUNT3;
}
adj |= (state & (TIMER_CR_FLOAD | TIMER_CR_ONESHOT | TIMER_LOAD1 | TIMER_ONESHOT0)) << 8;
state = adj;
if (timer == 0 && (state & TIMER_COUNT3) != 0)
{
state |= TIMER_LOAD | TIMER_OUT;
if ((state & (TIMER_ONESHOT | TIMER_ONESHOT0)) != 0)
{
state &= ~(TIMER_CR_START | TIMER_COUNT2);
}
toggle = (lastControlValue & 0x06) == 0x06;
pbToggle = toggle && !pbToggle;
serialPort();
underFlow();
}
if ((state & TIMER_LOAD) != 0)
{
timer = latch;
state &= ~TIMER_COUNT3;
}
}
public bool getPbToggle()
{
return pbToggle;
}
public int getTimer()
{
return timer;
}
public void reschedule()
{
if ((state & UNWANTED) != 0)
{
return;
}
if ((state & TIMER_COUNT3) != 0)
{
if ((timer & 0xFFFF) > 2 && (state & UNWANTED) == WANTED)
{
ciaEventPauseTime = 1;
cycleSkippingEvent = (timer - 1) & 0xFFFF;
return;
}
nextTick = 1;
return;
}
else
{
if ((state & UNWANTED1) == UNWANTED1 || (state & UNWANTED2) == UNWANTED2)
{
nextTick = 1;
return;
}
ciaEventPauseTime = -1;
return;
}
}
public void reset()
{
timer = 0xFFFF;
latch = 0xFFFF;
pbToggle = false;
state = 0;
ciaEventPauseTime = 0;
cycleSkippingEvent = -1;
}
public void setControlRegister(int cr)
{
state &= ~TIMER_MASK;
state |= cr & TIMER_MASK ^ TIMER_PHI2IN;
lastControlValue = cr;
}
public void setLatchLow(int low)
{
latch = ((latch & 0xFF00) | (low & 0xFF));
if ((state & TIMER_LOAD) != 0)
{
timer = ((timer & 0xFF00) | (low & 0xFF));
}
}
public void setLatchHigh(int high)
{
latch = ((latch & 0xFF) | ((high & 0xFF) << 8));
if ((state & TIMER_LOAD) != 0 || (state & TIMER_CR_START) == 0)
{
timer = latch;
}
}
public void setPbToggle(bool st)
{
pbToggle = st;
}
public void SyncState(Serializer ser)
{
SaveState.SyncObject(ser, this);
}
}
const int INT_NONE = 0x00;
const int INT_UNDERFLOW_A = 0x01;
const int INT_UNDERFLOW_B = 0x02;
const int INT_ALARM = 0x04;
const int INT_SP = 0x08;
const int INT_FLAG = 0x10;
int pra;
int prb;
int ddra;
int ddrb;
int ta;
int tb;
int tod_ten;
int tod_sec;
int tod_min;
int tod_hr;
int sdr;
int icr;
int idr;
int cra;
int crb;
int sdr_out;
bool sdr_buffered;
int sdr_count;
bool tod_latched;
bool tod_stopped;
int[] tod_clock = new int[4];
int[] tod_alarm = new int[4];
int[] tod_latch = new int[4];
int tod_cycles = -1;
int tod_period = -1;
bool postTimerBEvent;
int idr_old;
bool int_delayed;
int bcd_internal;
CiaTimer a;
CiaTimer b;
LatchedPort portA;
LatchedPort portB;
public MOS6526_2(Common.DisplayType region)
{
a = new CiaTimer(serialPortA, underFlowA);
b = new CiaTimer(serialPortB, underFlowB);
portA = new LatchedPort();
portB = new LatchedPort();
switch (region)
{
case Common.DisplayType.NTSC:
tod_period = 14318181 / 140;
break;
case Common.DisplayType.PAL:
tod_period = 17734472 / 180;
break;
}
}
int bcdToByte(int input)
{
return 10 * ((input & 0xF0) >> 4) + (input & 0x0F);
}
int byteToBcd(int input)
{
return (((input / 10) << 4) + (input % 10)) & 0xFF;
}
int int_clear()
{
int_delayed = false;
idr_old = idr;
idr = 0;
return idr_old;
}
void int_clearEnabled(int i)
{
icr &= ~i;
}
void int_reset()
{
int_delayed = false;
}
void int_set(int i)
{
idr |= i;
if ((icr & idr) != 0 && (idr & 0x80) == 0)
{
int_delayed = true;
}
}
void int_setEnabled(int i)
{
icr |= i & ~0x80;
}
void proc_a()
{
if (int_delayed)
{
int_delayed = false;
idr |= 0x80;
}
if (postTimerBEvent)
{
postTimerBEvent = false;
b.state |= CiaTimer.TIMER_STEP;
}
a.clock();
}
void proc_b()
{
b.clock();
}
void reset()
{
a.reset();
b.reset();
sdr_out = 0;
sdr_count = 0;
sdr_buffered = false;
icr = 0;
idr = 0;
idr_old = 0;
int_reset();
portA.Latch = 0xFF;
portB.Latch = 0xFF;
portA.Direction = 0xFF;
portB.Direction = 0xFF;
PortAMask = 0xFF;
PortBMask = 0xFF;
}
void serialPortA()
{
if ((cra & 0x40) != 0)
{
if (sdr_count != 0)
{
if (--sdr_count == 0)
{
triggerInterruptSP();
}
}
if (sdr_count == 0 && sdr_buffered)
{
sdr_out = sdr;
sdr_buffered = false;
sdr_count = 16;
}
}
}
void serialPortB()
{
// nop
}
void tod()
{
tod_cycles += tod_period;
if (!tod_stopped)
{
int todpos = 0;
int t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 10);
if (t >= 10)
{
t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 60);
if (t >= 60)
{
t = bcdToByte(tod_clock[todpos]) + 1;
tod_clock[todpos++] = byteToBcd(t % 60);
if (t >= 60)
{
int pm = tod_clock[todpos] & 0x80;
t = bcdToByte(tod_clock[todpos] & 0x1F);
if (t == 0x11)
{
pm ^= 0x80;
}
if (t == 0x12)
{
t = 1;
}
else if (++t == 10)
{
t = 0x10;
}
t &= 0x1F;
tod_clock[todpos] = t | pm;
}
}
}
if (tod_clock[0] == tod_alarm[0] &&
tod_clock[1] == tod_alarm[1] &&
tod_clock[2] == tod_alarm[2] &&
tod_clock[3] == tod_alarm[3])
{
triggerInterruptAlarm();
}
}
}
void triggerInterruptAlarm()
{
int_set(INT_ALARM);
}
void triggerInterruptSP()
{
int_set(INT_SP);
}
void triggerInterruptUnderFlowA()
{
int_set(INT_UNDERFLOW_A);
}
void triggerInterruptUnderFlowB()
{
int_set(INT_UNDERFLOW_B);
}
void underFlowA()
{
triggerInterruptUnderFlowA();
if ((crb & 0x41) == 0x41)
{
if ((b.state & CiaTimer.TIMER_CR_START) != 0)
{
postTimerBEvent = true;
}
}
}
void underFlowB()
{
triggerInterruptUnderFlowB();
}
public void SyncState(Serializer ser)
{
SaveState.SyncObject(ser, this);
}
}
}