BizHawk/EMU7800/Core/PIA.cs

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
}
}