578 lines
12 KiB
C#
578 lines
12 KiB
C#
using System;
|
|
using BizHawk.Common;
|
|
|
|
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<bool> 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<bool[]> keyboard, Func<bool[]> joysticks) : this(todNum, todDen)
|
|
{
|
|
_port = new JoystickKeyboardPort(joysticks, keyboard);
|
|
}
|
|
|
|
public Cia(int todNum, int todDen, Func<int> 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);
|
|
}
|
|
}
|
|
}
|