348 lines
12 KiB
C#
348 lines
12 KiB
C#
/*
|
|
* PIA.cs
|
|
*
|
|
* The Peripheral Interface Adapter (6532) device.
|
|
* a.k.a. RIOT (RAM I/O Timer?)
|
|
*
|
|
* Copyright © 2003, 2004, 2012 Mike Murphy
|
|
*
|
|
*/
|
|
using System;
|
|
|
|
namespace EMU7800.Core
|
|
{
|
|
public sealed class PIA : IDevice
|
|
{
|
|
readonly MachineBase M;
|
|
|
|
readonly byte[] RAM = new byte[0x80];
|
|
|
|
ulong TimerTarget;
|
|
int TimerShift;
|
|
bool IRQEnabled, IRQTriggered;
|
|
|
|
public byte DDRA { get; private set; }
|
|
public byte DDRB { get; private set; }
|
|
|
|
public byte WrittenPortA { get; private set; }
|
|
public byte WrittenPortB { get; private set; }
|
|
|
|
#region IDevice Members
|
|
|
|
public void Reset()
|
|
{
|
|
// Some games will loop/hang on $0284 if these are initialized to zero
|
|
TimerShift = 10;
|
|
TimerTarget = M.CPU.Clock + (ulong)(0xff << TimerShift);
|
|
|
|
IRQEnabled = false;
|
|
IRQTriggered = false;
|
|
|
|
DDRA = 0;
|
|
|
|
Log("{0} reset", this);
|
|
}
|
|
|
|
public byte this[ushort addr]
|
|
{
|
|
get { return peek(addr); }
|
|
set { poke(addr, value); }
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override string ToString()
|
|
{
|
|
return "PIA/RIOT M6532";
|
|
}
|
|
|
|
#region Constructors
|
|
|
|
private PIA()
|
|
{
|
|
}
|
|
|
|
public PIA(MachineBase m)
|
|
{
|
|
if (m == null)
|
|
throw new ArgumentNullException("m");
|
|
M = m;
|
|
}
|
|
|
|
#endregion
|
|
|
|
byte peek(ushort addr)
|
|
{
|
|
if ((addr & 0x200) == 0)
|
|
{
|
|
return RAM[addr & 0x7f];
|
|
}
|
|
|
|
switch ((byte)(addr & 7))
|
|
{
|
|
case 0: // SWCHA: Controllers
|
|
return ReadPortA();
|
|
case 1: // SWCHA DDR: 0=input, 1=output
|
|
return DDRA;
|
|
case 2: // SWCHB: Console switches (on 7800, PB2 & PB4 are used)
|
|
return ReadPortB();
|
|
case 3: // SWCHB DDR: 0=input, 1=output
|
|
return 0;
|
|
case 4: // INTIM
|
|
case 6:
|
|
return ReadTimerRegister();
|
|
case 5: // INTFLG
|
|
case 7:
|
|
return ReadInterruptFlag();
|
|
default:
|
|
LogDebug("PIA: Unhandled peek ${0:x4}, PC=${1:x4}", addr, M.CPU.PC);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void poke(ushort addr, byte data)
|
|
{
|
|
if ((addr & 0x200) == 0)
|
|
{
|
|
RAM[addr & 0x7f] = data;
|
|
return;
|
|
}
|
|
|
|
// A2 Distinguishes I/O registers from the Timer
|
|
if ((addr & 0x04) != 0)
|
|
{
|
|
if ((addr & 0x10) != 0)
|
|
{
|
|
IRQEnabled = (addr & 0x08) != 0;
|
|
SetTimerRegister(data, addr & 3);
|
|
}
|
|
else
|
|
{
|
|
LogDebug("PIA: Timer: Unhandled poke ${0:x4} w/${1:x2}, PC=${2:x4}", addr, data, M.CPU.PC);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch ((byte)(addr & 3))
|
|
{
|
|
case 0: // SWCHA: Port A
|
|
WritePortA(data);
|
|
break;
|
|
case 1: // SWACNT: Port A DDR
|
|
DDRA = data;
|
|
break;
|
|
case 2: // SWCHB: Port B
|
|
WritePortB(data);
|
|
break;
|
|
case 3: // SWBCNT: Port B DDR
|
|
DDRB = data;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 0: TIM1T: set 1 clock interval ( 838 nsec/interval)
|
|
// 1: TIM8T: set 8 clock interval ( 6.7 usec/interval)
|
|
// 2: TIM64T: set 64 clock interval ( 53.6 usec/interval)
|
|
// 3: T1024T: set 1024 clock interval (858.2 usec/interval)
|
|
void SetTimerRegister(byte data, int interval)
|
|
{
|
|
IRQTriggered = false;
|
|
TimerShift = new[] { 0, 3, 6, 10 }[interval];
|
|
TimerTarget = M.CPU.Clock + (ulong)(data << TimerShift);
|
|
}
|
|
|
|
byte ReadTimerRegister()
|
|
{
|
|
IRQTriggered = false;
|
|
var delta = (int)(TimerTarget - M.CPU.Clock);
|
|
if (delta >= 0)
|
|
{
|
|
return (byte)(delta >> TimerShift);
|
|
}
|
|
if (delta != -1)
|
|
{
|
|
IRQTriggered = true;
|
|
}
|
|
return (byte)(delta >= -256 ? delta : 0);
|
|
}
|
|
|
|
byte ReadInterruptFlag()
|
|
{
|
|
var delta = (int)(TimerTarget - M.CPU.Clock);
|
|
return (byte)((delta >= 0 || IRQEnabled && IRQTriggered) ? 0x00 : 0x80);
|
|
}
|
|
|
|
// PortA: Controller Jacks
|
|
//
|
|
// Left Jack Right Jack
|
|
// ------------- -------------
|
|
// \ 1 2 3 4 5 / \ 1 2 3 4 5 /
|
|
// \ 6 7 8 9 / \ 6 7 8 9 /
|
|
// --------- ---------
|
|
//
|
|
// pin 1 D4 PIA SWCHA D0 PIA SWCHA
|
|
// pin 2 D5 PIA SWCHA D1 PIA SWCHA
|
|
// pin 3 D6 PIA SWCHA D2 PIA SWCHA
|
|
// pin 4 D7 PIA SWCHA D3 PIA SWCHA
|
|
// pin 5 D7 TIA INPT1 (Dumped) D7 TIA INPT3 (Dumped) 7800: Right Fire
|
|
// pin 6 D7 TIA INPT4 (Latched) D7 TIA INPT5 (Latched) 2600: Fire
|
|
// pin 7 +5 +5
|
|
// pin 8 GND GND
|
|
// pin 9 D7 TIA INPT0 (Dumped) D7 TIA INPT2 (Dumped) 7800: Left Fire
|
|
//
|
|
byte ReadPortA()
|
|
{
|
|
var porta = 0;
|
|
var mi = M.InputState;
|
|
|
|
switch (mi.LeftControllerJack)
|
|
{
|
|
case Controller.Joystick:
|
|
case Controller.ProLineJoystick:
|
|
case Controller.BoosterGrip:
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Up) ? 0 : (1 << 4);
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Down) ? 0 : (1 << 5);
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Left) ? 0 : (1 << 6);
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Right) ? 0 : (1 << 7);
|
|
break;
|
|
case Controller.Driving:
|
|
porta |= mi.SampleCapturedDrivingState(0) << 4;
|
|
break;
|
|
case Controller.Paddles:
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? 0 : (1 << 7);
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? 0 : (1 << 6);
|
|
break;
|
|
case Controller.Lightgun:
|
|
porta |= mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? (1 << 4) : 0;
|
|
break;
|
|
}
|
|
|
|
switch (mi.RightControllerJack)
|
|
{
|
|
case Controller.Joystick:
|
|
case Controller.ProLineJoystick:
|
|
case Controller.BoosterGrip:
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Up) ? 0 : (1 << 0);
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Down) ? 0 : (1 << 1);
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Left) ? 0 : (1 << 2);
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Right) ? 0 : (1 << 3);
|
|
break;
|
|
case Controller.Driving:
|
|
porta |= mi.SampleCapturedDrivingState(1);
|
|
break;
|
|
case Controller.Paddles:
|
|
porta |= mi.SampleCapturedControllerActionState(2, ControllerAction.Trigger) ? 0 : (1 << 3);
|
|
porta |= mi.SampleCapturedControllerActionState(3, ControllerAction.Trigger) ? 0 : (1 << 2);
|
|
break;
|
|
case Controller.Lightgun:
|
|
porta |= mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? (1 << 0) : 0;
|
|
break;
|
|
}
|
|
|
|
return (byte)porta;
|
|
}
|
|
|
|
void WritePortA(byte porta)
|
|
{
|
|
WrittenPortA = (byte)((porta & DDRA) | (WrittenPortA & (~DDRA)));
|
|
}
|
|
|
|
void WritePortB(byte portb)
|
|
{
|
|
WrittenPortB = (byte)((portb & DDRB) | (WrittenPortB & (~DDRB)));
|
|
}
|
|
|
|
// PortB: Console Switches
|
|
//
|
|
// D0 Game Reset 0=on
|
|
// D1 Game Select 0=on
|
|
// D2 (used on 7800)
|
|
// D3 Console Color 1=Color, 0=B/W
|
|
// D4 (used on 7800)
|
|
// D5 (unused)
|
|
// D6 Left Difficulty A 1=A (pro), 0=B (novice)
|
|
// D7 Right Difficulty A 1=A (pro), 0=B (novice)
|
|
//
|
|
byte ReadPortB()
|
|
{
|
|
var portb = 0;
|
|
var mi = M.InputState;
|
|
|
|
portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameReset) ? 0 : (1 << 0);
|
|
portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameSelect) ? 0 : (1 << 1);
|
|
portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.GameBW) ? 0 : (1 << 3);
|
|
portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.LeftDifficultyA) ? (1 << 6) : 0;
|
|
portb |= mi.SampleCapturedConsoleSwitchState(ConsoleSwitch.RightDifficultyA) ? (1 << 7) : 0;
|
|
|
|
return (byte)portb;
|
|
}
|
|
|
|
#region Serialization Members
|
|
|
|
public PIA(DeserializationContext input, MachineBase m) : this(m)
|
|
{
|
|
if (input == null)
|
|
throw new ArgumentNullException("input");
|
|
|
|
var version = input.CheckVersion(1, 2);
|
|
RAM = input.ReadExpectedBytes(0x80);
|
|
TimerTarget = input.ReadUInt64();
|
|
TimerShift = input.ReadInt32();
|
|
IRQEnabled = input.ReadBoolean();
|
|
IRQTriggered = input.ReadBoolean();
|
|
DDRA = input.ReadByte();
|
|
WrittenPortA = input.ReadByte();
|
|
if (version > 1)
|
|
{
|
|
DDRB = input.ReadByte();
|
|
WrittenPortB = input.ReadByte();
|
|
}
|
|
}
|
|
|
|
public void GetObjectData(SerializationContext output)
|
|
{
|
|
if (output == null)
|
|
throw new ArgumentNullException("output");
|
|
|
|
output.WriteVersion(2);
|
|
output.Write(RAM);
|
|
output.Write(TimerTarget);
|
|
output.Write(TimerShift);
|
|
output.Write(IRQEnabled);
|
|
output.Write(IRQTriggered);
|
|
output.Write(DDRA);
|
|
output.Write(WrittenPortA);
|
|
output.Write(DDRB);
|
|
output.Write(WrittenPortB);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helpers
|
|
|
|
void Log(string format, params object[] args)
|
|
{
|
|
if (M == null || M.Logger == null)
|
|
return;
|
|
M.Logger.WriteLine(format, args);
|
|
}
|
|
|
|
[System.Diagnostics.Conditional("DEBUG")]
|
|
void LogDebug(string format, params object[] args)
|
|
{
|
|
if (M == null || M.Logger == null)
|
|
return;
|
|
M.Logger.WriteLine(format, args);
|
|
}
|
|
|
|
[System.Diagnostics.Conditional("DEBUG")]
|
|
void AssertDebug(bool cond)
|
|
{
|
|
if (!cond)
|
|
System.Diagnostics.Debugger.Break();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |