519 lines
13 KiB
C#
519 lines
13 KiB
C#
using BizHawk.Common;
|
||
using BizHawk.Emulation.Common;
|
||
using BizHawk.Emulation.Cores.Components.Z80A;
|
||
using System;
|
||
using System.Collections;
|
||
|
||
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
||
{
|
||
/// <summary>
|
||
/// The abstract class that all emulated models will inherit from
|
||
/// * Amstrad Gate Array *
|
||
/// https://web.archive.org/web/20170612081209/http://www.grimware.org/doku.php/documentations/devices/gatearray
|
||
/// </summary>
|
||
public abstract class GateArrayBase : IVideoProvider
|
||
{
|
||
public int Z80ClockSpeed = 4000000;
|
||
public int FrameLength = 79872;
|
||
|
||
#region Devices
|
||
|
||
private CPCBase _machine;
|
||
private Z80A CPU => _machine.CPU;
|
||
private CRCT_6845 CRCT => _machine.CRCT;
|
||
private IPSG PSG => _machine.AYDevice;
|
||
|
||
#endregion
|
||
|
||
#region Constants
|
||
|
||
/// <summary>
|
||
/// CRTC Register constants
|
||
/// </summary>
|
||
public const int HOR_TOTAL = 0;
|
||
public const int HOR_DISPLAYED = 1;
|
||
public const int HOR_SYNC_POS = 2;
|
||
public const int HOR_AND_VER_SYNC_WIDTHS = 3;
|
||
public const int VER_TOTAL = 4;
|
||
public const int VER_TOTAL_ADJUST = 5;
|
||
public const int VER_DISPLAYED = 6;
|
||
public const int VER_SYNC_POS = 7;
|
||
public const int INTERLACE_SKEW = 8;
|
||
public const int MAX_RASTER_ADDR = 9;
|
||
public const int CUR_START_RASTER = 10;
|
||
public const int CUR_END_RASTER = 11;
|
||
public const int DISP_START_ADDR_H = 12;
|
||
public const int DISP_START_ADDR_L = 13;
|
||
public const int CUR_ADDR_H = 14;
|
||
public const int CUR_ADDR_L = 15;
|
||
public const int LPEN_ADDR_H = 16;
|
||
public const int LPEN_ADDR_L = 17;
|
||
|
||
#endregion
|
||
|
||
#region Palletes
|
||
|
||
/// <summary>
|
||
/// The standard CPC Pallete (ordered by firmware #)
|
||
/// http://www.cpcwiki.eu/index.php/CPC_Palette
|
||
/// </summary>
|
||
private static readonly int[] CPCFirmwarePalette =
|
||
{
|
||
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||
Colors.ARGB(0x00, 0x00, 0x80), // Blue
|
||
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||
Colors.ARGB(0x80, 0x00, 0x00), // Red
|
||
Colors.ARGB(0x80, 0x00, 0x80), // Magenta
|
||
Colors.ARGB(0x80, 0x00, 0xFF), // Mauve
|
||
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple
|
||
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||
Colors.ARGB(0x00, 0x80, 0x00), // Green
|
||
Colors.ARGB(0x00, 0x80, 0x80), // Cyan
|
||
Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue
|
||
Colors.ARGB(0x80, 0x80, 0x00), // Yellow
|
||
Colors.ARGB(0x80, 0x80, 0x80), // White
|
||
Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue
|
||
Colors.ARGB(0xFF, 0x80, 0x00), // Orange
|
||
Colors.ARGB(0xFF, 0x80, 0x80), // Pink
|
||
Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta
|
||
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green
|
||
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||
Colors.ARGB(0x80, 0xFF, 0x00), // Lime
|
||
Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green
|
||
Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan
|
||
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow
|
||
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||
};
|
||
|
||
/// <summary>
|
||
/// The standard CPC Pallete (ordered by hardware #)
|
||
/// http://www.cpcwiki.eu/index.php/CPC_Palette
|
||
/// </summary>
|
||
private static readonly int[] CPCHardwarePalette =
|
||
{
|
||
Colors.ARGB(0x80, 0x80, 0x80), // White
|
||
Colors.ARGB(0x80, 0x80, 0x80), // White (duplicate)
|
||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green
|
||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow
|
||
Colors.ARGB(0x00, 0x00, 0x80), // Blue
|
||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple
|
||
Colors.ARGB(0x00, 0x80, 0x80), // Cyan
|
||
Colors.ARGB(0xFF, 0x80, 0x80), // Pink
|
||
Colors.ARGB(0xFF, 0x00, 0x80), // Purple (duplicate)
|
||
Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow (duplicate)
|
||
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||
Colors.ARGB(0xFF, 0x80, 0x00), // Orange
|
||
Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta
|
||
Colors.ARGB(0x00, 0x00, 0x80), // Blue (duplicate)
|
||
Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green (duplicate)
|
||
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||
Colors.ARGB(0x00, 0x80, 0x00), // Green
|
||
Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue
|
||
Colors.ARGB(0x80, 0x00, 0x80), // Magenta
|
||
Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green
|
||
Colors.ARGB(0x80, 0xFF, 0x00), // Lime
|
||
Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan
|
||
Colors.ARGB(0x80, 0x00, 0x00), // Red
|
||
Colors.ARGB(0x80, 0x00, 0xFF), // Mauve
|
||
Colors.ARGB(0x80, 0x80, 0x00), // Yellow
|
||
Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue
|
||
};
|
||
|
||
#endregion
|
||
|
||
#region Construction
|
||
|
||
public GateArrayBase(CPCBase machine)
|
||
{
|
||
_machine = machine;
|
||
PenColours = new int[17];
|
||
SetupScreenSize();
|
||
Reset();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Inits the pen lookup table
|
||
/// </summary>
|
||
public void SetupScreenMapping()
|
||
{
|
||
for (int m = 0; m < 4; m++)
|
||
{
|
||
Lookup[m] = new int[256 * 8];
|
||
int pos = 0;
|
||
|
||
for (int b = 0; b < 256; b++)
|
||
{
|
||
switch (m)
|
||
{
|
||
case 1:
|
||
int pc = (((b & 0x80) >> 7) | ((b & 0x80) >> 2));
|
||
Lookup[m][pos++] = pc;
|
||
Lookup[m][pos++] = pc;
|
||
pc = (((b & 0x40) >> 6) | ((b & 0x04) >> 1));
|
||
Lookup[m][pos++] = pc;
|
||
Lookup[m][pos++] = pc;
|
||
pc = (((b & 0x20) >> 5) | (b & 0x02));
|
||
Lookup[m][pos++] = pc;
|
||
Lookup[m][pos++] = pc;
|
||
pc = (((b & 0x10) >> 4) | ((b & 0x01) << 1));
|
||
break;
|
||
|
||
case 2:
|
||
for (int i = 7; i >= 0; i--)
|
||
{
|
||
bool pixel_on = ((b & (1 << i)) != 0);
|
||
Lookup[m][pos++] = pixel_on ? 1 : 0;
|
||
}
|
||
break;
|
||
|
||
case 3:
|
||
case 0:
|
||
int pc2 = (b & 0xAA);
|
||
pc2 = (
|
||
((pc2 & 0x80) >> 7) |
|
||
((pc2 & 0x08) >> 2) |
|
||
((pc2 & 0x20) >> 3) |
|
||
((pc2 & 0x02) << 2));
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
pc2 = (b & 0x55);
|
||
pc2 = (
|
||
((pc2 & 0x40) >> 6) |
|
||
((pc2 & 0x04) >> 1) |
|
||
((pc2 & 0x10) >> 2) |
|
||
((pc2 & 0x01) << 3));
|
||
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
Lookup[m][pos++] = pc2;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region State
|
||
|
||
private int[] PenColours;
|
||
private int CurrentPen;
|
||
private int ScreenMode;
|
||
private int INTScanlineCnt;
|
||
//private int VSYNCDelyCnt;
|
||
|
||
private int[][] Lookup = new int[4][];
|
||
|
||
//private bool DoModeUpdate;
|
||
|
||
//private int LatchedMode;
|
||
//private int buffPos;
|
||
|
||
public bool FrameEnd;
|
||
|
||
public bool WaitLine;
|
||
|
||
#endregion
|
||
|
||
#region Clock Operations
|
||
|
||
/// <summary>
|
||
/// The gatearray runs on a 16Mhz clock
|
||
/// (for the purposes of emulation, we will use a 4Mhz clock)
|
||
/// From this it generates:
|
||
/// 1Mhz clock for the CRTC chip
|
||
/// 1Mhz clock for the AY-3-8912 PSG
|
||
/// 4Mhz clock for the Z80 CPU
|
||
/// </summary>
|
||
public void ClockCycle()
|
||
{
|
||
// 4-phase clock
|
||
for (int i = 1; i < 5; i++)
|
||
{
|
||
switch (i)
|
||
{
|
||
// Phase 1
|
||
case 1:
|
||
CRCT.ClockCycle();
|
||
CPU.ExecuteOne();
|
||
break;
|
||
// Phase 2
|
||
case 2:
|
||
CPU.ExecuteOne();
|
||
break;
|
||
// Phase 3
|
||
case 3:
|
||
// video fetch
|
||
break;
|
||
// Phase 4
|
||
case 4:
|
||
// video fetch
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Internal Methods
|
||
|
||
/// <summary>
|
||
/// Selects the pen
|
||
/// </summary>
|
||
public virtual void SetPen(BitArray bi)
|
||
{
|
||
if (bi[4])
|
||
{
|
||
// border select
|
||
CurrentPen = 16;
|
||
}
|
||
else
|
||
{
|
||
// pen select
|
||
byte[] b = new byte[1];
|
||
bi.CopyTo(b, 0);
|
||
CurrentPen = b[0] & 0x0f;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Selects colour for the currently selected pen
|
||
/// </summary>
|
||
public virtual void SetPenColour(BitArray bi)
|
||
{
|
||
byte[] b = new byte[1];
|
||
bi.CopyTo(b, 0);
|
||
var colour = b[0] & 0x1f;
|
||
PenColours[CurrentPen] = colour;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Returns the actual ARGB pen colour value
|
||
/// </summary>
|
||
public virtual int GetPenColour(int idx)
|
||
{
|
||
return CPCHardwarePalette[PenColours[idx]];
|
||
}
|
||
|
||
/// <summary>
|
||
/// Screen mode and ROM config
|
||
/// </summary>
|
||
public virtual void SetReg2(BitArray bi)
|
||
{
|
||
byte[] b = new byte[1];
|
||
bi.CopyTo(b, 0);
|
||
|
||
// screen mode
|
||
var mode = b[0] & 0x03;
|
||
ScreenMode = mode;
|
||
|
||
// ROM
|
||
|
||
// upper
|
||
if ((b[0] & 0x08) != 0)
|
||
{
|
||
_machine.UpperROMPaged = false;
|
||
}
|
||
else
|
||
{
|
||
_machine.UpperROMPaged = true;
|
||
}
|
||
|
||
// lower
|
||
if ((b[0] & 0x04) != 0)
|
||
{
|
||
_machine.LowerROMPaged = false;
|
||
}
|
||
else
|
||
{
|
||
_machine.LowerROMPaged = true;
|
||
}
|
||
|
||
// INT delay
|
||
if ((b[0] & 0x10) != 0)
|
||
{
|
||
INTScanlineCnt = 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Only available on machines with a 64KB memory expansion
|
||
/// Default assume we don't have this
|
||
/// </summary>
|
||
public virtual void SetRAM(BitArray bi)
|
||
{
|
||
return;
|
||
}
|
||
|
||
public void InterruptACK()
|
||
{
|
||
INTScanlineCnt &= 0x01f;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#endregion
|
||
|
||
#region Reset
|
||
|
||
public void Reset()
|
||
{
|
||
CurrentPen = 0;
|
||
ScreenMode = 1;
|
||
for (int i = 0; i < 17; i++)
|
||
PenColours[i] = 0;
|
||
INTScanlineCnt = 0;
|
||
//VSYNCDelyCnt = 0;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region IPortIODevice
|
||
|
||
/// <summary>
|
||
/// Device responds to an IN instruction
|
||
/// </summary>
|
||
public bool ReadPort(ushort port, ref int result)
|
||
{
|
||
// gate array is OUT only
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Device responds to an OUT instruction
|
||
/// </summary>
|
||
public bool WritePort(ushort port, int result)
|
||
{
|
||
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
|
||
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result));
|
||
|
||
// The gate array responds to port 0x7F
|
||
bool accessed = !portBits[15];
|
||
if (!accessed)
|
||
return false;
|
||
|
||
// Bit 9 and 8 of the data byte define the function to access
|
||
if (!dataBits[6] && !dataBits[7])
|
||
{
|
||
// select pen
|
||
SetPen(dataBits);
|
||
}
|
||
|
||
if (dataBits[6] && !dataBits[7])
|
||
{
|
||
// select colour for selected pen
|
||
SetPenColour(dataBits);
|
||
}
|
||
|
||
if (!dataBits[6] && dataBits[7])
|
||
{
|
||
// select screen mode, ROM configuration and interrupt control
|
||
SetReg2(dataBits);
|
||
}
|
||
|
||
if (dataBits[6] && dataBits[7])
|
||
{
|
||
// RAM memory management
|
||
SetRAM(dataBits);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region IVideoProvider
|
||
|
||
/// <summary>
|
||
/// Video output buffer
|
||
/// </summary>
|
||
public int[] ScreenBuffer;
|
||
|
||
private int _virtualWidth;
|
||
private int _virtualHeight;
|
||
private int _bufferWidth;
|
||
private int _bufferHeight;
|
||
|
||
public int BackgroundColor => CPCHardwarePalette[16];
|
||
|
||
public int VirtualWidth
|
||
{
|
||
get => _virtualWidth;
|
||
set => _virtualWidth = value;
|
||
}
|
||
|
||
public int VirtualHeight
|
||
{
|
||
get => _virtualHeight;
|
||
set => _virtualHeight = value;
|
||
}
|
||
|
||
public int BufferWidth
|
||
{
|
||
get => _bufferWidth;
|
||
set => _bufferWidth = value;
|
||
}
|
||
|
||
public int BufferHeight
|
||
{
|
||
get => _bufferHeight;
|
||
set => _bufferHeight = value;
|
||
}
|
||
|
||
public int VsyncNumerator
|
||
{
|
||
get => Z80ClockSpeed * 50;
|
||
set { }
|
||
}
|
||
|
||
public int VsyncDenominator => Z80ClockSpeed;
|
||
|
||
public int[] GetVideoBuffer() => ScreenBuffer;
|
||
|
||
protected void SetupScreenSize()
|
||
{
|
||
/*
|
||
* Rect Pixels: Mode 0: 160×200 pixels with 16 colors (4 bpp)
|
||
Sqaure Pixels: Mode 1: 320×200 pixels with 4 colors (2 bpp)
|
||
Rect Pixels: Mode 2: 640×200 pixels with 2 colors (1 bpp)
|
||
Rect Pixels: Mode 3: 160×200 pixels with 4 colors (2bpp) (this is not an official mode, but rather a side-effect of the hardware)
|
||
*
|
||
* */
|
||
|
||
// define maximum screen buffer size
|
||
// to fit all possible resolutions, 640x400 should do it
|
||
// therefore a scanline will take two buffer rows
|
||
// and buffer columns will be:
|
||
// Mode 1: 2 pixels
|
||
// Mode 2: 1 pixel
|
||
// Mode 0: 4 pixels
|
||
// Mode 3: 4 pixels
|
||
|
||
BufferWidth = 640;
|
||
BufferHeight = 400;
|
||
VirtualHeight = BufferHeight;
|
||
VirtualWidth = BufferWidth;
|
||
ScreenBuffer = new int[BufferWidth * BufferHeight];
|
||
croppedBuffer = ScreenBuffer;
|
||
}
|
||
|
||
protected int[] croppedBuffer;
|
||
|
||
#endregion
|
||
}
|
||
}
|