449 lines
8.9 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|