1400 lines
38 KiB
C#
1400 lines
38 KiB
C#
using BizHawk.Common;
|
|
using BizHawk.Common.NumberExtensions;
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
|
|
{
|
|
/// <summary>
|
|
/// * Amstrad Gate Array *
|
|
/// http://www.cpcwiki.eu/index.php/Gate_Array
|
|
/// https://web.archive.org/web/20170612081209/http://www.grimware.org/doku.php/documentations/devices/gatearray
|
|
/// </summary>
|
|
public class AmstradGateArray : IPortIODevice, IVideoProvider
|
|
{
|
|
#region Devices
|
|
|
|
private CPCBase _machine;
|
|
private Z80A CPU => _machine.CPU;
|
|
private CRCT_6845 CRCT => _machine.CRCT;
|
|
//private CRTDevice CRT => _machine.CRT;
|
|
private IPSG PSG => _machine.AYDevice;
|
|
private NECUPD765 FDC => _machine.UPDDiskDevice;
|
|
private DatacorderDevice DATACORDER => _machine.TapeDevice;
|
|
private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
|
|
public const ushort PCh = 1;
|
|
|
|
private GateArrayType ChipType;
|
|
|
|
#endregion
|
|
|
|
#region Palettes
|
|
|
|
/// <summary>
|
|
/// The standard CPC Pallete (ordered by firmware #)
|
|
/// http://www.cpcwiki.eu/index.php/CPC_Palette
|
|
/// </summary>
|
|
public 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>
|
|
public 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 Clocks and Timing
|
|
|
|
/// <summary>
|
|
/// The Gate Array Clock Speed
|
|
/// </summary>
|
|
public int GAClockSpeed = 16000000;
|
|
|
|
/// <summary>
|
|
/// The CPU Clock Speed
|
|
/// </summary>
|
|
public int Z80ClockSpeed = 4000000;
|
|
|
|
/// <summary>
|
|
/// CRCT Clock Speed
|
|
/// </summary>
|
|
public int CRCTClockSpeed = 1000000;
|
|
|
|
/// <summary>
|
|
/// AY-3-8912 Clock Speed
|
|
/// </summary>
|
|
public int PSGClockSpeed = 1000000;
|
|
|
|
/// <summary>
|
|
/// Number of CPU cycles in one frame
|
|
/// </summary>
|
|
public int FrameLength = 79872;
|
|
|
|
/// <summary>
|
|
/// Number of Gate Array cycles within one frame
|
|
/// </summary>
|
|
public int GAFrameLength = 319488;
|
|
|
|
#endregion
|
|
|
|
#region Construction
|
|
|
|
public AmstradGateArray(CPCBase machine, GateArrayType chipType)
|
|
{
|
|
_machine = machine;
|
|
ChipType = chipType;
|
|
//PenColours = new int[17];
|
|
borderType = _machine.CPC.SyncSettings.BorderType;
|
|
SetupScreenSize();
|
|
//Reset();
|
|
|
|
CRCT.AttachHSYNCCallback(OnHSYNC);
|
|
CRCT.AttachVSYNCCallback(OnVSYNC);
|
|
|
|
CurrentLine = new CharacterLine();
|
|
InitByteLookup();
|
|
CalculateNextScreenMemory();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Registers and Internal State
|
|
|
|
/// <summary>
|
|
/// PENR (register 0) - Pen Selection
|
|
/// This register can be used to select one of the 17 color-registers (pen 0 to 15 or the border).
|
|
/// It will remain selected until another PENR command is executed.
|
|
/// PENR Index
|
|
/// 7 6 5 4 3 2 1 0 color register selected
|
|
/// 0 0 0 0 n n n n pen n from 0 to 15 (4bits)
|
|
/// 0 0 0 1 x x x x border
|
|
///
|
|
/// x can be 0 or 1, it doesn't matter
|
|
/// </summary>
|
|
private byte _PENR;
|
|
public byte PENR
|
|
{
|
|
get { return _PENR; }
|
|
set
|
|
{
|
|
_PENR = value;
|
|
if (_PENR.Bit(4))
|
|
{
|
|
// border select
|
|
CurrentPen = 16;
|
|
}
|
|
else
|
|
{
|
|
// pen select
|
|
CurrentPen = _PENR & 0x0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 0-15: Pen Registers
|
|
/// 16: Border Colour
|
|
/// </summary>
|
|
public int[] ColourRegisters = new int[17];
|
|
|
|
/// <summary>
|
|
/// The currently selected Pen
|
|
/// </summary>
|
|
private int CurrentPen;
|
|
|
|
/// <summary>
|
|
/// INKR (register 1) - Colour Selection
|
|
/// This register takes a 5bits parameter which is a color-code. This color-code range from 0 to 31 but there's only 27 differents colors
|
|
/// (because the Gate Array use a 3-states logic on the R,G and B signals, thus 3x3x3=27).
|
|
/// INKR Color
|
|
/// 7 6 5 4 3 2 1 0
|
|
/// 0 1 0 n n n n n where n is a color code (5 bits)
|
|
///
|
|
/// The PEN affected by the INKR command is updated (almost) immediatly
|
|
/// </summary>
|
|
private byte _INKR;
|
|
public byte INKR
|
|
{
|
|
get { return _INKR; }
|
|
set
|
|
{
|
|
_INKR = value;
|
|
ColourRegisters[CurrentPen] = _INKR & 0x1f;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// RMR (register 2) - Select screen mode and ROM configuration
|
|
/// This register control the interrupt counter (reset), the upper and lower ROM paging and the video mode.
|
|
/// RMR Commands
|
|
/// 7 6 5 4 3 2 1 0
|
|
/// 1 0 0 I UR LR VM-->
|
|
///
|
|
/// I : if set (1), this will reset the interrupt counter
|
|
/// UR : Enable (0) or Disable (1) the upper ROM paging (&C000 to &FFFF). You can select which upper ROM with the I/O address &DF00
|
|
/// LR : Enable (0) or Disable (1) the lower ROM paging
|
|
/// VM : Select the video mode 0,1,2 or 3 (it will take effect after the next HSync)
|
|
/// </summary>
|
|
private byte _RMR;
|
|
public byte RMR
|
|
{
|
|
get { return _RMR; }
|
|
set
|
|
{
|
|
_RMR = value;
|
|
//ScreenMode = _RMR & 0x03;
|
|
var sm = _RMR & 0x03;
|
|
if (sm != 1)
|
|
{
|
|
|
|
}
|
|
|
|
if ((_RMR & 0x08) != 0)
|
|
_machine.UpperROMPaged = false;
|
|
else
|
|
_machine.UpperROMPaged = true;
|
|
|
|
if ((_RMR & 0x04) != 0)
|
|
_machine.LowerROMPaged = false;
|
|
else
|
|
_machine.LowerROMPaged = true;
|
|
|
|
if (_RMR.Bit(4))
|
|
{
|
|
// reset interrupt counter
|
|
InterruptCounter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// RAMR (register 3) - RAM Banking
|
|
/// This register exists only in CPCs with 128K RAM (like the CPC 6128, or CPCs with Standard Memory Expansions)
|
|
/// Note: In the CPC 6128, the register is a separate PAL that assists the Gate Array chip
|
|
///
|
|
/// Bit Value Function
|
|
/// 7 1 Gate Array function 3
|
|
/// 6 1
|
|
/// 5 b 64K bank number(0..7); always 0 on an unexpanded CPC6128, 0-7 on Standard Memory Expansions
|
|
/// 4 b
|
|
/// 3 b
|
|
/// 2 x RAM Config(0..7)
|
|
/// 1 x ""
|
|
/// 0 x ""
|
|
///
|
|
/// The 3bit RAM Config value is used to access the second 64K of the total 128K RAM that is built into the CPC 6128 or the additional 64K-512K of standard memory expansions.
|
|
/// These contain up to eight 64K ram banks, which are selected with bit 3-5. A standard CPC 6128 only contains bank 0. Normally the register is set to 0, so that only the
|
|
/// first 64K RAM are used (identical to the CPC 464 and 664 models). The register can be used to select between the following eight predefined configurations only:
|
|
///
|
|
/// -Address- 0 1 2 3 4 5 6 7
|
|
/// 0000-3FFF RAM_0 RAM_0 RAM_4 RAM_0 RAM_0 RAM_0 RAM_0 RAM_0
|
|
/// 4000-7FFF RAM_1 RAM_1 RAM_5 RAM_3 RAM_4 RAM_5 RAM_6 RAM_7
|
|
/// 8000-BFFF RAM_2 RAM_2 RAM_6 RAM_2 RAM_2 RAM_2 RAM_2 RAM_2
|
|
/// C000-FFFF RAM_3 RAM_7 RAM_7 RAM_7 RAM_3 RAM_3 RAM_3 RAM_3
|
|
///
|
|
/// The Video RAM is always located in the first 64K, VRAM is in no way affected by this register
|
|
/// </summary>
|
|
private byte _RAMR;
|
|
/// <summary>
|
|
/// This is actually implemented outside of here. These values do nothing.
|
|
/// </summary>
|
|
public byte RAMR
|
|
{
|
|
get { return _RAMR; }
|
|
set
|
|
{
|
|
_RAMR = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The selected screen mode (updated after every HSYNC)
|
|
/// </summary>
|
|
private int ScreenMode;
|
|
|
|
/// <summary>
|
|
/// Simulates the internal 6bit INT counter
|
|
/// </summary>
|
|
private int _interruptCounter;
|
|
public int InterruptCounter
|
|
{
|
|
get { return _interruptCounter; }
|
|
set { _interruptCounter = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interrupts are delayed when a VSYNC occurs
|
|
/// </summary>
|
|
private int VSYNCDelay;
|
|
|
|
/// <summary>
|
|
/// Signals that the frame end has been reached
|
|
/// </summary>
|
|
public bool FrameEnd;
|
|
|
|
/// <summary>
|
|
/// Internal phase clock
|
|
/// </summary>
|
|
private int ClockCounter;
|
|
|
|
/// <summary>
|
|
/// Master frame clock counter
|
|
/// </summary>
|
|
public int FrameClock;
|
|
|
|
/// <summary>
|
|
/// Simulates the gate array memory /WAIT line
|
|
/// </summary>
|
|
private bool WaitLine;
|
|
|
|
/// <summary>
|
|
/// 16-bit address - read from the CRCT
|
|
/// </summary>
|
|
private short _MA;
|
|
private short MA
|
|
{
|
|
get
|
|
{
|
|
_MA = CRCT.MA;
|
|
return _MA;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set when the HSYNC signal is detected from the CRCT
|
|
/// </summary>
|
|
private bool HSYNC;
|
|
|
|
// /// <summary>
|
|
// /// Is set when an initial HSYNC is seen from the CRCT
|
|
// /// On real hardware interrupt generation is based on the falling edge of the HSYNC signal
|
|
// /// So in this emulation, once the falling edge is detected, interrupt processing happens
|
|
// /// </summary>
|
|
// private bool HSYNC_falling;
|
|
|
|
/// <summary>
|
|
/// Used to count HSYNCs during a VSYNC
|
|
/// </summary>
|
|
private int HSYNC_counter;
|
|
|
|
/// <summary>
|
|
/// Set when the VSYNC signal is detected from the CRCT
|
|
/// </summary>
|
|
private bool VSYNC;
|
|
|
|
/// <summary>
|
|
/// TRUE when the /INT pin is held low
|
|
/// </summary>
|
|
private bool InterruptRaised;
|
|
|
|
/// <summary>
|
|
/// Counts the GA cycles that the /INT pin should be held low
|
|
/// </summary>
|
|
private int InterruptHoldCounter;
|
|
|
|
/// <summary>
|
|
/// Set at the start of a new frame
|
|
/// </summary>
|
|
public bool IsNewFrame;
|
|
|
|
/// <summary>
|
|
/// Set when a new line is beginning
|
|
/// </summary>
|
|
public bool IsNewLine;
|
|
|
|
/// <summary>
|
|
/// Horizontal Character Counter
|
|
/// </summary>
|
|
private int HCC;
|
|
|
|
/// <summary>
|
|
/// Vertical Line Counter
|
|
/// </summary>
|
|
private int VLC;
|
|
|
|
/// <summary>
|
|
/// The first video byte fetched
|
|
/// </summary>
|
|
private byte VideoByte1;
|
|
|
|
/// <summary>
|
|
/// The second video byte fetched
|
|
/// </summary>
|
|
private byte VideoByte2;
|
|
|
|
#endregion
|
|
|
|
#region Clock Business
|
|
|
|
/// <summary>
|
|
/// Called every CPU cycle
|
|
/// In reality the GA is clocked at 16Mhz (4 times the frequency of the CPU)
|
|
/// Therefore this method has to take care of:
|
|
/// 4 GA cycles
|
|
/// 1 CRCT cycle every 4 calls
|
|
/// 1 PSG cycle every 4 calls
|
|
/// 1 CPU cycle (uncontended)
|
|
/// </summary>
|
|
public void ClockCycle()
|
|
{
|
|
// gatearray uses 4-phase clock to supply clocks to other devices
|
|
switch (ClockCounter)
|
|
{
|
|
case 0:
|
|
CRCT.ClockCycle();
|
|
WaitLine = false;
|
|
break;
|
|
case 1:
|
|
WaitLine = true;
|
|
// detect new scanline and upcoming new frame on next render cycle
|
|
//FrameDetector();
|
|
break;
|
|
case 2:
|
|
// video fetch
|
|
WaitLine = true;
|
|
//FetchByte(1);
|
|
break;
|
|
case 3:
|
|
// video fetch and render
|
|
WaitLine = true;
|
|
//FetchByte(2);
|
|
GACharacterCycle();
|
|
//PixelGenerator();
|
|
break;
|
|
}
|
|
|
|
if (!HSYNC && CRCT.HSYNC)
|
|
{
|
|
HSYNC = true;
|
|
}
|
|
|
|
// run the interrupt generator routine
|
|
InterruptGenerator();
|
|
|
|
if (!CRCT.HSYNC)
|
|
{
|
|
HSYNC = false;
|
|
}
|
|
|
|
// conditional CPU cycle
|
|
DoConditionalCPUCycle();
|
|
|
|
AdvanceClock();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Increments the internal clock counters by one
|
|
/// </summary>
|
|
private void AdvanceClock()
|
|
{
|
|
FrameClock++;
|
|
ClockCounter++;
|
|
|
|
if (ClockCounter == 4)
|
|
ClockCounter = 0;
|
|
|
|
// check for frame end
|
|
if (FrameClock == FrameLength)
|
|
{
|
|
FrameEnd = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs a 4 Mhz CPU cycle if neccessary
|
|
/// /WAIT line status is a factor here
|
|
/// </summary>
|
|
private void DoConditionalCPUCycle()
|
|
{
|
|
if (!WaitLine)
|
|
{
|
|
// /WAIT line is NOT active
|
|
CPU.ExecuteOne();
|
|
return;
|
|
}
|
|
|
|
// /WAIT line is active
|
|
switch (ClockCounter)
|
|
{
|
|
case 2:
|
|
case 3:
|
|
// gate array video fetch is occuring
|
|
// check for memory access
|
|
if (BUSRQ > 0)
|
|
{
|
|
// memory action upcoming - CPU clock is halted
|
|
CPU.TotalExecutedCycles++;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
// CPU accesses RAM if it's performing a non-opcode read or write
|
|
// assume for now that an opcode fetch is always looking at PC
|
|
if (BUSRQ == PCh)
|
|
{
|
|
// opcode fetch memory action upcoming - CPU clock is halted
|
|
CPU.TotalExecutedCycles++;
|
|
}
|
|
else
|
|
{
|
|
// no fetch, or non-opcode fetch
|
|
CPU.ExecuteOne();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Frame & Interrupt Handling
|
|
|
|
/// <summary>
|
|
/// The CRCT builds the picture in a strange way, so that the top left of the display area is the first pixel from
|
|
/// video RAM. The borders come either side of the HSYNC and VSYNCs later on:
|
|
/// https://web.archive.org/web/20170501112330im_/http://www.grimware.org/lib/exe/fetch.php/documentations/devices/crtc.6845/crtc.standard.video.frame.png?w=800&h=500
|
|
/// Therefore when the gate array initialises, we will attempt end the frame early in order to
|
|
/// sync up at the point where VSYNC is active and HSYNC just begins. This is roughly how a CRT monitor would display the picture.
|
|
/// The CRT would start a new line at the point where an HSYNC is detected.
|
|
/// </summary>
|
|
private void FrameDetector()
|
|
{
|
|
if (CRCT.HSYNC && !IsNewLine)
|
|
{
|
|
// start of a new line on the next render cycle
|
|
IsNewLine = true;
|
|
|
|
// process scanline
|
|
//CRT.CurrentLine.CommitScanline();
|
|
|
|
// check for end of frame
|
|
if (CRCT.VSYNC && !IsNewFrame)
|
|
{
|
|
// start of a new frame on the next render cycle
|
|
IsNewFrame = true;
|
|
//FrameEnd = true;
|
|
VLC = 0;
|
|
}
|
|
else if (!CRCT.VSYNC)
|
|
{
|
|
// increment line counter
|
|
VLC++;
|
|
IsNewFrame = false;
|
|
}
|
|
|
|
HCC = 0;
|
|
|
|
// update screenmode
|
|
//ScreenMode = RMR & 0x03;
|
|
//CRT.CurrentLine.InitScanline(ScreenMode, VLC);
|
|
}
|
|
else if (!CRCT.HSYNC)
|
|
{
|
|
// reset the flags
|
|
IsNewLine = false;
|
|
IsNewFrame = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles interrupt generation
|
|
/// </summary>
|
|
private void InterruptGenerator()
|
|
{
|
|
if (HSYNC && !CRCT.HSYNC)
|
|
{
|
|
// falling edge of the HSYNC detected
|
|
InterruptCounter++;
|
|
|
|
if (CRCT.VSYNC)
|
|
{
|
|
if (HSYNC_counter >= 2)
|
|
{
|
|
// x2 HSYNC have happened during VSYNC
|
|
if (InterruptCounter >= 32)
|
|
{
|
|
// no interrupt
|
|
InterruptCounter = 0;
|
|
}
|
|
else if (InterruptCounter < 32)
|
|
{
|
|
// interrupt
|
|
InterruptRaised = true;
|
|
InterruptCounter = 0;
|
|
}
|
|
|
|
HSYNC_counter = 0;
|
|
}
|
|
else
|
|
{
|
|
HSYNC_counter++;
|
|
}
|
|
}
|
|
|
|
if (InterruptCounter == 52)
|
|
{
|
|
// gatearray should raise an interrupt
|
|
InterruptRaised = true;
|
|
InterruptCounter = 0;
|
|
}
|
|
}
|
|
|
|
if (InterruptRaised)
|
|
{
|
|
// interrupt should been raised
|
|
CPU.FlagI = true;
|
|
InterruptHoldCounter++;
|
|
|
|
// the INT signal should be held low for 1.4us.
|
|
// in gatearray cycles, this equates to 22.4
|
|
// we will round down to 22 for emulation purposes
|
|
if (InterruptHoldCounter >= 22)
|
|
{
|
|
CPU.FlagI = false;
|
|
InterruptRaised = false;
|
|
InterruptHoldCounter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Rendering Business
|
|
|
|
/// <summary>
|
|
/// Builds up current scanline character information
|
|
/// Ther GA modifies HSYNC and VSYNC signals before they are sent to the monitor
|
|
/// This is handled here
|
|
/// Runs at 1Mhz
|
|
/// </summary>
|
|
private void GACharacterCycle()
|
|
{
|
|
if (CRCT.VSYNC && CRCT.HSYNC)
|
|
{
|
|
// both hsync and vsync active
|
|
CurrentLine.AddCharacter(Phase.HSYNCandVSYNC);
|
|
}
|
|
else if (CRCT.VSYNC)
|
|
{
|
|
// vsync is active but hsync is not
|
|
CurrentLine.AddCharacter(Phase.VSYNC);
|
|
}
|
|
else if (CRCT.HSYNC)
|
|
{
|
|
// hsync is active but vsync is not
|
|
CurrentLine.AddCharacter(Phase.HSYNC);
|
|
}
|
|
else if (!CRCT.DISPTMG)
|
|
{
|
|
// border generation
|
|
CurrentLine.AddCharacter(Phase.BORDER);
|
|
}
|
|
else if (CRCT.DISPTMG)
|
|
{
|
|
// pixels generated from video RAM
|
|
CurrentLine.AddCharacter(Phase.DISPLAY);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds the upcoming video RAM addresses for the next scanline
|
|
/// Firmware default size is 80 (40 characters - 2 bytes per character)
|
|
/// </summary>
|
|
private ushort[] NextVidRamLine = new ushort[40 * 2];
|
|
|
|
/// <summary>
|
|
/// The current character line we are working from
|
|
/// </summary>
|
|
private CharacterLine CurrentLine;
|
|
|
|
/// <summary>
|
|
/// List of screen lines as they are built up
|
|
/// </summary>
|
|
private List<CharacterLine> ScreenLines = new List<CharacterLine>();
|
|
|
|
/// <summary>
|
|
/// Pixel value lookups for every scanline byte value
|
|
/// Based on the lookup at https://github.com/gavinpugh/xnacpc
|
|
/// </summary>
|
|
private int[][] ByteLookup = new int[4][];
|
|
private void InitByteLookup()
|
|
{
|
|
int pix;
|
|
for (int m = 0; m < 4; m++)
|
|
{
|
|
int pos = 0;
|
|
ByteLookup[m] = new int[256 * 8];
|
|
for (int b = 0; b < 256; b++)
|
|
{
|
|
switch (m)
|
|
{
|
|
case 0:
|
|
pix = b & 0xaa;
|
|
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
|
|
for (int c = 0; c < 4; c++)
|
|
ByteLookup[m][pos++] = pix;
|
|
pix = b & 0x55;
|
|
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
|
|
for (int c = 0; c < 4; c++)
|
|
ByteLookup[m][pos++] = pix;
|
|
break;
|
|
case 1:
|
|
pix = (((b & 0x80) >> 7) | ((b & 0x08) >> 2));
|
|
ByteLookup[m][pos++] = pix;
|
|
ByteLookup[m][pos++] = pix;
|
|
pix = (((b & 0x40) >> 6) | ((b & 0x04) >> 1));
|
|
ByteLookup[m][pos++] = pix;
|
|
ByteLookup[m][pos++] = pix;
|
|
pix = (((b & 0x20) >> 5) | (b & 0x02));
|
|
ByteLookup[m][pos++] = pix;
|
|
ByteLookup[m][pos++] = pix;
|
|
pix = (((b & 0x10) >> 4) | ((b & 0x01) << 1));
|
|
ByteLookup[m][pos++] = pix;
|
|
ByteLookup[m][pos++] = pix;
|
|
break;
|
|
case 2:
|
|
for (int i = 7; i >= 0; i--)
|
|
ByteLookup[m][pos++] = ((b & (1 << i)) != 0) ? 1 : 0;
|
|
break;
|
|
case 3:
|
|
pix = b & 0xaa;
|
|
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
|
|
for (int c = 0; c < 4; c++)
|
|
ByteLookup[m][pos++] = pix;
|
|
pix = b & 0x55;
|
|
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
|
|
for (int c = 0; c < 4; c++)
|
|
ByteLookup[m][pos++] = pix;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs at HSYNC *AFTER* the scanline has been commmitted
|
|
/// Sets up the upcoming memory addresses for the next scanline
|
|
/// </summary>
|
|
private void CalculateNextScreenMemory()
|
|
{
|
|
var verCharCount = CRCT.VCC;
|
|
var verRasCount = CRCT.VLC;
|
|
|
|
var screenWidthByteCount = CRCT.DisplayWidth * 2;
|
|
NextVidRamLine = new ushort[screenWidthByteCount * 2];
|
|
var screenHeightCharCount = CRCT.DisplayHeightInChars;
|
|
var screenAddress = CRCT.MA;
|
|
|
|
int baseAddress = ((screenAddress << 2) & 0xf000);
|
|
int offset = (screenAddress * 2) & 0x7ff;
|
|
|
|
int x = offset + ((verCharCount * screenWidthByteCount) & 0x7ff);
|
|
int y = baseAddress + (verRasCount * 0x800);
|
|
|
|
for (int b = 0; b < screenWidthByteCount; b++)
|
|
{
|
|
NextVidRamLine[b] = (ushort)(y + x);
|
|
x++;
|
|
x &= 0x7ff;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called at the start of HSYNC, this renders the currently built-up scanline
|
|
/// </summary>
|
|
private void RenderScanline()
|
|
{
|
|
// memory addresses
|
|
int cRow = CRCT.VCC;
|
|
int cRas = CRCT.VLC;
|
|
|
|
int screenByteWidth = CRCT.DisplayWidth * 2;
|
|
var screenHeightCharCount = CRCT.DisplayHeightInChars;
|
|
//CalculateNextScreenMemory();
|
|
var crctAddr = CRCT.DStartHigh << 8;
|
|
crctAddr |= CRCT.DStartLow;
|
|
var baseAddr = ((crctAddr << 2) & (0xF000)); //CRCT.VideoPageBase;//
|
|
var baseOffset = (crctAddr * 2) & 0x7FF; //CRCT.VideoRAMOffset * 2; //
|
|
var xA = baseOffset + ((cRow * screenByteWidth) & 0x7ff);
|
|
var yA = baseAddr + (cRas * 2048);
|
|
|
|
// border and display
|
|
int pix = 0;
|
|
int scrByte = 0;
|
|
|
|
for (int i = 0; i < CurrentLine.PhaseCount; i++)
|
|
{
|
|
// every character renders 8 pixels
|
|
switch (CurrentLine.Phases[i])
|
|
{
|
|
case Phase.NONE:
|
|
break;
|
|
|
|
case Phase.HSYNC:
|
|
break;
|
|
case Phase.HSYNCandVSYNC:
|
|
break;
|
|
case Phase.VSYNC:
|
|
break;
|
|
case Phase.BORDER:
|
|
// output current border colour
|
|
for (pix = 0; pix < 16; pix++)
|
|
{
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[16]]);
|
|
}
|
|
break;
|
|
case Phase.DISPLAY:
|
|
// each character references 2 bytes in video RAM
|
|
byte data;
|
|
|
|
for (int by = 0; by < 2; by++)
|
|
{
|
|
ushort addr = (ushort)(yA + xA);
|
|
data = _machine.FetchScreenMemory(addr);
|
|
scrByte++;
|
|
|
|
switch (CurrentLine.ScreenMode)
|
|
{
|
|
case 0:
|
|
pix = data & 0xaa;
|
|
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
|
|
for (int c = 0; c < 4; c++)
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
pix = data & 0x55;
|
|
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
|
|
for (int c = 0; c < 4; c++)
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
break;
|
|
case 1:
|
|
pix = (((data & 0x80) >> 7) | ((data & 0x08) >> 2));
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
pix = (((data & 0x40) >> 6) | ((data & 0x04) >> 1));
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
pix = (((data & 0x20) >> 5) | (data & 0x02));
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
pix = (((data & 0x10) >> 4) | ((data & 0x01) << 1));
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
break;
|
|
case 2:
|
|
pix = data;
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(7) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(6) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(5) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(4) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(3) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(2) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(1) ? 1 : 0]]);
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix.Bit(0) ? 1 : 0]]);
|
|
break;
|
|
case 3:
|
|
pix = data & 0xaa;
|
|
pix = (((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02) << 2));
|
|
for (int c = 0; c < 4; c++)
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
pix = data & 0x55;
|
|
pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01) << 3));
|
|
for (int c = 0; c < 4; c++)
|
|
CurrentLine.Pixels.Add(CPCHardwarePalette[ColourRegisters[pix]]);
|
|
break;
|
|
}
|
|
|
|
xA++;
|
|
xA &= 0x7ff;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add to the list
|
|
ScreenLines.Add(new CharacterLine
|
|
{
|
|
ScreenMode = CurrentLine.ScreenMode,
|
|
Phases = CurrentLine.Phases.ToList(),
|
|
Pixels = CurrentLine.Pixels.ToList()
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Called when the Z80 acknowledges an interrupt
|
|
/// </summary>
|
|
public void IORQA()
|
|
{
|
|
// bit 5 of the interrupt counter is reset
|
|
InterruptCounter &= ~(1 << 5);
|
|
}
|
|
|
|
private int slCounter = 0;
|
|
private int slBackup = 0;
|
|
|
|
/// <summary>
|
|
/// Fired when the CRCT flags HSYNC
|
|
/// </summary>
|
|
public void OnHSYNC()
|
|
{
|
|
HSYNC = true;
|
|
slCounter++;
|
|
|
|
// commit the scanline
|
|
RenderScanline();
|
|
|
|
// setup vid memory for next scanline
|
|
CalculateNextScreenMemory();
|
|
|
|
if (CRCT.VLC == 0)
|
|
{
|
|
// update screenmode
|
|
ScreenMode = _RMR & 0x03;
|
|
}
|
|
|
|
// setup scanline for next
|
|
CurrentLine.Clear(ScreenMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fired when the CRCT flags VSYNC
|
|
/// </summary>
|
|
public void OnVSYNC()
|
|
{
|
|
FrameEnd = true;
|
|
slBackup = slCounter;
|
|
slCounter = 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IVideoProvider
|
|
|
|
public int[] ScreenBuffer;
|
|
|
|
private int _virtualWidth;
|
|
private int _virtualHeight;
|
|
private int _bufferWidth;
|
|
private int _bufferHeight;
|
|
|
|
public int BackgroundColor
|
|
{
|
|
get { return CPCHardwarePalette[0]; }
|
|
}
|
|
|
|
public int VirtualWidth
|
|
{
|
|
get { return _virtualWidth; }
|
|
set { _virtualWidth = value; }
|
|
}
|
|
|
|
public int VirtualHeight
|
|
{
|
|
get { return _virtualHeight; }
|
|
set { _virtualHeight = value; }
|
|
}
|
|
|
|
public int BufferWidth
|
|
{
|
|
get { return _bufferWidth; }
|
|
set { _bufferWidth = value; }
|
|
}
|
|
|
|
public int BufferHeight
|
|
{
|
|
get { return _bufferHeight; }
|
|
set { _bufferHeight = value; }
|
|
}
|
|
|
|
public int SysBufferWidth;
|
|
public int SysBufferHeight;
|
|
|
|
public int VsyncNumerator
|
|
{
|
|
get { return 200000000; }
|
|
set { }
|
|
}
|
|
|
|
public int VsyncDenominator
|
|
{
|
|
get { return Z80ClockSpeed; }
|
|
}
|
|
|
|
public int[] GetVideoBuffer()
|
|
{
|
|
// get only lines that have pixel data
|
|
var lines = ScreenLines.Where(a => a.Pixels.Count > 0);
|
|
|
|
int pos = 0;
|
|
int lCount = 0;
|
|
foreach (var l in lines)
|
|
{
|
|
var lCop = l.Pixels.ToList();
|
|
var len = l.Pixels.Count;
|
|
if (l.Phases.Contains(Phase.VSYNC) && l.Phases.Contains(Phase.BORDER))
|
|
continue;
|
|
|
|
if (len < 320)
|
|
continue;
|
|
|
|
var pad = BufferWidth - len;
|
|
if (pad < 0)
|
|
{
|
|
// trim the left and right
|
|
var padPos = pad * -1;
|
|
var excessL = padPos / 2;
|
|
var excessR = excessL + (padPos % 2);
|
|
for (int i = 0; i < excessL; i++)
|
|
{
|
|
var lThing = lCop.First();
|
|
|
|
lCop.Remove(lThing);
|
|
}
|
|
for (int i = 0; i < excessL; i++)
|
|
{
|
|
var lThing = lCop.Last();
|
|
|
|
lCop.Remove(lThing);
|
|
}
|
|
}
|
|
|
|
var lPad = pad / 2;
|
|
var rPad = lPad + (pad % 2);
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
lCount++;
|
|
|
|
for (int pL = 0; pL < lPad; pL++)
|
|
{
|
|
ScreenBuffer[pos++] = 0;
|
|
}
|
|
|
|
for (int pix = 0; pix < lCop.Count; pix++)
|
|
{
|
|
ScreenBuffer[pos++] = lCop[pix];
|
|
}
|
|
|
|
for (int pR = 0; pR < rPad; pR++)
|
|
{
|
|
ScreenBuffer[pos++] = 0;
|
|
}
|
|
}
|
|
|
|
if (lCount >= BufferHeight - 2)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
ScreenLines.Clear();
|
|
|
|
return ScreenBuffer;
|
|
/*
|
|
switch (borderType)
|
|
{
|
|
// crop to 768x272 (544)
|
|
// borders 64px - 64 scanlines
|
|
case AmstradCPC.BorderType.Uniform:
|
|
/*
|
|
var slSize = 64;
|
|
var dispLines = (24 * 8) * 2;
|
|
var origTopBorder = (7 * 8) * 2;
|
|
var origBotBorder = (5 * 8) * 2;
|
|
|
|
var lR = 16;
|
|
var rR = 16;
|
|
|
|
var trimTop = origTopBorder - slSize;
|
|
|
|
var startP = SysBufferWidth * (origTopBorder - 64);
|
|
var index1 = 0;
|
|
|
|
// line by line
|
|
int cnt = 0;
|
|
for (int line = startP; line < ScreenBuffer.Length; line += SysBufferWidth)
|
|
{
|
|
cnt++;
|
|
// pixels in line
|
|
for (int p = lR; p < SysBufferWidth - rR; p++)
|
|
{
|
|
if (index1 == croppedBuffer.Length)
|
|
break;
|
|
|
|
croppedBuffer[index1++] = ScreenBuffer[line + p];
|
|
}
|
|
}
|
|
return croppedBuffer;
|
|
*/
|
|
/*
|
|
var slWidth = BufferWidth;
|
|
return ScreenBuffer;
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
*/
|
|
//return ScreenBuffer;
|
|
}
|
|
|
|
public void SetupScreenSize()
|
|
{
|
|
SysBufferWidth = 800;
|
|
SysBufferHeight = 600;
|
|
BufferHeight = SysBufferHeight;
|
|
BufferWidth = SysBufferWidth;
|
|
VirtualHeight = BufferHeight;
|
|
VirtualWidth = BufferWidth;
|
|
ScreenBuffer = new int[BufferWidth * BufferHeight];
|
|
croppedBuffer = ScreenBuffer;
|
|
|
|
switch (borderType)
|
|
{
|
|
case AmstradCPC.BorderType.Uncropped:
|
|
break;
|
|
|
|
case AmstradCPC.BorderType.Uniform:
|
|
BufferWidth = 800;
|
|
BufferHeight = 600;
|
|
VirtualHeight = BufferHeight;
|
|
VirtualWidth = BufferWidth;
|
|
croppedBuffer = new int[BufferWidth * BufferHeight];
|
|
break;
|
|
|
|
case AmstradCPC.BorderType.Widescreen:
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected int[] croppedBuffer;
|
|
|
|
private AmstradCPC.BorderType _borderType;
|
|
|
|
public AmstradCPC.BorderType borderType
|
|
{
|
|
get { return _borderType; }
|
|
set { _borderType = value; }
|
|
}
|
|
|
|
#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));
|
|
byte portUpper = (byte)(port >> 8);
|
|
byte portLower = (byte)(port & 0xff);
|
|
|
|
// The gate array is selected when bit 15 of the I/O port address is set to "0" and bit 14 of the I/O port address is set to "1"
|
|
bool accessed = false;
|
|
if (!portUpper.Bit(7) && portUpper.Bit(6))
|
|
accessed = true;
|
|
|
|
if (!accessed)
|
|
return accessed;
|
|
|
|
// Bit 9 and 8 of the data byte define the function to access
|
|
if (!dataBits[6] && !dataBits[7])
|
|
{
|
|
// select pen
|
|
PENR = (byte)result;
|
|
}
|
|
|
|
if (dataBits[6] && !dataBits[7])
|
|
{
|
|
// select colour for selected pen
|
|
INKR = (byte)result;
|
|
}
|
|
|
|
if (!dataBits[6] && dataBits[7])
|
|
{
|
|
// select screen mode, ROM configuration and interrupt control
|
|
RMR = (byte)result;
|
|
}
|
|
|
|
if (dataBits[6] && dataBits[7])
|
|
{
|
|
// RAM memory management
|
|
RAMR = (byte)result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Serialization
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection("GateArray");
|
|
ser.SyncEnum(nameof(ChipType), ref ChipType);
|
|
ser.Sync(nameof(_PENR), ref _PENR);
|
|
ser.Sync(nameof(_INKR), ref _INKR);
|
|
ser.Sync(nameof(_RMR), ref _RMR);
|
|
ser.Sync(nameof(_RAMR), ref _RAMR);
|
|
ser.Sync(nameof(ColourRegisters), ref ColourRegisters, false);
|
|
ser.Sync(nameof(CurrentPen), ref CurrentPen);
|
|
ser.Sync(nameof(ClockCounter), ref ClockCounter);
|
|
ser.Sync(nameof(FrameClock), ref FrameClock);
|
|
ser.Sync(nameof(FrameEnd), ref FrameEnd);
|
|
ser.Sync(nameof(WaitLine), ref WaitLine);
|
|
ser.Sync(nameof(_interruptCounter), ref _interruptCounter);
|
|
ser.Sync(nameof(VSYNCDelay), ref VSYNCDelay);
|
|
ser.Sync(nameof(ScreenMode), ref ScreenMode);
|
|
ser.Sync(nameof(HSYNC), ref HSYNC);
|
|
//ser.Sync(nameof(HSYNC_falling), ref HSYNC_falling);
|
|
ser.Sync(nameof(HSYNC_counter), ref HSYNC_counter);
|
|
ser.Sync(nameof(VSYNC), ref VSYNC);
|
|
ser.Sync(nameof(InterruptRaised), ref InterruptRaised);
|
|
ser.Sync(nameof(InterruptHoldCounter), ref InterruptHoldCounter);
|
|
ser.Sync(nameof(_MA), ref _MA);
|
|
ser.Sync(nameof(IsNewFrame), ref IsNewFrame);
|
|
ser.Sync(nameof(IsNewLine), ref IsNewLine);
|
|
ser.Sync(nameof(HCC), ref HCC);
|
|
ser.Sync(nameof(VLC), ref VLC);
|
|
ser.Sync(nameof(VideoByte1), ref VideoByte1);
|
|
ser.Sync(nameof(VideoByte2), ref VideoByte2);
|
|
ser.Sync(nameof(NextVidRamLine), ref NextVidRamLine, false);
|
|
ser.EndSection();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Enums, Classes & Lookups
|
|
|
|
/// <summary>
|
|
/// Represents a single scanline (in characters)
|
|
/// </summary>
|
|
public class CharacterLine
|
|
{
|
|
/// <summary>
|
|
/// Screenmode is defined at each HSYNC (start of a new character line)
|
|
/// Therefore we pass the mode in via constructor
|
|
/// </summary>
|
|
//public CharacterLine(int screenMode)
|
|
//{
|
|
//ScreenMode = screenMode;
|
|
//}
|
|
|
|
public int ScreenMode = 1;
|
|
public List<Phase> Phases = new List<Phase>();
|
|
public List<int> Pixels = new List<int>();
|
|
|
|
/// <summary>
|
|
/// Adds a new horizontal character to the list
|
|
/// </summary>
|
|
public void AddCharacter(Phase phase)
|
|
{
|
|
Phases.Add(phase);
|
|
}
|
|
|
|
public int PhaseCount
|
|
{
|
|
get { return Phases.Count(); }
|
|
}
|
|
|
|
public void Clear(int screenMode)
|
|
{
|
|
ScreenMode = screenMode;
|
|
Phases.Clear();
|
|
Pixels.Clear();
|
|
}
|
|
}
|
|
|
|
[Flags]
|
|
public enum Phase : int
|
|
{
|
|
/// <summary>
|
|
/// Nothing
|
|
/// </summary>
|
|
NONE = 0,
|
|
/// <summary>
|
|
/// Border is being rendered
|
|
/// </summary>
|
|
BORDER = 1,
|
|
/// <summary>
|
|
/// Display rendered from video RAM
|
|
/// </summary>
|
|
DISPLAY = 2,
|
|
/// <summary>
|
|
/// HSYNC in progress
|
|
/// </summary>
|
|
HSYNC = 3,
|
|
/// <summary>
|
|
/// VSYNC in process
|
|
/// </summary>
|
|
VSYNC = 4,
|
|
/// <summary>
|
|
/// HSYNC occurs within a VSYNC
|
|
/// </summary>
|
|
HSYNCandVSYNC = 5
|
|
}
|
|
|
|
public enum GateArrayType
|
|
{
|
|
/// <summary>
|
|
/// CPC 464
|
|
/// The first version of the Gate Array is the 40007 and was released with the CPC 464
|
|
/// </summary>
|
|
Amstrad40007,
|
|
/// <summary>
|
|
/// CPC 664
|
|
/// Later, the CPC 664 came out fitted with the 40008 version (and at the same time, the CPC 464 was also upgraded with this version).
|
|
/// This version is pinout incompatible with the 40007 (that's why the upgraded 464 of this period have two Gate Array slots on the motherboard,
|
|
/// one for a 40007 and one for a 40008)
|
|
/// </summary>
|
|
Amstrad40008,
|
|
/// <summary>
|
|
/// CPC 6128
|
|
/// The CPC 6128 was released with the 40010 version (and the CPC 464 and 664 manufactured at that time were also upgraded to this version).
|
|
/// The 40010 is pinout compatible with the previous 40008
|
|
/// </summary>
|
|
Amstrad40010,
|
|
/// <summary>
|
|
/// Costdown CPC
|
|
/// In the last serie of CPC 464 and 6128 produced by Amstrad in 1988, a small ASIC chip have been used to reduce the manufacturing costs.
|
|
/// This ASIC emulates the Gate Array, the PAL and the CRTC 6845. And no, there is no extra features like on the Amstrad Plus.
|
|
/// The only noticeable difference seems to be about the RGB output levels which are not exactly the same than those produced with a real Gate Array
|
|
/// </summary>
|
|
Amstrad40226,
|
|
/// <summary>
|
|
/// Plus & GX-4000
|
|
/// All the Plus range is built upon a bigger ASIC chip which is integrating many features of the classic CPC (FDC, CRTC, PPI, Gate Array/PAL) and all
|
|
/// the new Plus specific features. The Gate Array on the Plus have a new register, named RMR2, to expand the ROM mapping functionnalities of the machine.
|
|
/// This register requires to be unlocked first to be available. And finally, the RGB levels produced by the ASIC on the Plus are noticeably differents
|
|
/// </summary>
|
|
Amstrad40489,
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|