584 lines
12 KiB
C#
584 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/
|
|
*/
|
|
|
|
|
|
// operation of the interrupt at the serial data port occurs in 2 instances:
|
|
// 1. Being in output mode and having a complete transfer as defined by clocking of timer A
|
|
// 2. Being in input mode and receiving 8 clocks from the /CNT pin
|
|
// This is TODO
|
|
|
|
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(nameof(DelayedInterrupts), ref DelayedInterrupts);
|
|
ser.Sync(nameof(_pra), ref _pra);
|
|
ser.Sync(nameof(_prb), ref _prb);
|
|
ser.Sync(nameof(_ddra), ref _ddra);
|
|
ser.Sync(nameof(_ddrb), ref _ddrb);
|
|
ser.Sync(nameof(_ta), ref _ta);
|
|
ser.Sync(nameof(_tb), ref _tb);
|
|
ser.Sync(nameof(_latcha), ref _latcha);
|
|
ser.Sync(nameof(_latchb), ref _latchb);
|
|
ser.Sync(nameof(_tod10Ths), ref _tod10Ths);
|
|
ser.Sync(nameof(_todSec), ref _todSec);
|
|
ser.Sync(nameof(_todMin), ref _todMin);
|
|
ser.Sync(nameof(_todHr), ref _todHr);
|
|
ser.Sync(nameof(_latch10Ths), ref _latch10Ths);
|
|
ser.Sync(nameof(_latchSec), ref _latchSec);
|
|
ser.Sync(nameof(_latchMin), ref _latchMin);
|
|
ser.Sync(nameof(_latchHr), ref _latchHr);
|
|
ser.Sync(nameof(_alm10Ths), ref _alm10Ths);
|
|
ser.Sync(nameof(_almSec), ref _almSec);
|
|
ser.Sync(nameof(_almMin), ref _almMin);
|
|
ser.Sync(nameof(_almHr), ref _almHr);
|
|
ser.Sync(nameof(_sdr), ref _sdr);
|
|
ser.Sync(nameof(_icr), ref _icr);
|
|
ser.Sync(nameof(_cra), ref _cra);
|
|
ser.Sync(nameof(_crb), ref _crb);
|
|
ser.Sync(nameof(_intMask), ref _intMask);
|
|
ser.Sync(nameof(_todLatch), ref _todLatch);
|
|
ser.Sync(nameof(_taCntPhi2), ref _taCntPhi2);
|
|
ser.Sync(nameof(_taCntCnt), ref _taCntCnt);
|
|
ser.Sync(nameof(_tbCntPhi2), ref _tbCntPhi2);
|
|
ser.Sync(nameof(_tbCntTa), ref _tbCntTa);
|
|
ser.Sync(nameof(_tbCntCnt), ref _tbCntCnt);
|
|
ser.Sync(nameof(_taIrqNextCycle), ref _taIrqNextCycle);
|
|
ser.Sync(nameof(_taPrb6NegativeNextCycle), ref _taPrb6NegativeNextCycle);
|
|
ser.Sync(nameof(_tbIrqNextCycle), ref _tbIrqNextCycle);
|
|
ser.Sync(nameof(_tbPrb7NegativeNextCycle), ref _tbPrb7NegativeNextCycle);
|
|
ser.Sync(nameof(_hasNewCra), ref _hasNewCra);
|
|
ser.Sync(nameof(_hasNewCrb), ref _hasNewCrb);
|
|
ser.SyncEnum(nameof(_taState), ref _taState);
|
|
ser.SyncEnum(nameof(_tbState), ref _tbState);
|
|
ser.Sync(nameof(_newCra), ref _newCra);
|
|
ser.Sync(nameof(_newCrb), ref _newCrb);
|
|
ser.Sync(nameof(_flagLatch), ref _flagLatch);
|
|
|
|
ser.Sync(nameof(_todCounter), ref _todCounter);
|
|
}
|
|
}
|
|
}
|