BizHawk/BizHawk.Emulation.Cores/Consoles/Atari/2600/Mappers/mDPCPlus.cs

367 lines
8.6 KiB
C#

using System;
using System.Linq;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Atari.Atari2600
{
/**
Cartridge class used for DPC+. There are six 4K program banks, a 4K
display bank, 1K frequency table and the DPC chip. For complete details on
the DPC chip see David P. Crane's United States Patent Number 4,644,495.
*/
internal class mDPCPlus : MapperBase
{
// TODO: PokeMem, and everything else
public mDPCPlus()
{
throw new NotImplementedException();
}
private IntBuffer _counters = new IntBuffer(8);
private ByteBuffer _tops = new ByteBuffer(8);
private ByteBuffer _flags = new ByteBuffer(8);
private ByteBuffer _bottoms = new ByteBuffer(8);
private bool[] _musicModes = new bool[3];
private int _bank4K;
private byte _currentRandomVal;
private int _elapsedCycles = 85; // 85 compensates for a slight timing issue when ClockCpu is first run, 85 puts BizHawk back on track with Stella on elapsed timing values
private float _fractionalClocks; // Fractional DPC music OSC clocks unused during the last update
private byte[] _dspData;
public byte[] DspData
{
get
{
return _dspData ?? (_dspData = Core.Rom.Skip(8192).Take(2048).ToArray());
}
}
// Table for computing the input bit of the random number generator's
// shift register (it's the NOT of the EOR of four bits)
private readonly byte[] _randomInputBits = { 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 };
public override void Dispose()
{
base.Dispose();
_counters.Dispose();
_tops.Dispose();
_flags.Dispose();
_bottoms.Dispose();
}
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
ser.Sync("counters", ref _counters);
ser.Sync("tops", ref _tops);
ser.Sync("flags", ref _flags);
ser.Sync("bottoms", ref _bottoms);
ser.Sync("musicMode0", ref _musicModes[0]); // Silly, but I didn't want to support bool[] in Serializer just for this one variable
ser.Sync("musicMode1", ref _musicModes[1]);
ser.Sync("musicMode2", ref _musicModes[2]);
ser.Sync("bank_4k", ref _bank4K);
ser.Sync("currentRandomVal", ref _currentRandomVal);
ser.Sync("elapsedCycles", ref _elapsedCycles);
ser.Sync("fractionalClocks", ref _fractionalClocks);
}
public override void HardReset()
{
_counters = new IntBuffer(8);
_tops = new ByteBuffer(8);
_flags = new ByteBuffer(8);
_bottoms = new ByteBuffer(8);
_musicModes = new bool[3];
_bank4K = 0;
_currentRandomVal = 0;
_elapsedCycles = 85;
_fractionalClocks = 0;
base.HardReset();
}
public override void ClockCpu()
{
_elapsedCycles++;
}
private byte ReadMem(ushort addr, bool peek)
{
if (addr < 0x1000)
{
return base.ReadMemory(addr);
}
if (!peek)
{
Address(addr);
ClockRandomNumberGenerator();
}
if (addr < 0x1040)
{
byte result;
// Get the index of the data fetcher that's being accessed
var index = addr & 0x07;
var function = (addr >> 3) & 0x07;
// Update flag register for selected data fetcher
if ((_counters[index] & 0x00ff) == _tops[index])
{
_flags[index] = 0xff;
}
else if ((_counters[index] & 0x00ff) == _bottoms[index])
{
_flags[index] = 0x00;
}
switch (function)
{
case 0x00:
if (index < 4)
{
result = _currentRandomVal;
}
else // No, it's a music read
{
var musicAmplitudes = new byte[] {
0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f
};
// Update the music data fetchers (counter & flag)
UpdateMusicModeDataFetchers();
byte i = 0;
if (_musicModes[0] && _flags[5] > 0)
{
i |= 0x01;
}
if (_musicModes[1] && _flags[6] > 0)
{
i |= 0x02;
}
if (_musicModes[2] && _flags[7] > 0)
{
i |= 0x04;
}
result = musicAmplitudes[i];
}
break;
// DFx display data read
case 0x01:
result = DspData[2047 - _counters[index]];
break;
// DFx display data read AND'd w/flag
case 0x02:
result = (byte)(DspData[2047 - _counters[index]] & _flags[index]);
break;
// DFx flag
case 0x07:
result = _flags[index];
break;
default:
result = 0;
break;
}
// Clock the selected data fetcher's counter if needed
if ((index < 5) || ((index >= 5) && (!_musicModes[index - 5])))
{
_counters[index] = (_counters[index] - 1) & 0x07ff;
}
return result;
}
return Core.Rom[(_bank4K << 12) + (addr & 0xFFF)];
}
public override byte ReadMemory(ushort addr)
{
return ReadMem(addr, false);
}
public override byte PeekMemory(ushort addr)
{
return ReadMem(addr, true);
}
public override void WriteMemory(ushort addr, byte value)
{
if (addr < 0x1000)
{
base.WriteMemory(addr, value);
return;
}
Address(addr);
ClockRandomNumberGenerator();
if (addr >= 0x1040 && addr < 0x1080)
{
var index = addr & 0x07;
var function = (addr >> 3) & 0x07;
switch (function)
{
// DFx top count
case 0x00:
_tops[index] = value;
_flags[index] = 0x00;
break;
// DFx bottom count
case 0x01:
_bottoms[index] = value;
break;
// DFx counter low
case 0x02:
if ((index >= 5) && _musicModes[index - 5])
{
// Data fetcher is in music mode so its low counter value
// should be loaded from the top register not the poked value
_counters[index] = (_counters[index] & 0x0700) |
_tops[index];
}
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[index] = (_counters[index] & 0x0700) | value;
}
break;
// DFx counter high
case 0x03:
_counters[index] = (ushort)(((value & 0x07) << 8) |
(_counters[index] & 0x00ff));
// Execute special code for music mode data fetchers
if (index >= 5)
{
_musicModes[index - 5] = (value & 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:
_currentRandomVal = 1;
break;
}
}
}
private void Address(ushort addr)
{
if (addr == 0x1FF6)
{
_bank4K = 0;
}
else if (addr == 0x1FF7)
{
_bank4K = 1;
}
else if (addr == 0x1FF8)
{
_bank4K = 2;
}
else if (addr == 0x1FF9)
{
_bank4K = 3;
}
else if (addr == 0x1FFA)
{
_bank4K = 4;
}
else if (addr == 0x1FFB)
{
_bank4K = 5;
}
}
private void ClockRandomNumberGenerator()
{
// Using bits 7, 5, 4, & 3 of the shift register compute the input
// bit for the shift register
var bit = _randomInputBits[((_currentRandomVal >> 3) & 0x07) |
(((_currentRandomVal & 0x80) > 0) ? 0x08 : 0x00)];
// Update the shift register
_currentRandomVal = (byte)((_currentRandomVal << 1) | bit);
}
private void UpdateMusicModeDataFetchers()
{
// Calculate the number of cycles since the last update
var cycles = _elapsedCycles;
_elapsedCycles = 0;
// Calculate the number of DPC OSC clocks since the last update
var clocks = ((20000.0 * cycles) / 1193191.66666667) + _fractionalClocks;
var wholeClocks = (int)clocks;
_fractionalClocks = (float)(clocks - wholeClocks);
if (wholeClocks <= 0)
{
return;
}
// Let's update counters and flags of the music mode data fetchers
for (var x = 5; x <= 7; ++x)
{
// Update only if the data fetcher is in music mode
if (_musicModes[x - 5])
{
var top = _tops[x] + 1;
var newLow = _counters[x] & 0x00ff;
if (_tops[x] != 0)
{
newLow -= wholeClocks % top;
if (newLow < 0)
{
newLow += top;
}
}
else
{
newLow = 0;
}
// Update flag register for this data fetcher
if (newLow <= _bottoms[x])
{
_flags[x] = 0x00;
}
else if (newLow <= _tops[x])
{
_flags[x] = 0xff;
}
_counters[x] = (_counters[x] & 0x0700) | (ushort)newLow;
}
}
}
}
}