BizHawk/EMU7800/Core/CartDPC.cs

341 lines
10 KiB
C#

namespace EMU7800.Core
{
/// <summary>
/// Pitfall II cartridge.
/// There are two 4k banks, 2k display bank, and the DPC chip.
/// For complete details on the DPC chip see David P. Crane's United States Patent Number 4,644,495.
/// </summary>
public sealed class CartDPC : Cart
{
//
// Cart Format Mapping to ROM Address Space
// Bank1: 0x0000:0x1000 0x1000:0x1000 Bank selected by accessing 0x1ff8,0x1ff9
// Bank2: 0x1000:0x1000
//
const ushort DisplayBaseAddr = 0x2000;
ushort BankBaseAddr;
readonly byte[] MusicAmplitudes = new byte[] { 0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f };
readonly byte[] Tops = new byte[8];
readonly byte[] Bots = new byte[8];
readonly ushort[] Counters = new ushort[8];
readonly byte[] Flags = new byte[8];
readonly bool[] MusicMode = new bool[3];
ulong LastSystemClock;
double FractionalClocks;
byte _ShiftRegister;
int Bank
{
set { BankBaseAddr = (ushort)(value * 0x1000); }
}
//
// Generate a sequence of pseudo-random numbers 255 numbers long
// by emulating an 8-bit shift register with feedback taps at
// bits 4, 3, 2, and 0.
byte ShiftRegister
{
get
{
var a = _ShiftRegister;
a &= (1 << 0);
var x = _ShiftRegister;
x &= (1 << 2);
x >>= 2;
a ^= x;
x = _ShiftRegister;
x &= (1 << 3);
x >>= 3;
a ^= x;
x = _ShiftRegister;
x &= (1 << 4);
x >>= 4;
a ^= x;
a <<= 7;
_ShiftRegister >>= 1;
_ShiftRegister |= a;
return _ShiftRegister;
}
set { _ShiftRegister = value; }
}
#region IDevice Members
public override void Reset()
{
Bank = 1;
LastSystemClock = 3*M.CPU.Clock;
FractionalClocks = 0.0;
ShiftRegister = 1;
}
public override byte this[ushort addr]
{
get
{
addr &= 0x0fff;
if (addr < 0x0040)
{
return ReadPitfall2Reg(addr);
}
UpdateBank(addr);
return ROM[BankBaseAddr + addr];
}
set
{
addr &= 0x0fff;
if (addr >= 0x0040 && addr < 0x0080)
{
WritePitfall2Reg(addr, value);
}
else
{
UpdateBank(addr);
}
}
}
#endregion
private CartDPC()
{
}
public CartDPC(byte[] romBytes)
{
LoadRom(romBytes, 0x2800);
Bank = 1;
}
void UpdateBank(ushort addr)
{
switch(addr)
{
case 0x0ff8:
Bank = 0;
break;
case 0x0ff9:
Bank = 1;
break;
}
}
byte ReadPitfall2Reg(ushort addr)
{
byte result;
var i = addr & 0x07;
var fn = (addr >> 3) & 0x07;
// Update flag register for selected data fetcher
if ((Counters[i] & 0x00ff) == Tops[i])
{
Flags[i] = 0xff;
}
else if ((Counters[i] & 0x00ff) == Bots[i])
{
Flags[i] = 0x00;
}
switch (fn)
{
case 0x00:
if (i < 4)
{
// This is a random number read
result = ShiftRegister;
break;
}
// Its a music read
UpdateMusicModeDataFetchers();
byte j = 0;
if (MusicMode[0] && Flags[5] != 0)
{
j |= 0x01;
}
if (MusicMode[1] && Flags[6] != 0)
{
j |= 0x02;
}
if (MusicMode[2] && Flags[7] != 0)
{
j |= 0x04;
}
result = MusicAmplitudes[j];
break;
// DFx display data read
case 0x01:
result = ROM[DisplayBaseAddr + 0x7ff - Counters[i]];
break;
// DFx display data read AND'd w/flag
case 0x02:
result = ROM[DisplayBaseAddr + 0x7ff - Counters[i]];
result &= Flags[i];
break;
// DFx flag
case 0x07:
result = Flags[i];
break;
default:
result = 0;
break;
}
// Clock the selected data fetcher's counter if needed
if (i < 5 || i >= 5 && MusicMode[i - 5] == false)
{
Counters[i]--;
Counters[i] &= 0x07ff;
}
return result;
}
void UpdateMusicModeDataFetchers()
{
var sysClockDelta = 3*M.CPU.Clock - LastSystemClock;
LastSystemClock = 3*M.CPU.Clock;
var OSCclocks = ((15750.0 * sysClockDelta) / 1193191.66666667) + FractionalClocks;
var wholeClocks = (int)OSCclocks;
FractionalClocks = OSCclocks - wholeClocks;
if (wholeClocks <= 0)
{
return;
}
for (var i=0; i < 3; i++)
{
var r = i + 5;
if (!MusicMode[i]) continue;
var top = Tops[r] + 1;
var newLow = Counters[r] & 0x00ff;
if (Tops[r] != 0)
{
newLow -= (wholeClocks % top);
if (newLow < 0)
{
newLow += top;
}
}
else
{
newLow = 0;
}
if (newLow <= Bots[r])
{
Flags[r] = 0x00;
}
else if (newLow <= Tops[r])
{
Flags[r] = 0xff;
}
Counters[r] = (ushort)((Counters[r] & 0x0700) | (ushort)newLow);
}
}
void WritePitfall2Reg(ushort addr, byte val)
{
var i = addr & 0x07;
var fn = (addr >> 3) & 0x07;
switch (fn)
{
// DFx top count
case 0x00:
Tops[i] = val;
Flags[i] = 0x00;
break;
// DFx bottom count
case 0x01:
Bots[i] = val;
break;
// DFx counter low
case 0x02:
Counters[i] &= 0x0700;
if (i >= 5 && MusicMode[i - 5])
{
// Data fetcher is in music mode so its low counter value
// should be loaded from the top register not the poked value
Counters[i] |= Tops[i];
}
else
{
// Data fetcher is either not a music mode data fetcher or it
// isn't in music mode so it's low counter value should be loaded
// with the poked value
Counters[i] |= val;
}
break;
// DFx counter high
case 0x03:
Counters[i] &= 0x00ff;
Counters[i] |= (ushort)((val & 0x07) << 8);
// Execute special code for music mode data fetchers
if (i >= 5)
{
MusicMode[i - 5] = (val & 0x10) != 0;
// NOTE: We are not handling the clock source input for
// the music mode data fetchers. We're going to assume
// they always use the OSC input.
}
break;
// Random Number Generator Reset
case 0x06:
ShiftRegister = 1;
break;
}
}
#region Serialization Members
public CartDPC(DeserializationContext input, MachineBase m) : base(input)
{
input.CheckVersion(1);
LoadRom(input.ReadExpectedBytes(0x2800), 0x2800);
BankBaseAddr = input.ReadUInt16();
Tops = input.ReadExpectedBytes(8);
Bots = input.ReadExpectedBytes(8);
Counters = input.ReadUnsignedShorts(8);
Flags = input.ReadExpectedBytes(8);
MusicMode = input.ReadBooleans(3);
LastSystemClock = input.ReadUInt64();
FractionalClocks = input.ReadDouble();
_ShiftRegister = input.ReadByte();
}
public override void GetObjectData(SerializationContext output)
{
base.GetObjectData(output);
output.WriteVersion(1);
output.Write(ROM);
output.Write(BankBaseAddr);
output.Write(Tops);
output.Write(Bots);
output.Write(Counters);
output.Write(Flags);
output.Write(MusicMode);
output.Write(LastSystemClock);
output.Write(FractionalClocks);
output.Write(_ShiftRegister);
}
#endregion
}
}