451 lines
12 KiB
C#
451 lines
12 KiB
C#
using System;
|
|
using BizHawk.Common;
|
|
|
|
namespace BizHawk.Emulation.Cores.Computers.Commodore64.MOS
|
|
{
|
|
public sealed partial class Via
|
|
{
|
|
private const int PCR_INT_CONTROL_NEGATIVE_EDGE = 0x00;
|
|
private const int PCR_INT_CONTROL_POSITIVE_EDGE = 0x01;
|
|
private const int PCR_CONTROL_INPUT_NEGATIVE_ACTIVE_EDGE = 0x00;
|
|
private const int PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_NEGATIVE_EDGE = 0x02;
|
|
private const int PCR_CONTROL_INPUT_POSITIVE_ACTIVE_EDGE = 0x04;
|
|
private const int PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_POSITIVE_EDGE = 0x06;
|
|
private const int PCR_CONTROL_HANDSHAKE_OUTPUT = 0x08;
|
|
private const int PCR_CONTROL_PULSE_OUTPUT = 0x0A;
|
|
private const int PCR_CONTROL_LOW_OUTPUT = 0x0C;
|
|
private const int PCR_CONTROL_HIGH_OUTPUT = 0x0E;
|
|
private const int ACR_SR_CONTROL_DISABLED = 0x00;
|
|
private const int ACR_SR_CONTROL_SHIFT_IN_T2_ONCE = 0x04;
|
|
private const int ACR_SR_CONTROL_SHIFT_IN_PHI2 = 0x08;
|
|
private const int ACR_SR_CONTROL_SHIFT_IN_CLOCK = 0x0C;
|
|
private const int ACR_SR_CONTROL_SHIFT_OUT_T2 = 0x10;
|
|
private const int ACR_SR_CONTROL_SHIFT_OUT_T2_ONCE = 0x14;
|
|
private const int ACR_SR_CONTROL_SHIFT_OUT_PHI2 = 0x18;
|
|
private const int ACR_SR_CONTROL_SHIFT_OUT_CLOCK = 0x1C;
|
|
private const int ACR_T2_CONTROL_TIMED = 0x00;
|
|
private const int ACR_T2_CONTROL_COUNT_ON_PB6 = 0x20;
|
|
private const int ACR_T1_CONTROL_INTERRUPT_ON_LOAD = 0x00;
|
|
private const int ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS = 0x40;
|
|
private const int ACR_T1_CONTROL_INTERRUPT_ON_LOAD_AND_ONESHOT_PB7 = 0x80;
|
|
private const int ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS_AND_OUTPUT_ON_PB7 = 0xC0;
|
|
private const int ACR_T1_CONTROL_INTERRUPT_ON_LOAD_AND_PULSE_PB7 = 0x80;
|
|
|
|
private int _pra;
|
|
private int _ddra;
|
|
private int _prb;
|
|
private int _ddrb;
|
|
private int _t1C;
|
|
private int _t1L;
|
|
private int _t2C;
|
|
private int _t2L;
|
|
private int _sr;
|
|
private int _acr;
|
|
private int _pcr;
|
|
private int _ifr;
|
|
private int _ier;
|
|
private readonly IPort _port;
|
|
|
|
private int _paLatch;
|
|
private int _pbLatch;
|
|
|
|
private int _pcrCa1IntControl;
|
|
private int _pcrCa2Control;
|
|
private int _pcrCb1IntControl;
|
|
private int _pcrCb2Control;
|
|
private bool _acrPaLatchEnable;
|
|
private bool _acrPbLatchEnable;
|
|
private int _acrSrControl;
|
|
private int _acrT1Control;
|
|
private int _acrT2Control;
|
|
private int _srCount;
|
|
|
|
private bool _ca1L;
|
|
private bool _ca2L;
|
|
private bool _cb1L;
|
|
private bool _cb2L;
|
|
private bool _pb6L;
|
|
|
|
private bool _resetCa2NextClock;
|
|
private bool _resetCb2NextClock;
|
|
private bool _resetPb7NextClock;
|
|
private bool _setPb7NextClock;
|
|
|
|
private bool _handshakeCa2NextClock;
|
|
private bool _handshakeCb2NextClock;
|
|
|
|
public bool Ca1;
|
|
public bool Ca2;
|
|
public bool Cb1;
|
|
public bool Cb2;
|
|
private bool _pb6;
|
|
|
|
private int _interruptNextClock;
|
|
private bool _t1CLoaded;
|
|
private bool _t2CLoaded;
|
|
private int _t1Delayed;
|
|
private int _t2Delayed;
|
|
|
|
public Via()
|
|
{
|
|
_port = new DisconnectedPort();
|
|
}
|
|
|
|
public Via(Func<int> readPrA, Func<int> readPrB)
|
|
{
|
|
_port = new DriverPort(readPrA, readPrB);
|
|
}
|
|
|
|
public Via(Func<bool> readClock, Func<bool> readData, Func<bool> readAtn, int driveNumber)
|
|
{
|
|
_port = new IecPort(readClock, readData, readAtn, driveNumber);
|
|
_ca1L = true;
|
|
}
|
|
|
|
public bool Irq => (_ifr & 0x80) == 0;
|
|
|
|
public void HardReset()
|
|
{
|
|
_pra = 0;
|
|
_prb = 0;
|
|
_ddra = 0;
|
|
_ddrb = 0;
|
|
_t1C = 0xFFFF;
|
|
_t1L = 0xFFFF;
|
|
_t2C = 0xFFFF;
|
|
_t2L = 0xFFFF;
|
|
_sr = 0xFF;
|
|
_acr = 0;
|
|
_pcr = 0;
|
|
_ifr = 0;
|
|
_ier = 0;
|
|
_paLatch = 0;
|
|
_pbLatch = 0;
|
|
_pcrCa1IntControl = 0;
|
|
_pcrCa2Control = 0;
|
|
_pcrCb1IntControl = 0;
|
|
_pcrCb2Control = 0;
|
|
_acrPaLatchEnable = false;
|
|
_acrPbLatchEnable = false;
|
|
_acrSrControl = 0;
|
|
_acrT1Control = 0;
|
|
_acrT2Control = 0;
|
|
_ca1L = true;
|
|
_cb1L = true;
|
|
Ca1 = true;
|
|
Ca2 = true;
|
|
Cb1 = true;
|
|
Cb2 = true;
|
|
_srCount = 0;
|
|
|
|
_pb6L = true;
|
|
_pb6 = true;
|
|
_resetCa2NextClock = false;
|
|
_resetCb2NextClock = false;
|
|
_handshakeCa2NextClock = false;
|
|
_handshakeCb2NextClock = false;
|
|
_interruptNextClock = 0;
|
|
_t1CLoaded = false;
|
|
_t2CLoaded = false;
|
|
_resetPb7NextClock = false;
|
|
_setPb7NextClock = false;
|
|
}
|
|
|
|
public void ExecutePhase()
|
|
{
|
|
var _shiftIn = false;
|
|
var _shiftOut = false;
|
|
|
|
// Process delayed interrupts
|
|
_ifr |= _interruptNextClock;
|
|
_interruptNextClock = 0;
|
|
|
|
// Process 'pulse' and 'handshake' outputs on PB7, CA2 and CB2
|
|
if (_resetCa2NextClock)
|
|
{
|
|
Ca2 = true;
|
|
_resetCa2NextClock = false;
|
|
}
|
|
else if (_handshakeCa2NextClock)
|
|
{
|
|
Ca2 = false;
|
|
_resetCa2NextClock = _pcrCa2Control == PCR_CONTROL_PULSE_OUTPUT;
|
|
_handshakeCa2NextClock = false;
|
|
}
|
|
|
|
if (_resetCb2NextClock)
|
|
{
|
|
Cb2 = true;
|
|
_resetCb2NextClock = false;
|
|
}
|
|
else if (_handshakeCb2NextClock)
|
|
{
|
|
Cb2 = false;
|
|
_resetCb2NextClock = _pcrCb2Control == PCR_CONTROL_PULSE_OUTPUT;
|
|
_handshakeCb2NextClock = false;
|
|
}
|
|
|
|
if (_resetPb7NextClock)
|
|
{
|
|
_prb &= 0x7F;
|
|
_resetPb7NextClock = false;
|
|
}
|
|
else if (_setPb7NextClock)
|
|
{
|
|
_prb |= 0x80;
|
|
_setPb7NextClock = false;
|
|
}
|
|
|
|
// Count timers
|
|
if (_t1Delayed > 0)
|
|
{
|
|
_t1Delayed--;
|
|
}
|
|
else
|
|
{
|
|
_t1C--;
|
|
if (_t1C == 0)
|
|
{
|
|
switch (_acrT1Control)
|
|
{
|
|
case ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS_AND_OUTPUT_ON_PB7:
|
|
_prb ^= 0x80;
|
|
break;
|
|
case ACR_T1_CONTROL_INTERRUPT_ON_LOAD_AND_PULSE_PB7:
|
|
_prb |= 0x80;
|
|
break;
|
|
}
|
|
}
|
|
else if (_t1C < 0)
|
|
{
|
|
if (_t1CLoaded)
|
|
{
|
|
_interruptNextClock |= 0x40;
|
|
_t1CLoaded = false;
|
|
}
|
|
|
|
switch (_acrT1Control)
|
|
{
|
|
case ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS:
|
|
case ACR_T1_CONTROL_CONTINUOUS_INTERRUPTS_AND_OUTPUT_ON_PB7:
|
|
_t1C = _t1L;
|
|
_t1CLoaded = true;
|
|
break;
|
|
}
|
|
|
|
_t1C &= 0xFFFF;
|
|
}
|
|
}
|
|
|
|
if (_t2Delayed > 0)
|
|
{
|
|
_t2Delayed--;
|
|
}
|
|
else
|
|
{
|
|
switch (_acrT2Control)
|
|
{
|
|
case ACR_T2_CONTROL_TIMED:
|
|
_t2C--;
|
|
if (_t2C < 0)
|
|
{
|
|
if (_t2CLoaded)
|
|
{
|
|
_interruptNextClock |= 0x20;
|
|
_t2CLoaded = false;
|
|
}
|
|
_t2C = _t2L;
|
|
}
|
|
break;
|
|
case ACR_T2_CONTROL_COUNT_ON_PB6:
|
|
_pb6L = _pb6;
|
|
_pb6 = (_port.ReadExternalPrb() & 0x40) != 0;
|
|
if (!_pb6 && _pb6L)
|
|
{
|
|
_t2C--;
|
|
if (_t2C == 0)
|
|
_ifr |= 0x20;
|
|
_t2C &= 0xFFFF;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Process CA2
|
|
switch (_pcrCa2Control)
|
|
{
|
|
case PCR_CONTROL_INPUT_NEGATIVE_ACTIVE_EDGE:
|
|
case PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_NEGATIVE_EDGE:
|
|
if (_ca2L && !Ca2)
|
|
_ifr |= 0x01;
|
|
break;
|
|
case PCR_CONTROL_INPUT_POSITIVE_ACTIVE_EDGE:
|
|
case PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_POSITIVE_EDGE:
|
|
if (!_ca2L && Ca2)
|
|
_ifr |= 0x01;
|
|
break;
|
|
case PCR_CONTROL_HANDSHAKE_OUTPUT:
|
|
if (_ca1L && !Ca1)
|
|
{
|
|
Ca2 = true;
|
|
_ifr |= 0x01;
|
|
}
|
|
break;
|
|
case PCR_CONTROL_PULSE_OUTPUT:
|
|
break;
|
|
case PCR_CONTROL_LOW_OUTPUT:
|
|
Ca2 = false;
|
|
break;
|
|
case PCR_CONTROL_HIGH_OUTPUT:
|
|
Ca2 = true;
|
|
break;
|
|
}
|
|
|
|
// Process CB2
|
|
switch (_pcrCb2Control)
|
|
{
|
|
case PCR_CONTROL_INPUT_NEGATIVE_ACTIVE_EDGE:
|
|
case PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_NEGATIVE_EDGE:
|
|
if (_cb2L && !Cb2)
|
|
_ifr |= 0x08;
|
|
break;
|
|
case PCR_CONTROL_INPUT_POSITIVE_ACTIVE_EDGE:
|
|
case PCR_CONTROL_INDEPENDENT_INTERRUPT_INPUT_POSITIVE_EDGE:
|
|
if (!_cb2L && Cb2)
|
|
_ifr |= 0x08;
|
|
break;
|
|
case PCR_CONTROL_HANDSHAKE_OUTPUT:
|
|
if (_cb1L && !Cb1)
|
|
{
|
|
Cb2 = true;
|
|
_ifr |= 0x08;
|
|
}
|
|
break;
|
|
case PCR_CONTROL_PULSE_OUTPUT:
|
|
break;
|
|
case PCR_CONTROL_LOW_OUTPUT:
|
|
Cb2 = false;
|
|
break;
|
|
case PCR_CONTROL_HIGH_OUTPUT:
|
|
Cb2 = true;
|
|
break;
|
|
}
|
|
|
|
// interrupt generation
|
|
|
|
if (_acrSrControl == ACR_SR_CONTROL_DISABLED)
|
|
{
|
|
_ifr &= 0xFB;
|
|
_srCount = 0;
|
|
}
|
|
|
|
/*
|
|
As long as the CA1 interrupt flag is set, the data on the peripheral pins can change
|
|
without affecting the data in the latches. This input latching can be used with any of the CA2
|
|
input or output modes.
|
|
It is important to note that on the PA port, the processor always reads the data on the
|
|
peripheral pins (as reflected in the latches). For output pins, the processor still reads the
|
|
latches. This may or may not reflect the data currently in the ORA. Proper system operation
|
|
requires careful planning on the part of the system designer if input latching is combined
|
|
with output pins on the peripheral ports.
|
|
*/
|
|
|
|
if ((_pcrCa1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Ca1 && !_ca1L) ||
|
|
(_pcrCa1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Ca1 && _ca1L))
|
|
{
|
|
if (_acrPaLatchEnable && (_ifr & 0x02) == 0)
|
|
_paLatch = _port.ReadExternalPra();
|
|
_ifr |= 0x02;
|
|
}
|
|
|
|
/*
|
|
Input latching on the PB port is controlled in the same manner as that described for the PA port.
|
|
However, with the peripheral B port the input latch will store either the voltage on the pin or the contents
|
|
of the Output Register (ORB) depending on whether the pin is programmed to act as an input or an
|
|
output. As with the PA port, the processor always reads the input latches.
|
|
*/
|
|
|
|
if ((_pcrCb1IntControl == PCR_INT_CONTROL_POSITIVE_EDGE && Cb1 && !_cb1L) ||
|
|
(_pcrCb1IntControl == PCR_INT_CONTROL_NEGATIVE_EDGE && !Cb1 && _cb1L))
|
|
{
|
|
if (_acrPbLatchEnable && (_ifr & 0x10) == 0)
|
|
_pbLatch = _port.ReadPrb(_prb, _ddrb);
|
|
if (_acrSrControl == ACR_SR_CONTROL_DISABLED)
|
|
_shiftIn = true;
|
|
_ifr |= 0x10;
|
|
}
|
|
|
|
if (_shiftIn)
|
|
{
|
|
_sr <<= 1;
|
|
_sr |= Cb2 ? 1 : 0;
|
|
}
|
|
|
|
if ((_ifr & _ier & 0x7F) != 0)
|
|
_ifr |= 0x80;
|
|
else
|
|
_ifr &= 0x7F;
|
|
|
|
_ca1L = Ca1;
|
|
_ca2L = Ca2;
|
|
_cb1L = Cb1;
|
|
_cb2L = Cb2;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("PortOutputA", ref _pra);
|
|
ser.Sync("PortDirectionA", ref _ddra);
|
|
ser.Sync("PortOutputB", ref _prb);
|
|
ser.Sync("PortDirectionB", ref _ddrb);
|
|
ser.Sync("Timer1Counter", ref _t1C);
|
|
ser.Sync("Timer1Latch", ref _t1L);
|
|
ser.Sync("Timer2Counter", ref _t2C);
|
|
ser.Sync("Timer2Latch", ref _t2L);
|
|
ser.Sync("ShiftRegister", ref _sr);
|
|
ser.Sync("AuxiliaryControlRegister", ref _acr);
|
|
ser.Sync("PeripheralControlRegister", ref _pcr);
|
|
ser.Sync("InterruptFlagRegister", ref _ifr);
|
|
ser.Sync("InterruptEnableRegister", ref _ier);
|
|
|
|
ser.BeginSection("Port");
|
|
_port.SyncState(ser);
|
|
ser.EndSection();
|
|
|
|
ser.Sync("PortLatchA", ref _paLatch);
|
|
ser.Sync("PortLatchB", ref _pbLatch);
|
|
ser.Sync("CA1InterruptControl", ref _pcrCa1IntControl);
|
|
ser.Sync("CA2Control", ref _pcrCa2Control);
|
|
ser.Sync("CB1InterruptControl", ref _pcrCb1IntControl);
|
|
ser.Sync("CB2Control", ref _pcrCb2Control);
|
|
ser.Sync("PortLatchEnableA", ref _acrPaLatchEnable);
|
|
ser.Sync("PortLatchEnableB", ref _acrPbLatchEnable);
|
|
ser.Sync("ShiftRegisterControl", ref _acrSrControl);
|
|
ser.Sync("Timer1Control", ref _acrT1Control);
|
|
ser.Sync("Timer2Control", ref _acrT2Control);
|
|
ser.Sync("PreviousCA1", ref _ca1L);
|
|
ser.Sync("PreviousCA2", ref _ca2L);
|
|
ser.Sync("PreviousCB1", ref _cb1L);
|
|
ser.Sync("PreviousCB2", ref _cb2L);
|
|
ser.Sync("PreviousPB6", ref _pb6L);
|
|
ser.Sync("ResetCa2NextClock", ref _resetCa2NextClock);
|
|
ser.Sync("ResetCb2NextClock", ref _resetCb2NextClock);
|
|
ser.Sync("HandshakeCa2NextClock", ref _handshakeCa2NextClock);
|
|
ser.Sync("HandshakeCb2NextClock", ref _handshakeCb2NextClock);
|
|
ser.Sync("CA1", ref Ca1);
|
|
ser.Sync("CA2", ref Ca2);
|
|
ser.Sync("CB1", ref Cb1);
|
|
ser.Sync("CB2", ref Cb2);
|
|
ser.Sync("PB6", ref _pb6);
|
|
ser.Sync("InterruptNextClock", ref _interruptNextClock);
|
|
ser.Sync("T1Loaded", ref _t1CLoaded);
|
|
ser.Sync("T2Loaded", ref _t2CLoaded);
|
|
ser.Sync("T1Delayed", ref _t1Delayed);
|
|
ser.Sync("T2Delayed", ref _t2Delayed);
|
|
ser.Sync("ResetPb7NextClock", ref _resetPb7NextClock);
|
|
ser.Sync("SetPb7NextClock", ref _setPb7NextClock);
|
|
ser.Sync("ShiftRegisterCount", ref _srCount);
|
|
}
|
|
}
|
|
}
|