using BizHawk.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.Z80A; using System; using System.Collections; namespace BizHawk.Emulation.Cores.Computers.AmstradCPC { /// /// 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 /// 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 /// /// CRTC Register constants /// 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 /// /// The standard CPC Pallete (ordered by firmware #) /// http://www.cpcwiki.eu/index.php/CPC_Palette /// 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 }; /// /// The standard CPC Pallete (ordered by hardware #) /// http://www.cpcwiki.eu/index.php/CPC_Palette /// 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(); } /// /// Inits the pen lookup table /// 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 /// /// 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 /// 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 /// /// Selects the pen /// 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; } } /// /// Selects colour for the currently selected pen /// public virtual void SetPenColour(BitArray bi) { byte[] b = new byte[1]; bi.CopyTo(b, 0); var colour = b[0] & 0x1f; PenColours[CurrentPen] = colour; } /// /// Returns the actual ARGB pen colour value /// public virtual int GetPenColour(int idx) { return CPCHardwarePalette[PenColours[idx]]; } /// /// Screen mode and ROM config /// 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; } } /// /// Only available on machines with a 64KB memory expansion /// Default assume we don't have this /// 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 /// /// Device responds to an IN instruction /// public bool ReadPort(ushort port, ref int result) { // gate array is OUT only return false; } /// /// Device responds to an OUT instruction /// 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 /// /// Video output buffer /// 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 } }