341 lines
10 KiB
C#
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
|
|
}
|
|
} |