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(Region region) { a = new CiaTimer(serialPortA, underFlowA); b = new CiaTimer(serialPortB, underFlowB); portA = new LatchedPort(); portB = new LatchedPort(); switch (region) { case Region.NTSC: tod_period = 14318181 / 140; break; case Region.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); } } }