using BizHawk.Common; using System; namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS { public sealed partial class Cia { /* Commodore CIA 6526 core. Many thanks to: - 6502.org for hosting the 6526 datasheet http://archive.6502.org/datasheets/mos_6526_cia.pdf - Christian Bauer for information on the delayed interrupt mechanism on the 6526 http://frodo.cebix.net/ */ private enum TimerState { Stop = 0, WaitThenCount = 1, LoadThenStop = 2, LoadThenCount = 3, LoadThenWaitThenCount = 4, Count = 5, CountThenStop = 6 } public Func ReadFlag = () => true; public bool DelayedInterrupts = true; private int _pra; private int _prb; private int _ddra; private int _ddrb; private int _ta; private int _tb; private int _latcha; private int _latchb; private int _tod10Ths; private int _todSec; private int _todMin; private int _todHr; private int _latch10Ths; private int _latchSec; private int _latchMin; private int _latchHr; private int _alm10Ths; private int _almSec; private int _almMin; private int _almHr; private int _sdr; private int _icr; private int _cra; private int _crb; private int _intMask; private bool _todLatch; private bool _taCntPhi2; private bool _taCntCnt; private bool _tbCntPhi2; private bool _tbCntTa; private bool _tbCntCnt; private bool _taIrqNextCycle; private bool _taPrb6NegativeNextCycle; private bool _tbIrqNextCycle; private bool _tbPrb7NegativeNextCycle; private bool _hasNewCra; private bool _hasNewCrb; private TimerState _taState; private TimerState _tbState; private int _newCra; private int _newCrb; private bool _flagLatch; private bool _flagInput; private bool _taUnderflow; private readonly IPort _port; private int _todlo; private int _todhi; private readonly int _todNum; private readonly int _todDen; private int _todCounter; private Cia(int todNum, int todDen) { _todNum = todNum; _todDen = todDen; } public Cia(int todNum, int todDen, Func keyboard, Func joysticks) : this(todNum, todDen) { _port = new JoystickKeyboardPort(joysticks, keyboard); } public Cia(int todNum, int todDen, Func readIec) : this(todNum, todDen) { _port = new IecPort(readIec); } public void HardReset() { _pra = 0; _prb = 0; _ddra = 0; _ddrb = 0; _ta = 0xFFFF; _tb = 0xFFFF; _latcha = 1; _latchb = 1; _tod10Ths = 0; _todSec = 0; _todMin = 0; _todHr = 0; _alm10Ths = 0; _almSec = 0; _almMin = 0; _almHr = 0; _sdr = 0; _icr = 0; _cra = 0; _crb = 0; _intMask = 0; _todLatch = false; _taCntPhi2 = false; _taCntCnt = false; _tbCntPhi2 = false; _tbCntTa = false; _tbCntCnt = false; _taIrqNextCycle = false; _tbIrqNextCycle = false; _taState = TimerState.Stop; _tbState = TimerState.Stop; } private void CheckIrqs() { if (_taIrqNextCycle) { _taIrqNextCycle = false; TriggerInterrupt(1); } if (_tbIrqNextCycle) { _tbIrqNextCycle = false; TriggerInterrupt(2); } } public void ExecutePhase() { _taUnderflow = false; if (DelayedInterrupts) { CheckIrqs(); } if (_taPrb6NegativeNextCycle) { _prb &= 0xBF; _taPrb6NegativeNextCycle = false; } if (_tbPrb7NegativeNextCycle) { _prb &= 0x7F; _tbPrb7NegativeNextCycle = false; } switch (_taState) { case TimerState.WaitThenCount: _taState = TimerState.Count; Ta_Idle(); break; case TimerState.Stop: Ta_Idle(); break; case TimerState.LoadThenStop: _taState = TimerState.Stop; _ta = _latcha; Ta_Idle(); break; case TimerState.LoadThenCount: _taState = TimerState.Count; _ta = _latcha; Ta_Idle(); break; case TimerState.LoadThenWaitThenCount: _taState = TimerState.WaitThenCount; if (_ta == 1) { Ta_Interrupt(); _taUnderflow = true; } else { _ta = _latcha; } Ta_Idle(); break; case TimerState.Count: Ta_Count(); break; case TimerState.CountThenStop: _taState = TimerState.Stop; Ta_Count(); break; } switch (_tbState) { case TimerState.WaitThenCount: _tbState = TimerState.Count; Tb_Idle(); break; case TimerState.Stop: Tb_Idle(); break; case TimerState.LoadThenStop: _tbState = TimerState.Stop; _tb = _latchb; Tb_Idle(); break; case TimerState.LoadThenCount: _tbState = TimerState.Count; _tb = _latchb; Tb_Idle(); break; case TimerState.LoadThenWaitThenCount: _tbState = TimerState.WaitThenCount; if (_tb == 1) { Tb_Interrupt(); } else { _tb = _latchb; } Tb_Idle(); break; case TimerState.Count: Tb_Count(); break; case TimerState.CountThenStop: _tbState = TimerState.Stop; Tb_Count(); break; } CountTod(); if (!_todLatch) { _latch10Ths = _tod10Ths; _latchSec = _todSec; _latchMin = _todMin; _latchHr = _todHr; } _flagInput = ReadFlag(); if (!_flagInput && _flagLatch) { TriggerInterrupt(16); } _flagLatch = _flagInput; if (!DelayedInterrupts) { CheckIrqs(); } if ((_cra & 0x02) != 0) _ddra |= 0x40; if ((_crb & 0x02) != 0) _ddrb |= 0x80; } private void Ta_Count() { if (_taCntPhi2) { if (_ta <= 0 || --_ta == 0) { if (_taState != TimerState.Stop) { Ta_Interrupt(); } _taUnderflow = true; } } Ta_Idle(); } private void Ta_Interrupt() { _ta = _latcha; _taIrqNextCycle = true; _icr |= 1; if ((_cra & 0x08) != 0) { _cra &= 0xFE; _newCra &= 0xFE; _taState = TimerState.LoadThenStop; } else { _taState = TimerState.LoadThenCount; } if ((_cra & 0x02) != 0) { if ((_cra & 0x04) != 0) { _taPrb6NegativeNextCycle = true; _prb |= 0x40; } else { _prb ^= 0x40; } _ddrb |= 0x40; } } private void Ta_Idle() { if (_hasNewCra) { switch (_taState) { case TimerState.Stop: case TimerState.LoadThenStop: if ((_newCra & 0x01) != 0) { _taState = (_newCra & 0x10) != 0 ? TimerState.LoadThenWaitThenCount : TimerState.WaitThenCount; } else { if ((_newCra & 0x10) != 0) { _taState = TimerState.LoadThenStop; } } break; case TimerState.Count: if ((_newCra & 0x01) != 0) { if ((_newCra & 0x10) != 0) { _taState = TimerState.LoadThenWaitThenCount; } } else { _taState = (_newCra & 0x10) != 0 ? TimerState.LoadThenStop : TimerState.CountThenStop; } break; case TimerState.LoadThenCount: case TimerState.WaitThenCount: if ((_newCra & 0x01) != 0) { if ((_newCra & 0x08) != 0) { _newCra &= 0xFE; _taState = TimerState.Stop; } else if ((_newCra & 0x10) != 0) { _taState = TimerState.LoadThenWaitThenCount; } } else { _taState = TimerState.Stop; } break; } _cra = _newCra & 0xEF; _hasNewCra = false; } } private void Tb_Count() { if (_tbCntPhi2 || (_tbCntTa && _taUnderflow)) { if (_tb <= 0 || --_tb == 0) { if (_tbState != TimerState.Stop) { Tb_Interrupt(); } } } Tb_Idle(); } private void Tb_Interrupt() { _tb = _latchb; _tbIrqNextCycle = true; _icr |= 2; if ((_crb & 0x08) != 0) { _crb &= 0xFE; _newCrb &= 0xFE; _tbState = TimerState.LoadThenStop; } else { _tbState = TimerState.LoadThenCount; } if ((_crb & 0x02) != 0) { if ((_crb & 0x04) != 0) { _tbPrb7NegativeNextCycle = true; _prb |= 0x80; } else { _prb ^= 0x80; } } } private void Tb_Idle() { if (_hasNewCrb) { switch (_tbState) { case TimerState.Stop: case TimerState.LoadThenStop: if ((_newCrb & 0x01) != 0) { _tbState = (_newCrb & 0x10) != 0 ? TimerState.LoadThenWaitThenCount : TimerState.WaitThenCount; } else { if ((_newCrb & 0x10) != 0) { _tbState = TimerState.LoadThenStop; } } break; case TimerState.Count: if ((_newCrb & 0x01) != 0) { if ((_newCrb & 0x10) != 0) { _tbState = TimerState.LoadThenWaitThenCount; } } else { _tbState = (_newCrb & 0x10) != 0 ? TimerState.LoadThenStop : TimerState.CountThenStop; } break; case TimerState.LoadThenCount: case TimerState.WaitThenCount: if ((_newCrb & 0x01) != 0) { if ((_newCrb & 0x08) != 0) { _newCrb &= 0xFE; _tbState = TimerState.Stop; } else if ((_newCrb & 0x10) != 0) { _tbState = TimerState.LoadThenWaitThenCount; } } else { _tbState = TimerState.Stop; } break; } _crb = _newCrb & 0xEF; _hasNewCrb = false; } } private void TriggerInterrupt(int bit) { _icr |= bit; if ((_intMask & bit) == 0) return; _icr |= 0x80; } public void SyncState(Serializer ser) { ser.Sync("DelayedInterrupts", ref DelayedInterrupts); ser.Sync("_pra", ref _pra); ser.Sync("_prb", ref _prb); ser.Sync("_ddra", ref _ddra); ser.Sync("_ddrb", ref _ddrb); ser.Sync("_ta", ref _ta); ser.Sync("_tb", ref _tb); ser.Sync("_latcha", ref _latcha); ser.Sync("_latchb", ref _latchb); ser.Sync("_tod10Ths", ref _tod10Ths); ser.Sync("_todSec", ref _todSec); ser.Sync("_todMin", ref _todMin); ser.Sync("_todHr", ref _todHr); ser.Sync("_latch10Ths", ref _latch10Ths); ser.Sync("_latchSec", ref _latchSec); ser.Sync("_latchMin", ref _latchMin); ser.Sync("_latchHr", ref _latchHr); ser.Sync("_alm10Ths", ref _alm10Ths); ser.Sync("_almSec", ref _almSec); ser.Sync("_almMin", ref _almMin); ser.Sync("_almHr", ref _almHr); ser.Sync("_sdr", ref _sdr); ser.Sync("_icr", ref _icr); ser.Sync("_cra", ref _cra); ser.Sync("_crb", ref _crb); ser.Sync("_intMask", ref _intMask); ser.Sync("_todLatch", ref _todLatch); ser.Sync("_taCntPhi2", ref _taCntPhi2); ser.Sync("_taCntCnt", ref _taCntCnt); ser.Sync("_tbCntPhi2", ref _tbCntPhi2); ser.Sync("_tbCntTa", ref _tbCntTa); ser.Sync("_tbCntCnt", ref _tbCntCnt); ser.Sync("_taIrqNextCycle", ref _taIrqNextCycle); ser.Sync("_taPrb6NegativeNextCycle", ref _taPrb6NegativeNextCycle); ser.Sync("_tbIrqNextCycle", ref _tbIrqNextCycle); ser.Sync("_tbPrb7NegativeNextCycle", ref _tbPrb7NegativeNextCycle); ser.Sync("_hasNewCra", ref _hasNewCra); ser.Sync("_hasNewCrb", ref _hasNewCrb); ser.SyncEnum("_taState", ref _taState); ser.SyncEnum("_tbState", ref _tbState); ser.Sync("_newCra", ref _newCra); ser.Sync("_newCrb", ref _newCrb); ser.Sync("_flagLatch", ref _flagLatch); ser.Sync("_todCounter", ref _todCounter); } } }