diff --git a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs
index f915a16630..226da42757 100644
--- a/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs
+++ b/src/BizHawk.Client.Common/movie/PlatformFrameRates.cs
@@ -72,7 +72,8 @@ namespace BizHawk.Client.Common
["INTV"] = 59.92,
["ZXSpectrum_PAL"] = 50.080128205,
- ["AmstradCPC_PAL"] = 50.08012820512821,
+ ["AmstradCPC_PAL"] = 50.08012820512821, // = 1 / ((1024 * 312) / 16,000,000)
+
["UZE"] = 1125000.0 / 18733.0, // = 8 * 315000000 / 88 / 1820 / 262 ≈ 60.05444936742646666
["VEC"] = 50,
["O2"] = 89478485.0 / 1495643, // 59.8260982065907439141559850846
diff --git a/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
index 6aad757ca7..31997b07f6 100755
--- a/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
+++ b/src/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj
@@ -267,10 +267,10 @@
-
+
-
-
+
+
@@ -330,9 +330,9 @@
-
-
-
+
+
+
diff --git a/src/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs b/src/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs
index 1ff73b119f..eb43c80696 100644
--- a/src/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs
+++ b/src/BizHawk.Client.EmuHawk/config/AmstradCPC/AmstradCPCCoreEmulationSettings.cs
@@ -99,9 +99,8 @@ namespace BizHawk.Client.EmuHawk
{
lblBorderInfo.Text = type switch
{
- AmstradCPC.BorderType.Uniform => "Attempts to equalize the border areas",
- AmstradCPC.BorderType.Uncropped => "Pretty much the signal the gate array is generating (looks pants)",
- AmstradCPC.BorderType.Widescreen => "Top and bottom border removed so that the result is *almost* 16:9",
+ AmstradCPC.BorderType.Visible => "Approximates what you see on a CPC monitor",
+ AmstradCPC.BorderType.Uncropped => "The full display area",
_ => lblBorderInfo.Text
};
}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs
index a8ca51e56c..89da0719dd 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.ISettable.cs
@@ -106,7 +106,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
[DisplayName("Border type")]
[Description("Select how to show the border area")]
- [DefaultValue(BorderType.Uniform)]
+ [DefaultValue(BorderType.Visible)]
public BorderType BorderType { get; set; }
public AmstradCPCSyncSettings Clone()
@@ -331,19 +331,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public enum BorderType
{
///
- /// Attempts to equalise the border areas
+ /// Roughly what you might see on an Amstrad monitor
///
- Uniform,
+ Visible,
///
- /// Pretty much the signal the gate array is generating (looks shit)
+ /// The full display area
///
Uncropped,
-
- ///
- /// Top and bottom border removed so that the result is *almost* 16:9
- ///
- Widescreen,
}
}
}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs
index abf68689bc..81c0a87a8d 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/AmstradCPC.cs
@@ -57,7 +57,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Register(_tracer);
ser.Register(_cpu);
- ser.Register(_machine.GateArray);
+ //ser.Register(_machine.GateArray);
+ ser.Register(_machine.CRTScreen);
ser.Register(new StateSerializer(SyncState));
// initialize sound mixer and attach the various ISoundProvider devices
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/SoundOutput/AY38912.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/AY38912.cs
similarity index 100%
rename from src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/SoundOutput/AY38912.cs
rename to src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/AY38912.cs
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/SoundOutput/Beeper.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Beeper.cs
similarity index 100%
rename from src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/SoundOutput/Beeper.cs
rename to src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Beeper.cs
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type0.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type0.cs
new file mode 100644
index 0000000000..3bc6feb7a2
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type0.cs
@@ -0,0 +1,347 @@
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// TYPE 0
+ /// - Hitachi HD6845S http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf
+ /// - UMC UM6845 http://www.cpcwiki.eu/imgs/1/13/Um6845.umc.pdf
+ ///
+ public class CRTC_Type0 : CRTC
+ {
+ ///
+ /// Defined CRTC type number
+ ///
+ public override int CrtcType => 0;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public override void Clock()
+ {
+ base.Clock();
+
+ int maxScanLine;
+
+ if (HCC == R0_HorizontalTotal)
+ {
+ // end of displayable area reached
+ // set up for the next line
+ HCC = 0;
+
+ if (R8_Interlace == 3)
+ {
+ // in interlace sync and video mask off bit 0 of the max scanline address
+ maxScanLine = R9_MaxScanline & 0b11110;
+ }
+ else
+ {
+ maxScanLine = R9_MaxScanline;
+ }
+
+ if (VLC == maxScanLine)
+ {
+ // we have reached the final scanline within this vertical character row
+ // move to next character
+ VLC = 0;
+
+ // TODO: implement vertical adjust
+
+
+ if (VCC == R4_VerticalTotal)
+ {
+ // check the interlace mode
+ if (R8_Interlace.Bit(0))
+ {
+ // toggle the field
+ _field = !_field;
+ }
+ else
+ {
+ // stay on the even field
+ _field = false;
+ }
+
+ // we have reached the end of the vertical display area
+ // address loaded from start address register at the top of each field
+ _vmaRowStart = (Register[R12_START_ADDR_H] << 8) | Register[R13_START_ADDR_L];
+
+ // reset the vertical character counter
+ VCC = 0;
+
+ // increment field counter
+ CFC++;
+ }
+ else
+ {
+ // row start address is increased by Horiztonal Displayed
+ _vmaRowStart += R1_HorizontalDisplayed;
+
+ // increment vertical character counter
+ VCC++;
+ }
+ }
+ else
+ {
+ // next scanline
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // vertical line counter increments by 2
+ VLC += 2;
+
+ // ensure vertical line counter is an even value
+ VLC &= ~1;
+ }
+ else
+ {
+ // non-interlace mode
+ // increment vertical line counter
+ VLC++;
+ }
+ }
+
+ // MA set to row start at the beginning of each line
+ _vma = _vmaRowStart;
+ }
+ else
+ {
+ // next horizontal character (1us)
+ // increment horizontal character counter
+ HCC++;
+
+ // increment VMA
+ _vma++;
+ }
+
+ hssstart = false;
+ hhclock = false;
+
+ if (HCC == R2_HorizontalSyncPosition)
+ {
+ // start of horizontal sync
+ hssstart = true;
+ }
+
+ if (HCC == R2_HorizontalSyncPosition / 2)
+ {
+ // we are half way through the line
+ hhclock = true;
+ }
+
+ /* Hor active video */
+ if (HCC == 0)
+ {
+ // active display
+ latch_hdisp = true;
+ }
+
+ if (HCC == R1_HorizontalDisplayed)
+ {
+ // inactive display
+ latch_hdisp = false;
+ }
+
+ /* Hor sync */
+ if (hssstart || // start of horizontal sync
+ HSYNC) // already in horizontal sync
+ {
+ // start of horizontal sync
+ HSYNC = true;
+ HSC++;
+ }
+ else
+ {
+ // reset hsync counter
+ HSC = 0;
+ }
+
+ if (HSC == R3_HorizontalSyncWidth)
+ {
+ // end of horizontal sync
+ HSYNC = false;
+ }
+
+ /* Ver active video */
+ if (VCC == 0)
+ {
+ // active display
+ latch_vdisp = true;
+ }
+
+ if (VCC == R6_VerticalDisplayed)
+ {
+ // inactive display
+ latch_vdisp = false;
+ }
+
+ // vertical sync occurs at different times depending on the interlace field
+ // even field: the same time as HSYNC
+ // odd field: half a line later than HSYNC
+ if ((!_field && hssstart) || (_field && hhclock))
+ {
+ if ((VCC == R7_VerticalSyncPosition && VLC == 0) // vsync starts on the first line
+ || VSYNC) // vsync is already in progress
+ {
+ // start of vertical sync
+ VSYNC = true;
+ // increment vertical sync counter
+ VSC++;
+ }
+ else
+ {
+ // reset vsync counter
+ VSC = 0;
+ }
+
+ if (VSYNC && VSC == R3_VerticalSyncWidth - 1)
+ {
+ // end of vertical sync
+ VSYNC = false;
+ }
+ }
+
+
+ /* Address Generation */
+ int line = VLC;
+
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // the least significant bit is based on the current field number
+ int fNum = _field ? 1 : 0;
+ int lNum = VLC.Bit(0) ? 1 : 0;
+ line &= ~1;
+
+ _RA = line & (fNum | lNum);
+ }
+ else
+ {
+ // raster address is just the VLC
+ _RA = VLC;
+ }
+
+ _LA = _vma;
+
+ // DISPTMG Generation
+ if (!latch_hdisp || !latch_vdisp)
+ {
+ // HSYNC output pin is fed through a NOR gate with either 2 or 3 inputs
+ // - H Display
+ // - V Display
+ // - TODO: R8 DISPTMG Skew (only on certain CRTC types)
+ DISPTMG = false;
+ }
+ else
+ {
+ DISPTMG = true;
+ }
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected override bool ReadRegister(ref int data)
+ {
+ bool addressed;
+
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R3_SYNC_WIDTHS:
+ case R4_V_TOTAL:
+ case R5_V_TOTAL_ADJUST:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R8_INTERLACE_MODE:
+ case R9_MAX_SL_ADDRESS:
+ case R10_CURSOR_START:
+ case R11_CURSOR_END:
+ // write-only registers return 0x0 on Type 0 CRTC
+ addressed = true;
+ data = 0;
+ break;
+ case R12_START_ADDR_H:
+ case R14_CURSOR_H:
+ case R16_LIGHT_PEN_H:
+ // read/write registers (6bit)
+ addressed = true;
+ data = Register[AddressRegister] & 0x3F;
+ break;
+ case R13_START_ADDR_L:
+ case R15_CURSOR_L:
+ case R17_LIGHT_PEN_L:
+ // read/write regiters (8bit)
+ addressed = true;
+ data = Register[AddressRegister];
+ break;
+ default:
+ // non-existent registers return 0x0
+ addressed = true;
+ data = 0;
+ break;
+ }
+
+ return addressed;
+ }
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected override void WriteRegister(int data)
+ {
+ byte v = (byte)data;
+
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R3_SYNC_WIDTHS:
+ case R13_START_ADDR_L:
+ case R15_CURSOR_L:
+ // 8-bit registers
+ Register[AddressRegister] = v;
+ break;
+ case R4_V_TOTAL:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R10_CURSOR_START:
+ // 7-bit registers
+ Register[AddressRegister] = (byte)(v & 0x7F);
+ break;
+ case R12_START_ADDR_H:
+ case R14_CURSOR_H:
+ // 6-bit registers
+ Register[AddressRegister] = (byte)(v & 0x3F);
+ break;
+ case R5_V_TOTAL_ADJUST:
+ case R9_MAX_SL_ADDRESS:
+ case R11_CURSOR_END:
+ // 5-bit registers
+ Register[AddressRegister] = (byte)(v & 0x1F);
+ break;
+ case R8_INTERLACE_MODE:
+ // Interlace & skew masks bits 2 & 3
+ Register[AddressRegister] = (byte)(v & 0xF3);
+ break;
+ }
+ }
+
+ ///
+ /// CRTC 0 has no status register
+ ///
+ protected override bool ReadStatus(ref int data)
+ {
+ // ACCC1.8 - 21.3.2
+ // CRTC0 randomly apparently returns 255 or 127 on this port
+
+ // For the purposes of Bizhawk determinism, we will return one of the above values based on the current HCC
+ data = HCC.Bit(0) ? 255 : 127;
+ return true;
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type1.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type1.cs
new file mode 100644
index 0000000000..ef0fcffffb
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type1.cs
@@ -0,0 +1,361 @@
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// TYPE 1
+ /// - UMC UM6845R http://www.cpcwiki.eu/imgs/b/b5/Um6845r.umc.pdf
+ ///
+ public class CRTC_Type1 : CRTC
+ {
+ ///
+ /// Defined CRTC type number
+ ///
+ public override int CrtcType => 1;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public override void Clock()
+ {
+ base.Clock();
+
+ int maxScanLine;
+
+ if (HCC == R0_HorizontalTotal)
+ {
+ // end of displayable area reached
+ // set up for the next line
+ HCC = 0;
+
+ // TODO: handle interlace setup
+ if (R8_Interlace == 3)
+ {
+ // in interlace sync and video mask off bit 0 of the max scanline address
+ maxScanLine = R9_MaxScanline & 0b11110;
+ }
+ else
+ {
+ maxScanLine = R9_MaxScanline;
+ }
+
+ if (VLC == maxScanLine)
+ {
+ // we have reached the final scanline within this vertical character row
+ // move to next character
+ VLC = 0;
+
+ // TODO: implement vertical adjust
+
+
+ if (VCC == R4_VerticalTotal)
+ {
+ // check the interlace mode
+ if (R8_Interlace.Bit(0))
+ {
+ // toggle the field
+ _field = !_field;
+ }
+ else
+ {
+ // stay on the even field
+ _field = false;
+ }
+
+ // we have reached the end of the vertical display area
+ // address loaded from start address register at the top of each field
+ _vmaRowStart = (Register[R12_START_ADDR_H] << 8) | Register[R13_START_ADDR_L];
+
+ // reset the vertical character counter
+ VCC = 0;
+
+ // increment field counter
+ CFC++;
+ }
+ else
+ {
+ // row start address is increased by Horiztonal Displayed
+ _vmaRowStart += R1_HorizontalDisplayed;
+
+ // increment vertical character counter
+ VCC++;
+ }
+ }
+ else
+ {
+ // next scanline
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // vertical line counter increments by 2
+ VLC += 2;
+
+ // ensure vertical line counter is an even value
+ VLC &= ~1;
+ }
+ else
+ {
+ // non-interlace mode
+ // increment vertical line counter
+ VLC++;
+ }
+ }
+
+ // MA set to row start at the beginning of each line
+ _vma = _vmaRowStart;
+ }
+ else
+ {
+ // next horizontal character (1us)
+ // increment horizontal character counter
+ HCC++;
+
+ // increment VMA
+ _vma++;
+ }
+
+ hssstart = false;
+ hhclock = false;
+
+ if (HCC == R2_HorizontalSyncPosition)
+ {
+ // start of horizontal sync
+ hssstart = true;
+ }
+
+ if (HCC == R2_HorizontalSyncPosition / 2)
+ {
+ // we are half way through the line
+ hhclock = true;
+ }
+
+ /* Hor active video */
+ if (HCC == 0)
+ {
+ // active display
+ latch_hdisp = true;
+ }
+
+ if (HCC == R1_HorizontalDisplayed)
+ {
+ // inactive display
+ latch_hdisp = false;
+ }
+
+ /* Hor sync */
+ if (hssstart || // start of horizontal sync
+ HSYNC) // already in horizontal sync
+ {
+ // start of horizontal sync
+ HSYNC = true;
+ HSC++;
+ }
+ else
+ {
+ // reset hsync counter
+ HSC = 0;
+ }
+
+ if (HSC == R3_HorizontalSyncWidth)
+ {
+ // end of horizontal sync
+ HSYNC = false;
+ }
+
+ /* Ver active video */
+ if (VCC == 0)
+ {
+ // active display
+ latch_vdisp = true;
+ }
+
+ if (VCC == R6_VerticalDisplayed)
+ {
+ // inactive display
+ latch_vdisp = false;
+
+ // ACCC1.8 - 21.3.3
+ // On CRTC 1, bit 5 of the Status register is updated when C0=R0 according to the BORDER R6
+ // conditions (False: C4=C9=C0=0 / True: C4=R6 & C9=C0=0)
+ StatusRegister |= (1 << 5);
+ }
+
+ // vertical sync occurs at different times depending on the interlace field
+ // even field: the same time as HSYNC
+ // odd field: half a line later than HSYNC
+ if ((!_field && hssstart) || (_field && hhclock))
+ {
+ if ((VCC == R7_VerticalSyncPosition && VLC == 0) // vsync starts on the first line
+ || VSYNC) // vsync is already in progress
+ {
+ // start of vertical sync
+ VSYNC = true;
+ // increment vertical sync counter
+ VSC++;
+ }
+ else
+ {
+ // reset vsync counter
+ VSC = 0;
+ }
+
+ if (VSYNC && VSC == R3_VerticalSyncWidth - 1)
+ {
+ // end of vertical sync
+ VSYNC = false;
+ }
+ }
+
+
+ /* Address Generation */
+ int line = VLC;
+
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // the least significant bit is based on the current field number
+ int fNum = _field ? 1 : 0;
+ int lNum = VLC.Bit(0) ? 1 : 0;
+ line &= ~1;
+
+ _RA = line & (fNum | lNum);
+ }
+ else
+ {
+ // raster address is just the VLC
+ _RA = VLC;
+ }
+
+ _LA = _vma;
+
+ // DISPTMG Generation
+ if (!latch_hdisp || !latch_vdisp)
+ {
+ // HSYNC output pin is fed through a NOR gate with either 2 or 3 inputs
+ // - H Display
+ // - V Display
+ // - TODO: R8 DISPTMG Skew (only on certain CRTC types)
+ DISPTMG = false;
+ }
+ else
+ {
+ DISPTMG = true;
+ }
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected override bool ReadRegister(ref int data)
+ {
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R3_SYNC_WIDTHS:
+ case R4_V_TOTAL:
+ case R5_V_TOTAL_ADJUST:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R8_INTERLACE_MODE:
+ case R9_MAX_SL_ADDRESS:
+ case R10_CURSOR_START:
+ case R11_CURSOR_END:
+ case R12_START_ADDR_H:
+ case R13_START_ADDR_L:
+ // write-only registers return 0x0 on Type 1 CRTC
+ data = 0;
+ break;
+ case R14_CURSOR_H:
+ data = Register[AddressRegister] & 0x3F;
+ break;
+ case R15_CURSOR_L:
+ data = Register[AddressRegister];
+ break;
+ case R16_LIGHT_PEN_H:
+ // read/write registers (6bit)
+ data = Register[AddressRegister] & 0x3F;
+ // reading from R16 resets bit6 of the status register
+ StatusRegister &= byte.MaxValue ^ (1 << 6);
+ break;
+ case R17_LIGHT_PEN_L:
+ // read/write regiters (8bit)
+ data = Register[AddressRegister];
+ // reading from R17 resets bit6 of the status register
+ StatusRegister &= byte.MaxValue ^ (1 << 6);
+ break;
+ case 31:
+ // Dummy Register. Datasheet describes this as N/A but CPCWIKI suggests that reading from it return 0xFF;
+ data = 0xFF;
+ break;
+ default:
+ // non-existent registers return 0x0
+ data = 0;
+ break;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected override void WriteRegister(int data)
+ {
+ byte v = (byte)data;
+
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R13_START_ADDR_L:
+ case R15_CURSOR_L:
+ // 8-bit registers
+ Register[AddressRegister] = v;
+ break;
+ case R4_V_TOTAL:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R10_CURSOR_START:
+ // 7-bit registers
+ Register[AddressRegister] = (byte)(v & 0x7F);
+ break;
+ case R12_START_ADDR_H:
+ case R14_CURSOR_H:
+ // 6-bit registers
+ Register[AddressRegister] = (byte)(v & 0x3F);
+ break;
+ case R5_V_TOTAL_ADJUST:
+ case R9_MAX_SL_ADDRESS:
+ case R11_CURSOR_END:
+ // 5-bit registers
+ Register[AddressRegister] = (byte)(v & 0x1F);
+ break;
+ case R3_SYNC_WIDTHS:
+ // 4-bit register
+ Register[AddressRegister] = (byte)(v & 0x0F);
+ break;
+ case R8_INTERLACE_MODE:
+ // Interlace & skew - 2bit
+ Register[AddressRegister] = (byte)(v & 0x03);
+ break;
+ }
+ }
+
+ ///
+ /// CRTC 1 has a status register
+ ///
+ protected override bool ReadStatus(ref int data)
+ {
+ // ACCC1.8 - 21.3.1
+ // Only CRTC 1 has a status register present on the specific port &BE00.
+ // This port is a mirror of the read port for CRTC’s 3 and 4, which handle status differently
+ data = StatusRegister;
+
+ return true;
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type2.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type2.cs
new file mode 100644
index 0000000000..8542e90b25
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type2.cs
@@ -0,0 +1,343 @@
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// TYPE 2
+ /// - Motorola MC6845
+ /// http://www.cpcwiki.eu/imgs/d/da/Mc6845.motorola.pdf
+ /// http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
+ ///
+ public class CRTC_Type2 : CRTC
+ {
+ ///
+ /// Defined CRTC type number
+ ///
+ public override int CrtcType => 2;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public override void Clock()
+ {
+ base.Clock();
+
+ int maxScanLine;
+
+ if (HCC == R0_HorizontalTotal)
+ {
+ // end of displayable area reached
+ // set up for the next line
+ HCC = 0;
+
+ // TODO: handle interlace setup
+ if (R8_Interlace == 3)
+ {
+ // in interlace sync and video mask off bit 0 of the max scanline address
+ maxScanLine = R9_MaxScanline & 0b11110;
+ }
+ else
+ {
+ maxScanLine = R9_MaxScanline;
+ }
+
+ if (VLC == maxScanLine)
+ {
+ // we have reached the final scanline within this vertical character row
+ // move to next character
+ VLC = 0;
+
+ // TODO: implement vertical adjust
+
+
+ if (VCC == R4_VerticalTotal)
+ {
+ // check the interlace mode
+ if (R8_Interlace.Bit(0))
+ {
+ // toggle the field
+ _field = !_field;
+ }
+ else
+ {
+ // stay on the even field
+ _field = false;
+ }
+
+ // we have reached the end of the vertical display area
+ // address loaded from start address register at the top of each field
+ _vmaRowStart = (Register[R12_START_ADDR_H] << 8) | Register[R13_START_ADDR_L];
+
+ // reset the vertical character counter
+ VCC = 0;
+
+ // increment field counter
+ CFC++;
+ }
+ else
+ {
+ // row start address is increased by Horiztonal Displayed
+ _vmaRowStart += R1_HorizontalDisplayed;
+
+ // increment vertical character counter
+ VCC++;
+ }
+ }
+ else
+ {
+ // next scanline
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // vertical line counter increments by 2
+ VLC += 2;
+
+ // ensure vertical line counter is an even value
+ VLC &= ~1;
+ }
+ else
+ {
+ // non-interlace mode
+ // increment vertical line counter
+ VLC++;
+ }
+ }
+
+ // MA set to row start at the beginning of each line
+ _vma = _vmaRowStart;
+ }
+ else
+ {
+ // next horizontal character (1us)
+ // increment horizontal character counter
+ HCC++;
+
+ // increment VMA
+ _vma++;
+ }
+
+ hssstart = false;
+ hhclock = false;
+
+ if (HCC == R2_HorizontalSyncPosition)
+ {
+ // start of horizontal sync
+ hssstart = true;
+ }
+
+ if (HCC == R2_HorizontalSyncPosition / 2)
+ {
+ // we are half way through the line
+ hhclock = true;
+ }
+
+ /* Hor active video */
+ if (HCC == 0)
+ {
+ // active display
+ latch_hdisp = true;
+ }
+
+ if (HCC == R1_HorizontalDisplayed)
+ {
+ // inactive display
+ latch_hdisp = false;
+ }
+
+ /* Hor sync */
+ if (hssstart || // start of horizontal sync
+ HSYNC) // already in horizontal sync
+ {
+ // start of horizontal sync
+ HSYNC = true;
+ HSC++;
+ }
+ else
+ {
+ // reset hsync counter
+ HSC = 0;
+ }
+
+ if (HSC == R3_HorizontalSyncWidth)
+ {
+ // end of horizontal sync
+ HSYNC = false;
+ }
+
+ /* Ver active video */
+ if (VCC == 0)
+ {
+ // active display
+ latch_vdisp = true;
+ }
+
+ if (VCC == R6_VerticalDisplayed)
+ {
+ // inactive display
+ latch_vdisp = false;
+ }
+
+ // vertical sync occurs at different times depending on the interlace field
+ // even field: the same time as HSYNC
+ // odd field: half a line later than HSYNC
+ if ((!_field && hssstart) || (_field && hhclock))
+ {
+ if ((VCC == R7_VerticalSyncPosition && VLC == 0) // vsync starts on the first line
+ || VSYNC) // vsync is already in progress
+ {
+ // start of vertical sync
+ VSYNC = true;
+ // increment vertical sync counter
+ VSC++;
+ }
+ else
+ {
+ // reset vsync counter
+ VSC = 0;
+ }
+
+ if (VSYNC && VSC == R3_VerticalSyncWidth - 1)
+ {
+ // end of vertical sync
+ VSYNC = false;
+ }
+ }
+
+
+ /* Address Generation */
+ int line = VLC;
+
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // the least significant bit is based on the current field number
+ int fNum = _field ? 1 : 0;
+ int lNum = VLC.Bit(0) ? 1 : 0;
+ line &= ~1;
+
+ _RA = line & (fNum | lNum);
+ }
+ else
+ {
+ // raster address is just the VLC
+ _RA = VLC;
+ }
+
+ _LA = _vma;
+
+ // DISPTMG Generation
+ if (!latch_hdisp || !latch_vdisp)
+ {
+ // HSYNC output pin is fed through a NOR gate with either 2 or 3 inputs
+ // - H Display
+ // - V Display
+ // - TODO: R8 DISPTMG Skew (only on certain CRTC types)
+ DISPTMG = false;
+ }
+ else
+ {
+ DISPTMG = true;
+ }
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected override bool ReadRegister(ref int data)
+ {
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R3_SYNC_WIDTHS:
+ case R4_V_TOTAL:
+ case R5_V_TOTAL_ADJUST:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R8_INTERLACE_MODE:
+ case R9_MAX_SL_ADDRESS:
+ case R10_CURSOR_START:
+ case R11_CURSOR_END:
+ case R12_START_ADDR_H:
+ case R13_START_ADDR_L:
+ // write-only registers do not respond on type 2
+ return false;
+ case R14_CURSOR_H:
+ case R16_LIGHT_PEN_H:
+ // read/write registers (6bit)
+ data = Register[AddressRegister] & 0x3F;
+ break;
+ case R17_LIGHT_PEN_L:
+ case R15_CURSOR_L:
+ // read/write regiters (8bit)
+ data = Register[AddressRegister];
+ break;
+ default:
+ // non-existent registers return 0x0
+ data = 0;
+ break;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected override void WriteRegister(int data)
+ {
+ byte v = (byte)data;
+
+ switch (AddressRegister)
+ {
+ case R0_H_TOTAL:
+ case R1_H_DISPLAYED:
+ case R2_H_SYNC_POS:
+ case R13_START_ADDR_L:
+ case R15_CURSOR_L:
+ // 8-bit registers
+ Register[AddressRegister] = v;
+ break;
+ case R4_V_TOTAL:
+ case R6_V_DISPLAYED:
+ case R7_V_SYNC_POS:
+ case R10_CURSOR_START:
+ // 7-bit registers
+ Register[AddressRegister] = (byte)(v & 0x7F);
+ break;
+ case R12_START_ADDR_H:
+ case R14_CURSOR_H:
+ // 6-bit registers
+ Register[AddressRegister] = (byte)(v & 0x3F);
+ break;
+ case R5_V_TOTAL_ADJUST:
+ case R9_MAX_SL_ADDRESS:
+ case R11_CURSOR_END:
+ // 5-bit registers
+ Register[AddressRegister] = (byte)(v & 0x1F);
+ break;
+ case R3_SYNC_WIDTHS:
+ // 4-bit register
+ Register[AddressRegister] = (byte)(v & 0x0F);
+ break;
+ case R8_INTERLACE_MODE:
+ // Interlace & skew - 2bit
+ Register[AddressRegister] = (byte)(v & 0x03);
+ break;
+ }
+ }
+
+ ///
+ /// CRTC 2 has no status register
+ ///
+ protected override bool ReadStatus(ref int data)
+ {
+ // ACCC1.8 - 21.3.2
+ // CRTC2 always returns 255 on this port
+ data = 255;
+ return true;
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type3.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type3.cs
new file mode 100644
index 0000000000..c586855ae8
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type3.cs
@@ -0,0 +1,303 @@
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// TYPE 3
+ /// - Amstrad AMS40489
+ ///
+ public class CRTC_Type3 : CRTC
+ {
+ ///
+ /// Defined CRTC type number
+ ///
+ public override int CrtcType => 3;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public override void Clock()
+ {
+ base.Clock();
+
+ int maxScanLine;
+
+ if (HCC == R0_HorizontalTotal)
+ {
+ // end of displayable area reached
+ // set up for the next line
+ HCC = 0;
+
+ // TODO: handle interlace setup
+ if (R8_Interlace == 3)
+ {
+ // in interlace sync and video mask off bit 0 of the max scanline address
+ maxScanLine = R9_MaxScanline & 0b11110;
+ }
+ else
+ {
+ maxScanLine = R9_MaxScanline;
+ }
+
+ if (VLC == maxScanLine)
+ {
+ // we have reached the final scanline within this vertical character row
+ // move to next character
+ VLC = 0;
+
+ // TODO: implement vertical adjust
+
+
+ if (VCC == R4_VerticalTotal)
+ {
+ // check the interlace mode
+ if (R8_Interlace.Bit(0))
+ {
+ // toggle the field
+ _field = !_field;
+ }
+ else
+ {
+ // stay on the even field
+ _field = false;
+ }
+
+ // we have reached the end of the vertical display area
+ // address loaded from start address register at the top of each field
+ _vmaRowStart = (Register[R12_START_ADDR_H] << 8) | Register[R13_START_ADDR_L];
+
+ // reset the vertical character counter
+ VCC = 0;
+
+ // increment field counter
+ CFC++;
+ }
+ else
+ {
+ // row start address is increased by Horiztonal Displayed
+ _vmaRowStart += R1_HorizontalDisplayed;
+
+ // increment vertical character counter
+ VCC++;
+ }
+ }
+ else
+ {
+ // next scanline
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // vertical line counter increments by 2
+ VLC += 2;
+
+ // ensure vertical line counter is an even value
+ VLC &= ~1;
+ }
+ else
+ {
+ // non-interlace mode
+ // increment vertical line counter
+ VLC++;
+ }
+ }
+
+ // MA set to row start at the beginning of each line
+ _vma = _vmaRowStart;
+ }
+ else
+ {
+ // next horizontal character (1us)
+ // increment horizontal character counter
+ HCC++;
+
+ // increment VMA
+ _vma++;
+ }
+
+ hssstart = false;
+ hhclock = false;
+
+ if (HCC == R2_HorizontalSyncPosition)
+ {
+ // start of horizontal sync
+ hssstart = true;
+ }
+
+ if (HCC == R2_HorizontalSyncPosition / 2)
+ {
+ // we are half way through the line
+ hhclock = true;
+ }
+
+ /* Hor active video */
+ if (HCC == 0)
+ {
+ // active display
+ latch_hdisp = true;
+ }
+
+ if (HCC == R1_HorizontalDisplayed)
+ {
+ // inactive display
+ latch_hdisp = false;
+ }
+
+ /* Hor sync */
+ if (hssstart || // start of horizontal sync
+ HSYNC) // already in horizontal sync
+ {
+ // start of horizontal sync
+ HSYNC = true;
+ HSC++;
+ }
+ else
+ {
+ // reset hsync counter
+ HSC = 0;
+ }
+
+ if (HSC == R3_HorizontalSyncWidth)
+ {
+ // end of horizontal sync
+ HSYNC = false;
+ }
+
+ /* Ver active video */
+ if (VCC == 0)
+ {
+ // active display
+ latch_vdisp = true;
+ }
+
+ if (VCC == R6_VerticalDisplayed)
+ {
+ // inactive display
+ latch_vdisp = false;
+ }
+
+ // vertical sync occurs at different times depending on the interlace field
+ // even field: the same time as HSYNC
+ // odd field: half a line later than HSYNC
+ if ((!_field && hssstart) || (_field && hhclock))
+ {
+ if ((VCC == R7_VerticalSyncPosition && VLC == 0) // vsync starts on the first line
+ || VSYNC) // vsync is already in progress
+ {
+ // start of vertical sync
+ VSYNC = true;
+ // increment vertical sync counter
+ VSC++;
+ }
+ else
+ {
+ // reset vsync counter
+ VSC = 0;
+ }
+
+ if (VSYNC && VSC == R3_VerticalSyncWidth - 1)
+ {
+ // end of vertical sync
+ VSYNC = false;
+ }
+ }
+
+
+ /* Address Generation */
+ int line = VLC;
+
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // the least significant bit is based on the current field number
+ int fNum = _field ? 1 : 0;
+ int lNum = VLC.Bit(0) ? 1 : 0;
+ line &= ~1;
+
+ _RA = line & (fNum | lNum);
+ }
+ else
+ {
+ // raster address is just the VLC
+ _RA = VLC;
+ }
+
+ _LA = _vma;
+
+ // DISPTMG Generation
+ if (!latch_hdisp || !latch_vdisp)
+ {
+ // HSYNC output pin is fed through a NOR gate with either 2 or 3 inputs
+ // - H Display
+ // - V Display
+ // - TODO: R8 DISPTMG Skew (only on certain CRTC types)
+ DISPTMG = false;
+ }
+ else
+ {
+ DISPTMG = true;
+ }
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected override bool ReadRegister(ref int data)
+ {
+ // http://cpctech.cpc-live.com/docs/cpcplus.html
+ switch (AddressRegister & 0x6F)
+ {
+ case 0:
+ data = Register[R16_LIGHT_PEN_H] & 0x3F;
+ break;
+ case 1:
+ data = Register[R17_LIGHT_PEN_L];
+ break;
+ case 2:
+ // Status 1
+ break;
+ case 3:
+ // Status 2
+ break;
+ case 4:
+ data = Register[R12_START_ADDR_H] & 0x3F;
+ break;
+ case 5:
+ data = Register[R13_START_ADDR_L];
+ break;
+ case 6:
+ case 7:
+ data = 0;
+ break;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected override void WriteRegister(int data)
+ {
+ byte v3 = (byte)data;
+ switch (AddressRegister)
+ {
+ case 16:
+ case 17:
+ // read only registers
+ return;
+ default:
+ if (AddressRegister < 16)
+ {
+ Register[AddressRegister] = v3;
+ }
+ else
+ {
+ // read only dummy registers
+ return;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type4.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type4.cs
new file mode 100644
index 0000000000..78cd3a2cc9
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.Type4.cs
@@ -0,0 +1,304 @@
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// TYPE 4
+ /// - Amstrad AMS40041
+ /// - Amstrad AMS40226
+ ///
+ public class CRTC_Type4 : CRTC
+ {
+ ///
+ /// Defined CRTC type number
+ ///
+ public override int CrtcType => 4;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public override void Clock()
+ {
+ base.Clock();
+
+ int maxScanLine;
+
+ if (HCC == R0_HorizontalTotal)
+ {
+ // end of displayable area reached
+ // set up for the next line
+ HCC = 0;
+
+ // TODO: handle interlace setup
+ if (R8_Interlace == 3)
+ {
+ // in interlace sync and video mask off bit 0 of the max scanline address
+ maxScanLine = R9_MaxScanline & 0b11110;
+ }
+ else
+ {
+ maxScanLine = R9_MaxScanline;
+ }
+
+ if (VLC == maxScanLine)
+ {
+ // we have reached the final scanline within this vertical character row
+ // move to next character
+ VLC = 0;
+
+ // TODO: implement vertical adjust
+
+
+ if (VCC == R4_VerticalTotal)
+ {
+ // check the interlace mode
+ if (R8_Interlace.Bit(0))
+ {
+ // toggle the field
+ _field = !_field;
+ }
+ else
+ {
+ // stay on the even field
+ _field = false;
+ }
+
+ // we have reached the end of the vertical display area
+ // address loaded from start address register at the top of each field
+ _vmaRowStart = (Register[R12_START_ADDR_H] << 8) | Register[R13_START_ADDR_L];
+
+ // reset the vertical character counter
+ VCC = 0;
+
+ // increment field counter
+ CFC++;
+ }
+ else
+ {
+ // row start address is increased by Horiztonal Displayed
+ _vmaRowStart += R1_HorizontalDisplayed;
+
+ // increment vertical character counter
+ VCC++;
+ }
+ }
+ else
+ {
+ // next scanline
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // vertical line counter increments by 2
+ VLC += 2;
+
+ // ensure vertical line counter is an even value
+ VLC &= ~1;
+ }
+ else
+ {
+ // non-interlace mode
+ // increment vertical line counter
+ VLC++;
+ }
+ }
+
+ // MA set to row start at the beginning of each line
+ _vma = _vmaRowStart;
+ }
+ else
+ {
+ // next horizontal character (1us)
+ // increment horizontal character counter
+ HCC++;
+
+ // increment VMA
+ _vma++;
+ }
+
+ hssstart = false;
+ hhclock = false;
+
+ if (HCC == R2_HorizontalSyncPosition)
+ {
+ // start of horizontal sync
+ hssstart = true;
+ }
+
+ if (HCC == R2_HorizontalSyncPosition / 2)
+ {
+ // we are half way through the line
+ hhclock = true;
+ }
+
+ /* Hor active video */
+ if (HCC == 0)
+ {
+ // active display
+ latch_hdisp = true;
+ }
+
+ if (HCC == R1_HorizontalDisplayed)
+ {
+ // inactive display
+ latch_hdisp = false;
+ }
+
+ /* Hor sync */
+ if (hssstart || // start of horizontal sync
+ HSYNC) // already in horizontal sync
+ {
+ // start of horizontal sync
+ HSYNC = true;
+ HSC++;
+ }
+ else
+ {
+ // reset hsync counter
+ HSC = 0;
+ }
+
+ if (HSC == R3_HorizontalSyncWidth)
+ {
+ // end of horizontal sync
+ HSYNC = false;
+ }
+
+ /* Ver active video */
+ if (VCC == 0)
+ {
+ // active display
+ latch_vdisp = true;
+ }
+
+ if (VCC == R6_VerticalDisplayed)
+ {
+ // inactive display
+ latch_vdisp = false;
+ }
+
+ // vertical sync occurs at different times depending on the interlace field
+ // even field: the same time as HSYNC
+ // odd field: half a line later than HSYNC
+ if ((!_field && hssstart) || (_field && hhclock))
+ {
+ if ((VCC == R7_VerticalSyncPosition && VLC == 0) // vsync starts on the first line
+ || VSYNC) // vsync is already in progress
+ {
+ // start of vertical sync
+ VSYNC = true;
+ // increment vertical sync counter
+ VSC++;
+ }
+ else
+ {
+ // reset vsync counter
+ VSC = 0;
+ }
+
+ if (VSYNC && VSC == R3_VerticalSyncWidth - 1)
+ {
+ // end of vertical sync
+ VSYNC = false;
+ }
+ }
+
+
+ /* Address Generation */
+ int line = VLC;
+
+ if (R8_Interlace == 3)
+ {
+ // interlace sync+video mode
+ // the least significant bit is based on the current field number
+ int fNum = _field ? 1 : 0;
+ int lNum = VLC.Bit(0) ? 1 : 0;
+ line &= ~1;
+
+ _RA = line & (fNum | lNum);
+ }
+ else
+ {
+ // raster address is just the VLC
+ _RA = VLC;
+ }
+
+ _LA = _vma;
+
+ // DISPTMG Generation
+ if (!latch_hdisp || !latch_vdisp)
+ {
+ // HSYNC output pin is fed through a NOR gate with either 2 or 3 inputs
+ // - H Display
+ // - V Display
+ // - TODO: R8 DISPTMG Skew (only on certain CRTC types)
+ DISPTMG = false;
+ }
+ else
+ {
+ DISPTMG = true;
+ }
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected override bool ReadRegister(ref int data)
+ {
+ // http://cpctech.cpc-live.com/docs/cpcplus.html
+ switch (AddressRegister & 0x6F)
+ {
+ case 0:
+ data = Register[R16_LIGHT_PEN_H] & 0x3F;
+ break;
+ case 1:
+ data = Register[R17_LIGHT_PEN_L];
+ break;
+ case 2:
+ // Status 1
+ break;
+ case 3:
+ // Status 2
+ break;
+ case 4:
+ data = Register[R12_START_ADDR_H] & 0x3F;
+ break;
+ case 5:
+ data = Register[R13_START_ADDR_L];
+ break;
+ case 6:
+ case 7:
+ data = 0;
+ break;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected override void WriteRegister(int data)
+ {
+ byte v3 = (byte)data;
+ switch (AddressRegister)
+ {
+ case 16:
+ case 17:
+ // read only registers
+ return;
+ default:
+ if (AddressRegister < 16)
+ {
+ Register[AddressRegister] = v3;
+ }
+ else
+ {
+ // read only dummy registers
+ return;
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.cs
new file mode 100644
index 0000000000..6f67da5719
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/CRTC.cs
@@ -0,0 +1,880 @@
+using BizHawk.Common;
+using BizHawk.Common.NumberExtensions;
+using System.Collections;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
+ /// http://www.cpcwiki.eu/index.php/CRTC
+ /// http://cpctech.cpc-live.com/docs/cpcplus.html
+ /// https://shaker.logonsystem.eu/
+ /// https://shaker.logonsystem.eu/ACCC1.8-EN.pdf
+ /// https://shaker.logonsystem.eu/tests
+ /// This implementation aims to emulate all the various CRTC chips that appear within
+ /// the CPC, CPC+ and GX4000 ranges. The CPC community have assigned them type numbers.
+ /// If different implementations share the same type number it indicates that they are functionally identical
+ ///
+ /// Part No. Manufacturer Type No. Info.
+ /// ------------------------------------------------------------------------------------------------------
+ /// HD6845S Hitachi 0
+ /// Datasheet: http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf
+ /// ------------------------------------------------------------------------------------------------------
+ /// UM6845 UMC 0
+ /// Datasheet: http://www.cpcwiki.eu/imgs/1/13/Um6845.umc.pdf
+ /// ------------------------------------------------------------------------------------------------------
+ /// UM6845R UMC 1
+ /// Datasheet: http://www.cpcwiki.eu/imgs/b/b5/Um6845r.umc.pdf
+ /// ------------------------------------------------------------------------------------------------------
+ /// MC6845 Motorola 2
+ /// Datasheet: http://www.cpcwiki.eu/imgs/d/da/Mc6845.motorola.pdf & http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
+ /// ------------------------------------------------------------------------------------------------------
+ /// AMS40489 Amstrad 3 Only exists in the CPC464+, CPC6128+ and GX4000 and is integrated into a single CPC+ ASIC chip (along with the gatearray)
+ /// Datasheet: {none}
+ /// ------------------------------------------------------------------------------------------------------
+ /// AMS40041 Amstrad 4 'Pre-ASIC' IC. The CRTC is integrated into a aingle ASIC IC with functionality being almost identical to the AMS40489
+ /// (or 40226) Used in the 'Cost-Down' range of CPC464 and CPC6128 systems
+ /// Datasheet: {none}
+ ///
+ ///
+ public abstract partial class CRTC : IPortIODevice
+ {
+ ///
+ /// Instantiation helper
+ ///
+ public static CRTC Create(int crtcType)
+ {
+ return crtcType switch
+ {
+ 0 => new CRTC_Type0(),
+ 2 => new CRTC_Type2(),
+ 3 => new CRTC_Type3(),
+ 4 => new CRTC_Type4(),
+ _ => new CRTC_Type1(),
+ };
+ }
+
+
+ ///
+ /// Defined CRTC type number
+ ///
+ public virtual int CrtcType { get; }
+
+ ///
+ /// CPC register default values
+ ///
+ public byte[] RegDefaults = { 63, 40, 46, 142, 38, 0, 25, 30, 0, 7, 0, 0, 48, 0, 192, 7, 0, 0 };
+
+ ///
+ /// The ClK isaTTUMOS-compatible input used to synchronize all CRT' functions except for the processor interface.
+ /// An external dot counter is used to derive this signal which is usually the character rate in an alphanumeric CRT.
+ /// The active transition is high-to-low
+ ///
+ public bool CLK;
+
+ ///
+ /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation.
+ /// This signal determines the horizontal position of the displayed text.
+ ///
+ public virtual bool HSYNC
+ {
+ get => _HSYNC;
+ protected set
+ {
+ if (value != _HSYNC)
+ {
+ // value has changed
+ if (value) { HSYNC_On_Callbacks(); }
+ else { HSYNC_Off_Callbacks(); }
+ }
+ _HSYNC = value;
+ }
+ }
+ private bool _HSYNC;
+
+ ///
+ /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation.
+ /// This signal determines the vertical position of the displayed text.
+ ///
+ public virtual bool VSYNC
+ {
+ get => _VSYNC;
+ protected set
+ {
+ if (value != _VSYNC)
+ {
+ // value has changed
+ if (value) { VSYNC_On_Callbacks(); }
+ else { VSYNC_Off_Callbacks(); }
+ }
+ _VSYNC = value;
+ }
+ }
+ private bool _VSYNC;
+
+ ///
+ /// This TTL compatible output is an active high signal which indicates the CRTC is providing addressing in the active Display Area.
+ ///
+ public virtual bool DISPTMG
+ {
+ get => _DISPTMG;
+ protected set => _DISPTMG = value;
+ }
+ private bool _DISPTMG;
+
+ ///
+ /// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal.
+ ///
+ public virtual bool CUDISP
+ {
+ get => _CUDISP;
+ protected set => _CUDISP = value;
+ }
+ private bool _CUDISP;
+
+ ///
+ /// Linear Address Generator
+ /// Character pos address (0 index).
+ /// Feeds the MA lines
+ ///
+ protected int _LA;
+
+ ///
+ /// Generated by the Vertical Control Raster Counter
+ /// Feeds the RA lines
+ ///
+ protected int _RA;
+
+ ///
+ /// This 16-bit property emulates how the Amstrad CPC Gate Array is wired up to the CRTC
+ /// Built from LA, RA and CLK
+ ///
+ /// Memory Address Signal Signal source Signal name
+ /// A15 6845 MA13
+ /// A14 6845 MA12
+ /// A13 6845 RA2
+ /// A12 6845 RA1
+ /// A11 6845 RA0
+ /// A10 6845 MA9
+ /// A9 6845 MA8
+ /// A8 6845 MA7
+ /// A7 6845 MA6
+ /// A6 6845 MA5
+ /// A5 6845 MA4
+ /// A4 6845 MA3
+ /// A3 6845 MA2
+ /// A2 6845 MA1
+ /// A1 6845 MA0
+ /// A0 Gate-Array CLK
+ ///
+ public ushort MA_Address
+ {
+ get
+ {
+ var MA = new BitArray(16);
+ MA[0] = CLK;
+ MA[1] = _LA.Bit(0);
+ MA[2] = _LA.Bit(1);
+ MA[3] = _LA.Bit(2);
+ MA[4] = _LA.Bit(3);
+ MA[5] = _LA.Bit(4);
+ MA[6] = _LA.Bit(5);
+ MA[7] = _LA.Bit(6);
+ MA[8] = _LA.Bit(7);
+ MA[9] = _LA.Bit(8);
+ MA[10] = _LA.Bit(9);
+ MA[11] = _RA.Bit(0);
+ MA[12] = _RA.Bit(1);
+ MA[13] = _RA.Bit(2);
+ MA[14] = _LA.Bit(12);
+ MA[15] = _LA.Bit(13);
+ int[] array = new int[1];
+ MA.CopyTo(array, 0);
+ return (ushort)array[0];
+ }
+ }
+
+ ///
+ /// Public Delegate
+ ///
+ public delegate void CallBack();
+ ///
+ /// Fired on CRTC HSYNC signal rising edge
+ ///
+ protected CallBack HSYNC_On_Callbacks;
+ ///
+ /// Fired on CRTC HSYNC signal falling edge
+ ///
+ protected CallBack HSYNC_Off_Callbacks;
+ ///
+ /// Fired on CRTC VSYNC signal rising edge
+ ///
+ protected CallBack VSYNC_On_Callbacks;
+ ///
+ /// Fired on CRTC VSYNC signal falling edge
+ ///
+ protected CallBack VSYNC_Off_Callbacks;
+
+ public void AttachHSYNCOnCallback(CallBack hCall) => HSYNC_On_Callbacks += hCall;
+ public void AttachHSYNCOffCallback(CallBack hCall) => HSYNC_Off_Callbacks += hCall;
+ public void AttachVSYNCOnCallback(CallBack vCall) => VSYNC_On_Callbacks += vCall;
+ public void AttachVSYNCOffCallback(CallBack vCall) => VSYNC_Off_Callbacks += vCall;
+
+ ///
+ /// Reset Counter
+ ///
+ protected int _inReset;
+
+ ///
+ /// This is a 5 bit register which is used as a pointer to direct data transfers to and from the system MPU
+ ///
+ protected byte AddressRegister
+ {
+ get => (byte)(_addressRegister & 0b0001_1111);
+ set => _addressRegister = (byte)(value & 0b0001_1111);
+ }
+ private byte _addressRegister;
+
+ ///
+ /// This 8 bit write-only register determines the horizontal frequency of HS.
+ /// It is the total of displayed plus non-displayed character time units minus one.
+ ///
+ protected const int R0_H_TOTAL = 0;
+ ///
+ /// This 8 bit write-only register determines the number of displayed characters per horizontal line.
+ ///
+ protected const int R1_H_DISPLAYED = 1;
+ ///
+ /// This 8 bit write-only register determines the horizontal sync postiion on the horizontal line.
+ ///
+ protected const int R2_H_SYNC_POS = 2;
+ ///
+ /// This 4 bit write-only register determines the width of the HS pulse. It may not be apparent why this width needs to be programmed.However,
+ /// consider that all timing widths must be programmed as multiples of the character clock period which varies.If HS width were fixed as an integral
+ /// number of character times, it would vary with character rate and be out of tolerance for certain monitors.
+ /// The rate programmable feature allows compensating HS width.
+ /// NOTE: Dependent on chiptype this also may include VSYNC width - check the UpdateWidths() method
+ ///
+ protected const int R3_SYNC_WIDTHS = 3;
+
+ /* Vertical Timing Register Constants */
+ ///
+ /// The vertical frequency of VS is determined by both R4 and R5.The calculated number of character I ine times is usual I y an integer plus a fraction to
+ /// get exactly a 50 or 60Hz vertical refresh rate. The integer number of character line times minus one is programmed in the 7 bit write-only Vertical Total Register;
+ /// the fraction is programmed in the 5 bit write-only Vertical Scan Adjust Register as a number of scan line times.
+ ///
+ protected const int R4_V_TOTAL = 4;
+ protected const int R5_V_TOTAL_ADJUST = 5;
+ ///
+ /// This 7 bit write-only register determines the number of displayed character rows on the CRT screen, and is programmed in character row times.
+ ///
+ protected const int R6_V_DISPLAYED = 6;
+ ///
+ /// This 7 bit write-only register determines the vertical sync position with respect to the reference.It is programmed in character row times.
+ ///
+ protected const int R7_V_SYNC_POS = 7;
+ ///
+ /// This 2 bit write-only register controls the raster scan mode(see Figure 11 ). When bit 0 and bit 1 are reset, or bit 0 is reset and bit 1 set,
+ /// the non· interlace raster scan mode is selected.Two interlace modes are available.Both are interlaced 2 fields per frame.When bit 0 is set and bit 1 is reset,
+ /// the interlace sync raster scan mode is selected.Also when bit 0 and bit 1 are set, the interlace sync and video raster scan mode is selected.
+ ///
+ protected const int R8_INTERLACE_MODE = 8;
+ ///
+ /// This 5 bit write·only register determines the number of scan lines per character row including spacing.
+ /// The programmed value is a max address and is one less than the number of scan l1nes.
+ ///
+ protected const int R9_MAX_SL_ADDRESS = 9;
+ ///
+ /// This 7 bit write-only register controls the cursor format(see Figure 10). Bit 5 is the blink timing control.When bit 5 is low, the blink frequency is 1/16 of the
+ /// vertical field rate, and when bit 5 is high, the blink frequency is 1/32 of the vertical field rate.Bit 6 is used to enable a blink.
+ /// The cursor start scan line is set by the lower 5 bits.
+ ///
+ protected const int R10_CURSOR_START = 10;
+ ///
+ /// This 5 bit write-only register sets the cursor end scan line
+ ///
+ protected const int R11_CURSOR_END = 11;
+ ///
+ /// Start Address Register is a 14 bit write-only register which determines the first address put out as a refresh address after vertical blanking.
+ /// It consists of an 8 bit lower register, and a 6 bit higher register.
+ ///
+ protected const int R12_START_ADDR_H = 12;
+ protected const int R13_START_ADDR_L = 13;
+ ///
+ /// This 14 bit read/write register stores the cursor location.This register consists of an 8 bit lower and 6 bit higher register.
+ ///
+ protected const int R14_CURSOR_H = 14;
+ protected const int R15_CURSOR_L = 15;
+ ///
+ /// This 14 bit read -only register is used to store the contents of the Address Register(H & L) when the LPSTB input pulses high.
+ /// This register consists of an 8 bit lower and 6 bit higher register.
+ ///
+ protected const int R16_LIGHT_PEN_H = 16;
+ protected const int R17_LIGHT_PEN_L = 17;
+
+ ///
+ /// Storage for main MPU registers
+ ///
+ /// RegIdx Register Name Type
+ /// 0 1 2 3 4
+ /// 0 Horizontal Total Write Only Write Only Write Only (note 2) (note 3)
+ /// 1 Horizontal Displayed Write Only Write Only Write Only (note 2) (note 3)
+ /// 2 Horizontal Sync Position Write Only Write Only Write Only (note 2) (note 3)
+ /// 3 H and V Sync Widths Write Only Write Only Write Only (note 2) (note 3)
+ /// 4 Vertical Total Write Only Write Only Write Only (note 2) (note 3)
+ /// 5 Vertical Total Adjust Write Only Write Only Write Only (note 2) (note 3)
+ /// 6 Vertical Displayed Write Only Write Only Write Only (note 2) (note 3)
+ /// 7 Vertical Sync position Write Only Write Only Write Only (note 2) (note 3)
+ /// 8 Interlace and Skew Write Only Write Only Write Only (note 2) (note 3)
+ /// 9 Maximum Raster Address Write Only Write Only Write Only (note 2) (note 3)
+ /// 10 Cursor Start Raster Write Only Write Only Write Only (note 2) (note 3)
+ /// 11 Cursor End Raster Write Only Write Only Write Only (note 2) (note 3)
+ /// 12 Disp. Start Address (High) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
+ /// 13 Disp. Start Address (Low) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
+ /// 14 Cursor Address (High) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
+ /// 15 Cursor Address (Low) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
+ /// 16 Light Pen Address (High) Read Only Read Only Read Only Read Only (note 2) (note 3)
+ ///
+ /// 18-31 Not Used
+ ///
+ /// 1. On type 0 and 1, if a Write Only register is read from, "0" is returned.
+ /// 2. See the document "Extra CPC Plus Hardware Information" for more details.
+ /// 3. CRTC type 4 is the same as CRTC type 3. The registers also repeat as they do on the type 3.
+ ///
+ protected byte[] Register = new byte[32];
+
+ ///
+ /// Internal Status Register specific to the Type 1 UM6845R
+ ///
+ protected byte StatusRegister;
+
+ ///
+ /// CRTC-type horizontal total independent helper function
+ ///
+ protected virtual int R0_HorizontalTotal
+ {
+ get
+ {
+ int ht = Register[R0_H_TOTAL];
+ return ht;
+ }
+ }
+
+ ///
+ /// CRTC-type horizontal displayed independent helper function
+ ///
+ protected virtual int R1_HorizontalDisplayed
+ {
+ get
+ {
+ int hd = Register[R1_H_DISPLAYED];
+ return hd;
+ }
+ }
+
+ ///
+ /// CRTC-type horizontal sync position independent helper function
+ ///
+ protected virtual int R2_HorizontalSyncPosition
+ {
+ get
+ {
+ int hsp = Register[R2_H_SYNC_POS];
+ return hsp;
+ }
+ }
+
+ ///
+ /// CRTC-type horizontal sync width independent helper function
+ ///
+ protected virtual int R3_HorizontalSyncWidth
+ {
+ get
+ {
+ int swr;
+
+ // Bits 3..0 define Horizontal Sync Width
+ int sw = Register[R3_SYNC_WIDTHS] & 0x0F;
+
+ switch (CrtcType)
+ {
+ case 0:
+ case 1:
+ // If 0 is programmed no HSYNC is generated
+ swr = sw;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ default:
+ // If 0 is programmed this gives a HSYNC width of 16
+ swr = sw > 0 ? sw : 16;
+ break;
+ }
+
+ return swr;
+ }
+ }
+
+ ///
+ /// CRTC-type vertical sync width independent helper function
+ ///
+ protected virtual int R3_VerticalSyncWidth
+ {
+ get
+ {
+ int swr;
+
+ //Bits 7..4 define Vertical Sync Width
+ int sw = (Register[R3_SYNC_WIDTHS] >> 4) & 0x0F;
+
+ switch (CrtcType)
+ {
+ case 0:
+ case 3:
+ case 4:
+ default:
+ // If 0 is programmed this gives 16 lines of VSYNC
+ swr = sw > 0 ? sw : 16;
+ break;
+ case 1:
+ case 2:
+ // Vertical Sync is fixed at 16 lines
+ swr = 16;
+ break;
+ }
+
+ return swr;
+ }
+ }
+
+ ///
+ /// CRTC-type vertical total independent helper function
+ ///
+ protected virtual int R4_VerticalTotal
+ {
+ get
+ {
+ int vt = Register[R4_V_TOTAL];
+ return vt;
+ }
+ }
+
+ ///
+ /// CRTC-type vertical total adjust independent helper function
+ ///
+ protected virtual int R5_VerticalTotalAdjust
+ {
+ get
+ {
+ int vta = Register[R5_V_TOTAL_ADJUST];
+ return vta;
+ }
+ }
+
+ ///
+ /// CRTC-type vertical displayed independent helper function
+ ///
+ protected virtual int R6_VerticalDisplayed
+ {
+ get
+ {
+ int vd = Register[R6_V_DISPLAYED];
+ return vd;
+ }
+ }
+
+ ///
+ /// CRTC-type vertical sync position independent helper function
+ ///
+ protected virtual int R7_VerticalSyncPosition
+ {
+ get
+ {
+ int vsp = Register[R7_V_SYNC_POS];
+ return vsp;
+ }
+ }
+
+ ///
+ /// CRTC-type DISPTMG Active Display Skew helper function
+ ///
+ protected virtual int R8_Skew
+ {
+ get
+ {
+ int skew = 0;
+ switch (CrtcType)
+ {
+ case 0:
+ // For Hitachi HD6845:
+ // 0 = no skew
+ // 1 = one-character skew
+ // 2 = two-character skew
+ // 3 = non-output
+ skew = (Register[R8_INTERLACE_MODE] >> 4) & 0x03;
+ break;
+ case 1:
+ // skew not implemented
+ break;
+ case 2:
+ // skew not implemented
+ break;
+ default:
+ return skew;
+ }
+ return skew;
+ }
+ }
+
+ ///
+ /// CRTC-type Interlace Mode helper function
+ ///
+ protected virtual int R8_Interlace
+ {
+ get
+ {
+ int interlace = 0;
+ switch (CrtcType)
+ {
+
+ case 0:
+ case 1:
+ case 2:
+ // 0 = Non-interlace
+ // 1 = Interlace SYNC Raster Scan
+ // 2 = Interlace SYNC and Video Raster Scan
+ interlace = Register[R8_INTERLACE_MODE] & 0x03;
+ if (!interlace.Bit(0))
+ {
+ interlace = 0;
+ }
+ break;
+ default:
+ break;
+ }
+ return interlace;
+ }
+ }
+
+ ///
+ /// Max Scanlines
+ ///
+ protected virtual int R9_MaxScanline
+ {
+ get
+ {
+ int max = Register[R9_MAX_SL_ADDRESS];
+ return max;
+ }
+ }
+
+ ///
+ /// C0: Horizontal Character Counter
+ /// 8-bit
+ ///
+ protected virtual int HCC
+ {
+ get => _hcCTR & 0xFF;
+ set => _hcCTR = value & 0xFF;
+ }
+ private int _hcCTR;
+
+ ///
+ /// C3l: Horizontal Sync Width Counter (HSYNC)
+ /// 4-bit
+ ///
+ protected virtual int HSC
+ {
+ get => _hswCTR & 0x0F;
+ set => _hswCTR = value & 0x0F;
+ }
+ private int _hswCTR;
+
+ ///
+ /// C4: Vertical Character Row Counter
+ /// 7-bit
+ ///
+ protected virtual int VCC
+ {
+ get => _rowCTR & 0x7F;
+ set => _rowCTR = value & 0x7F;
+ }
+ private int _rowCTR;
+
+ ///
+ /// C3h: Vertical Sync Width Counter (VSYNC)
+ /// 4-bit
+ ///
+ protected virtual int VSC
+ {
+ get => _vswCTR & 0x0F;
+ set => _vswCTR = value & 0x0F;
+ }
+ private int _vswCTR;
+
+ ///
+ /// C9: Vertical Line Counter (Scanline Counter)
+ /// 5-bit
+ /// If not in IVM mode, this counter is exposed on CRTC pins RA0..RA4
+ ///
+ protected virtual int VLC
+ {
+ get => _lineCTR & 0x1F;
+ set => _lineCTR = value & 0x1F;
+ }
+ private int _lineCTR;
+
+ ///
+ /// C5: Vertical Total Adjust Counter
+ /// 5-bit??
+ /// This counter does not exist on CRTCs 0/3/4. C9 (VLC) is reused instead
+ ///
+ protected virtual int VTAC
+ {
+ get
+ {
+ switch (CrtcType)
+ {
+ case 0:
+ case 3:
+ case 4:
+ return VLC;
+ default:
+ return _vtacCTR & 0x1F;
+ }
+ }
+ set
+ {
+ switch (CrtcType)
+ {
+ case 0:
+ case 3:
+ case 4:
+ //VLC = value;
+ break;
+ default:
+ _vtacCTR = value & 0x1F;
+ break;
+ }
+ }
+ }
+ private int _vtacCTR;
+
+ ///
+ /// Field Counter
+ /// 6-bit
+ /// Used for cursor flash - counts the number of completed fields
+ ///
+ protected virtual int CFC
+ {
+ get => _fieldCTR & 0x1F;
+ set => _fieldCTR = value & 0x1F;
+ }
+ private int _fieldCTR;
+
+
+ ///
+ /// Constructor
+ ///
+ public CRTC()
+ {
+ //Reset();
+ }
+
+ // persistent control signals
+ protected bool latch_hsync;
+ protected bool latch_vadjust;
+ protected bool latch_skew;
+
+ private bool hend;
+ private bool hsend;
+
+ protected bool adjusting;
+
+ private bool hclock;
+
+
+ protected bool latch_hdisp;
+ protected bool latch_vdisp;
+ protected bool latch_idisp;
+ protected bool hssstart;
+ protected bool hhclock;
+ protected bool _field;
+ protected int _vma;
+ protected int _vmaRowStart;
+
+ ///
+ /// CRTC is clocked at 1MHz (16 GA cycles)
+ ///
+ public virtual void Clock()
+ {
+ if (_inReset > 0)
+ {
+ // reset takes a whole CRTC clock cycle
+ _inReset--;
+
+ HCC = 0;
+ HSC = 0;
+ VCC = 0;
+ VSC = 0;
+ VLC = 0;
+ VTAC = 0;
+ CFC = 0;
+ _field = false;
+ _vmaRowStart = 0;
+ _vma = 0;
+ _LA = 0;
+ _RA = 0;
+ latch_hdisp = false;
+ latch_vdisp = false;
+ latch_idisp = false;
+
+ /*
+ // set regs to default
+ for (int i = 0; i < 18; i++)
+ Register[i] = RegDefaults[i];
+ */
+
+ // regs aren't touched
+
+
+ return;
+ }
+ else
+ _inReset = -1;
+ }
+
+ ///
+ /// Selects a specific register
+ ///
+ protected void SelectRegister(int value)
+ {
+ byte v = (byte)(value & 0x1F);
+ AddressRegister = v;
+ }
+
+ ///
+ /// Attempts to read from the currently selected register
+ ///
+ protected virtual bool ReadRegister(ref int data) => false;
+
+ ///
+ /// Attempts to write to the currently selected register
+ ///
+ protected virtual void WriteRegister(int data) {}
+
+ ///
+ /// Attempts to read from the internal status register (if present)
+ ///
+ protected virtual bool ReadStatus(ref int data) => false;
+
+ ///
+ /// Device responds to an IN instruction
+ ///
+ public virtual bool ReadPort(ushort port, ref int result)
+ {
+ byte portUpper = (byte)(port >> 8);
+
+ bool accessed = false;
+
+ // The 6845 is selected when bit 14 of the I/O port address is set to "0"
+ if (portUpper.Bit(6))
+ return accessed;
+
+ // Bit 9 and 8 of the I/O port address define the function to access
+ if (portUpper.Bit(1) && !portUpper.Bit(0))
+ {
+ // read status register
+ accessed = ReadStatus(ref result);
+ }
+ else if ((portUpper & 3) == 3)
+ {
+ // read data register
+ accessed = ReadRegister(ref result);
+ }
+ else
+ {
+ result = 0;
+ }
+
+ return accessed;
+ }
+
+ ///
+ /// Device responds to an OUT instruction
+ ///
+ public virtual bool WritePort(ushort port, int result)
+ {
+ byte portUpper = (byte)(port >> 8);
+
+ bool accessed = false;
+
+ // The 6845 is selected when bit 14 of the I/O port address is set to "0"
+ if (portUpper.Bit(6))
+ return accessed;
+
+ int func = portUpper & 3;
+
+ switch (func)
+ {
+ // reg select
+ case 0:
+ SelectRegister(result);
+ break;
+
+ // data write
+ case 1:
+ WriteRegister(result);
+ break;
+ }
+
+ return accessed;
+ }
+
+ ///
+ /// Simulates the RESET pin
+ /// This should take at least one cycle
+ ///
+ public void Reset() => _inReset = 1;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("CRTC");
+ ser.Sync(nameof(CLK), ref CLK);
+ ser.Sync(nameof(_VSYNC), ref _VSYNC);
+ ser.Sync(nameof(_HSYNC), ref _HSYNC);
+ ser.Sync(nameof(_DISPTMG), ref _DISPTMG);
+ ser.Sync(nameof(_CUDISP), ref _CUDISP);
+ ser.Sync(nameof(_LA), ref _LA);
+ ser.Sync(nameof(_RA), ref _RA);
+ ser.Sync(nameof(_addressRegister), ref _addressRegister);
+ ser.Sync(nameof(Register), ref Register, false);
+ ser.Sync(nameof(StatusRegister), ref StatusRegister);
+ ser.Sync(nameof(_hcCTR), ref _hcCTR);
+ ser.Sync(nameof(_hswCTR), ref _hswCTR);
+ ser.Sync(nameof(_vswCTR), ref _vswCTR);
+ ser.Sync(nameof(_rowCTR), ref _rowCTR);
+ ser.Sync(nameof(_lineCTR), ref _lineCTR);
+ ser.Sync(nameof(_vtacCTR), ref _vtacCTR);
+ ser.Sync(nameof(_fieldCTR), ref _fieldCTR);
+ ser.Sync(nameof(latch_hdisp), ref latch_hdisp);
+ ser.Sync(nameof(latch_vdisp), ref latch_vdisp);
+ ser.Sync(nameof(latch_hsync), ref latch_hsync);
+ ser.Sync(nameof(latch_vadjust), ref latch_vadjust);
+ ser.Sync(nameof(latch_skew), ref latch_skew);
+ ser.Sync(nameof(_field), ref _field);
+ ser.Sync(nameof(adjusting), ref adjusting);
+ ser.Sync(nameof(_inReset), ref _inReset);
+ ser.Sync(nameof(hend), ref hend);
+ ser.Sync(nameof(hsend), ref hsend);
+ ser.Sync(nameof(hclock), ref hclock);
+ ser.Sync(nameof(latch_idisp), ref latch_idisp);
+ ser.Sync(nameof(hssstart), ref hssstart);
+ ser.Sync(nameof(hhclock), ref hhclock);
+ ser.Sync(nameof(_vma), ref _vma);
+ ser.Sync(nameof(_vmaRowStart), ref _vmaRowStart);
+ ser.EndSection();
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Datacorder/DatacorderDevice.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Datacorder/DatacorderDevice.cs
index c94740ff1a..5996284b01 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Datacorder/DatacorderDevice.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Datacorder/DatacorderDevice.cs
@@ -13,8 +13,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public sealed class DatacorderDevice
{
private CPCBase _machine;
- private Z80A _cpu => _machine.CPU;
- private IBeeperDevice _buzzer => _machine.TapeBuzzer;
+ private Z80A CPU => _machine.CPU;
+ private IBeeperDevice Buzzer => _machine.TapeBuzzer;
///
/// Default constructor
@@ -27,10 +27,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
/// Initializes the datacorder device
///
- public void Init(CPCBase machine)
- {
- _machine = machine;
- }
+ public void Init(CPCBase machine) => _machine = machine;
///
/// Signs whether the tape motor is running
@@ -81,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
get
{
- if (_dataBlocks.Any())
+ if (DataBlocks.Any())
{
return _currentDataBlockIndex;
}
@@ -91,7 +88,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
set
{
if (value == _currentDataBlockIndex) { return; }
- if (value < _dataBlocks.Count && value >= 0)
+ if (value < DataBlocks.Count && value >= 0)
{
_currentDataBlockIndex = value;
_position = 0;
@@ -103,7 +100,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// The current position within the current data block
///
private int _position = 0;
- public int Position => _position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count ? 0 : _position;
+ public int Position => _position >= DataBlocks[_currentDataBlockIndex].DataPeriods.Count ? 0 : _position;
///
/// Signs whether the tape is currently playing or not
@@ -111,15 +108,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
private bool _tapeIsPlaying = false;
public bool TapeIsPlaying => _tapeIsPlaying;
- ///
- /// A list of the currently loaded data blocks
- ///
- private List _dataBlocks = new List();
- public List DataBlocks
- {
- get => _dataBlocks;
- set => _dataBlocks = value;
- }
+ public List DataBlocks { get; set; } = new List();
///
/// Stores the last CPU t-state value
@@ -140,7 +129,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// Signs whether the device should autodetect when the Z80 has entered into
/// 'load' mode and auto-play the tape if neccesary
///
- private bool _autoPlay;
+ private readonly bool _autoPlay;
///
/// Should be fired at the end of every frame
@@ -170,29 +159,29 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_machine.CPC.OSD_TapeMotorActive();
// update the lastCycle
- _lastCycle = _cpu.TotalExecutedCycles;
+ _lastCycle = CPU.TotalExecutedCycles;
// reset waitEdge and position
_waitEdge = 0;
_position = 0;
- if (_dataBlocks.Count > 0 && _currentDataBlockIndex >= 0) //TODO removed a comment that said "index is 1 or greater", but code is clearly "0 or greater"--which is correct? --yoshi
+ if (DataBlocks.Count > 0 && _currentDataBlockIndex >= 0) //TODO removed a comment that said "index is 1 or greater", but code is clearly "0 or greater"--which is correct? --yoshi
{
- while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count)
+ while (_position >= DataBlocks[_currentDataBlockIndex].DataPeriods.Count)
{
// we are at the end of a data block - move to the next
_position = 0;
_currentDataBlockIndex++;
// are we at the end of the tape?
- if (_currentDataBlockIndex >= _dataBlocks.Count)
+ if (_currentDataBlockIndex >= DataBlocks.Count)
{
break;
}
}
// check for end of tape
- if (_currentDataBlockIndex >= _dataBlocks.Count)
+ if (_currentDataBlockIndex >= DataBlocks.Count)
{
// end of tape reached. Rewind to beginning
AutoStopTape();
@@ -201,7 +190,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
// update waitEdge with the current position in the current block
- _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position];
+ _waitEdge = DataBlocks[_currentDataBlockIndex].DataPeriods[_position];
// sign that the tape is now playing
_tapeIsPlaying = true;
@@ -223,12 +212,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_tapeIsPlaying = false;
if (_currentDataBlockIndex >= 0 // we are at datablock 1 or above //TODO 1-indexed then? --yoshi
- && _position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count - 1) // the block is still playing back
+ && _position >= DataBlocks[_currentDataBlockIndex].DataPeriods.Count - 1) // the block is still playing back
{
// move to the next block
_currentDataBlockIndex++;
- if (_currentDataBlockIndex >= _dataBlocks.Count)
+ if (_currentDataBlockIndex >= DataBlocks.Count)
{
_currentDataBlockIndex = -1;
}
@@ -237,7 +226,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_waitEdge = 0;
_position = 0;
- if (_currentDataBlockIndex < 0 && _dataBlocks.Count > 0) //TODO deleted a comment that said "block index is -1", but code is clearly "is negative"--are lower values not reachable? --yoshi
+ if (_currentDataBlockIndex < 0 && DataBlocks.Count > 0) //TODO deleted a comment that said "block index is -1", but code is clearly "is negative"--are lower values not reachable? --yoshi
{
// move the index on to 0
_currentDataBlockIndex = 0;
@@ -245,7 +234,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
// update the lastCycle
- _lastCycle = _cpu.TotalExecutedCycles;
+ _lastCycle = CPU.TotalExecutedCycles;
}
///
@@ -265,7 +254,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public void SkipBlock(bool skipForward)
{
- int blockCount = _dataBlocks.Count;
+ int blockCount = DataBlocks.Count;
int targetBlockId = _currentDataBlockIndex;
if (skipForward)
@@ -293,11 +282,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
- var bl = _dataBlocks[targetBlockId];
+ var bl = DataBlocks[targetBlockId];
- StringBuilder sbd = new StringBuilder();
+ var sbd = new StringBuilder();
sbd.Append('(');
- sbd.Append((targetBlockId + 1) + " of " + _dataBlocks.Count);
+ sbd.Append((targetBlockId + 1) + " of " + DataBlocks.Count);
sbd.Append(") : ");
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
sbd.Append(bl.BlockDescription);
@@ -323,7 +312,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public void LoadTape(byte[] tapeData)
{
// instantiate converters
- CdtConverter cdtSer = new CdtConverter(this);
+ var cdtSer = new CdtConverter(this);
// CDT
if (cdtSer.CheckType(tapeData))
@@ -347,10 +336,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
/// Resets the tape
///
- public void Reset()
- {
- RTZ();
- }
+ public void Reset() => RTZ();
///
/// Is called every cpu cycle but runs every 50 t-states
@@ -367,7 +353,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
counter = 0;
bool state = GetEarBit(_machine.CPU.TotalExecutedCycles);
- _buzzer.ProcessPulseValue(state);
+ Buzzer.ProcessPulseValue(state);
}
}
}
@@ -377,6 +363,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public bool GetEarBit(long cpuCycle)
{
+ if (DataBlocks.Count == 0)
+ {
+ // no tape loaded
+ return false;
+ }
+
// decide how many cycles worth of data we are capturing
long cycles = cpuCycle - _lastCycle;
@@ -400,12 +392,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
while (cycles >= _waitEdge)
{
// decrement cycles
- cycles -= _waitEdge;
+ cycles -= _waitEdge;
if (_position == 0 && tapeMotor)
{
// start of block - take care of initial pulse level for PZX
- switch (_dataBlocks[_currentDataBlockIndex].BlockDescription)
+ switch (DataBlocks[_currentDataBlockIndex].BlockDescription)
{
case BlockType.PULS:
// initial pulse level is always low
@@ -414,19 +406,19 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
break;
case BlockType.DATA:
// initial pulse level is stored in block
- if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
+ if (currentState != DataBlocks[_currentDataBlockIndex].InitialPulseLevel)
FlipTapeState();
break;
case BlockType.PAUS:
// initial pulse level is stored in block
- if (currentState != _dataBlocks[_currentDataBlockIndex].InitialPulseLevel)
+ if (currentState != DataBlocks[_currentDataBlockIndex].InitialPulseLevel)
FlipTapeState();
break;
}
// most of these amstrad tapes appear to have a pause block at the start
// skip this if it is the first block
- switch (_dataBlocks[_currentDataBlockIndex].BlockDescription)
+ switch (DataBlocks[_currentDataBlockIndex].BlockDescription)
{
case BlockType.PAUS:
case BlockType.PAUSE_BLOCK:
@@ -442,7 +434,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
bool okToSkipPause = true;
for (int i = _currentDataBlockIndex; i >= 0; i--)
{
- switch (_dataBlocks[i].BlockDescription)
+ switch (DataBlocks[i].BlockDescription)
{
case BlockType.Archive_Info:
case BlockType.BRWS:
@@ -472,11 +464,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
// notify about the current block
- var bl = _dataBlocks[_currentDataBlockIndex];
+ var bl = DataBlocks[_currentDataBlockIndex];
- StringBuilder sbd = new StringBuilder();
+ var sbd = new StringBuilder();
sbd.Append('(');
- sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count);
+ sbd.Append((_currentDataBlockIndex + 1) + " of " + DataBlocks.Count);
sbd.Append(") : ");
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
sbd.Append(bl.BlockDescription);
@@ -492,17 +484,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// increment the current period position
_position++;
- if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count)
+ if (_position >= DataBlocks[_currentDataBlockIndex].DataPeriods.Count)
{
// we have reached the end of the current block
- if (_dataBlocks[_currentDataBlockIndex].DataPeriods.Count == 0)
+ if (DataBlocks[_currentDataBlockIndex].DataPeriods.Count == 0)
{
// notify about the current block (we are skipping it because its empty)
- var bl = _dataBlocks[_currentDataBlockIndex];
- StringBuilder sbd = new StringBuilder();
+ var bl = DataBlocks[_currentDataBlockIndex];
+ var sbd = new StringBuilder();
sbd.Append('(');
- sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count);
+ sbd.Append((_currentDataBlockIndex + 1) + " of " + DataBlocks.Count);
sbd.Append(") : ");
//sbd.Append("ID" + bl.BlockID.ToString("X2") + " - ");
sbd.Append(bl.BlockDescription);
@@ -516,11 +508,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
// skip any empty blocks (and process any command blocks)
- while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count)
+ while (_position >= DataBlocks[_currentDataBlockIndex].DataPeriods.Count)
{
// check for any commands
- var command = _dataBlocks[_currentDataBlockIndex].Command;
- var block = _dataBlocks[_currentDataBlockIndex];
+ var command = DataBlocks[_currentDataBlockIndex].Command;
+ var block = DataBlocks[_currentDataBlockIndex];
bool shouldStop = false;
switch (command)
{
@@ -573,14 +565,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
_position = 0;
_currentDataBlockIndex++;
- if (_currentDataBlockIndex >= _dataBlocks.Count)
+ if (_currentDataBlockIndex >= DataBlocks.Count)
{
break;
}
}
// check for end of tape
- if (_currentDataBlockIndex >= _dataBlocks.Count)
+ if (_currentDataBlockIndex >= DataBlocks.Count)
{
_currentDataBlockIndex = -1;
RTZ();
@@ -589,7 +581,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
// update waitEdge with current position within the current block
- _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position];
+ _waitEdge = DataBlocks[_currentDataBlockIndex].DataPeriods[_position];
// flip the current state
FlipTapeState();
@@ -720,7 +712,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
if (TapeIsPlaying)
{
- GetEarBit(_cpu.TotalExecutedCycles);
+ GetEarBit(CPU.TotalExecutedCycles);
}
/*
if (currentState)
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs
deleted file mode 100644
index 1704c795cb..0000000000
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/AmstradGateArray.cs
+++ /dev/null
@@ -1,1323 +0,0 @@
-using BizHawk.Common;
-using BizHawk.Common.NumberExtensions;
-using BizHawk.Emulation.Common;
-using BizHawk.Emulation.Cores.Components.Z80A;
-
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
-{
- ///
- /// * 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
- ///
- public class AmstradGateArray : IPortIODevice, IVideoProvider
- {
- private readonly 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;
-
- ///
- /// The standard CPC Pallete (ordered by firmware #)
- /// http://www.cpcwiki.eu/index.php/CPC_Palette
- ///
- 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
- };
-
- ///
- /// The standard CPC Pallete (ordered by hardware #)
- /// http://www.cpcwiki.eu/index.php/CPC_Palette
- ///
- 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
- };
-
- ///
- /// The Gate Array Clock Speed
- ///
- public int GAClockSpeed = 16000000;
-
- ///
- /// The CPU Clock Speed
- ///
- public int Z80ClockSpeed = 4000000;
-
- ///
- /// CRCT Clock Speed
- ///
- public int CRCTClockSpeed = 1000000;
-
- ///
- /// AY-3-8912 Clock Speed
- ///
- public int PSGClockSpeed = 1000000;
-
- ///
- /// Number of CPU cycles in one frame
- ///
- public int FrameLength = 79872;
-
- ///
- /// Number of Gate Array cycles within one frame
- ///
- public int GAFrameLength = 319488;
-
- 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();
- }
-
- ///
- /// 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
- ///
- private byte _PENR;
- public byte PENR
- {
- get => _PENR;
- set
- {
- _PENR = value;
- if (_PENR.Bit(4))
- {
- // border select
- CurrentPen = 16;
- }
- else
- {
- // pen select
- CurrentPen = _PENR & 0x0f;
- }
- }
- }
-
- ///
- /// 0-15: Pen Registers
- /// 16: Border Colour
- ///
- public int[] ColourRegisters = new int[17];
-
- ///
- /// The currently selected Pen
- ///
- private int CurrentPen;
-
- ///
- /// 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
- ///
- private byte _INKR;
- public byte INKR
- {
- get => _INKR;
- set
- {
- _INKR = value;
- ColourRegisters[CurrentPen] = _INKR & 0x1f;
- }
- }
-
- ///
- /// 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)
- ///
- private byte _RMR;
- public byte RMR
- {
- get => _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;
- }
- }
- }
-
- ///
- /// 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
- ///
- private byte _RAMR;
- ///
- /// This is actually implemented outside of here. These values do nothing.
- ///
- public byte RAMR
- {
- get => _RAMR;
- set => _RAMR = value;
- }
-
- ///
- /// The selected screen mode (updated after every HSYNC)
- ///
- private int ScreenMode;
-
- ///
- /// Simulates the internal 6bit INT counter
- ///
- private int _interruptCounter;
- public int InterruptCounter
- {
- get => _interruptCounter;
- set => _interruptCounter = value;
- }
-
- ///
- /// Interrupts are delayed when a VSYNC occurs
- ///
- private int VSYNCDelay;
-
- ///
- /// Signals that the frame end has been reached
- ///
- public bool FrameEnd;
-
- ///
- /// Internal phase clock
- ///
- private int ClockCounter;
-
- ///
- /// Master frame clock counter
- ///
- public int FrameClock;
-
- ///
- /// Simulates the gate array memory /WAIT line
- ///
- private bool WaitLine;
-
- ///
- /// 16-bit address - read from the CRCT
- ///
- private short _MA;
- private short MA
- {
- get
- {
- _MA = CRCT.MA;
- return _MA;
- }
- }
-
- ///
- /// Set when the HSYNC signal is detected from the CRCT
- ///
- private bool HSYNC;
-
- // ///
- // /// 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
- // ///
- // private bool HSYNC_falling;
-
- ///
- /// Used to count HSYNCs during a VSYNC
- ///
- private int HSYNC_counter;
-
- ///
- /// Set when the VSYNC signal is detected from the CRCT
- ///
- private bool VSYNC;
-
- ///
- /// TRUE when the /INT pin is held low
- ///
- private bool InterruptRaised;
-
- ///
- /// Counts the GA cycles that the /INT pin should be held low
- ///
- private int InterruptHoldCounter;
-
- ///
- /// Set at the start of a new frame
- ///
- public bool IsNewFrame;
-
- ///
- /// Set when a new line is beginning
- ///
- public bool IsNewLine;
-
- ///
- /// Horizontal Character Counter
- ///
- private int HCC;
-
- ///
- /// Vertical Line Counter
- ///
- private int VLC;
-
- ///
- /// The first video byte fetched
- ///
- private byte VideoByte1;
-
- ///
- /// The second video byte fetched
- ///
- private byte VideoByte2;
-
- ///
- /// 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)
- ///
- 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();
- }
-
- ///
- /// Increments the internal clock counters by one
- ///
- private void AdvanceClock()
- {
- FrameClock++;
- ClockCounter++;
-
- if (ClockCounter == 4)
- ClockCounter = 0;
-
- // check for frame end
- if (FrameClock == FrameLength)
- {
- FrameEnd = true;
- }
- }
-
- ///
- /// Runs a 4 Mhz CPU cycle if neccessary
- /// /WAIT line status is a factor here
- ///
- 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;
- }
- }
-
- ///
- /// 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.
- ///
- 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;
- }
- }
-
- ///
- /// Handles interrupt generation
- ///
- 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;
- }
- }
- }
-
- ///
- /// 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
- ///
- 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);
- }
- }
-
- ///
- /// Holds the upcoming video RAM addresses for the next scanline
- /// Firmware default size is 80 (40 characters - 2 bytes per character)
- ///
- private ushort[] NextVidRamLine = new ushort[40 * 2];
-
- ///
- /// The current character line we are working from
- ///
- private readonly CharacterLine CurrentLine;
-
- ///
- /// List of screen lines as they are built up
- ///
- private readonly List ScreenLines = new List();
-
- ///
- /// Pixel value lookups for every scanline byte value
- /// Based on the lookup at https://github.com/gavinpugh/xnacpc
- ///
- private readonly 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;
- }
- }
- }
- }
-
- ///
- /// Runs at HSYNC *AFTER* the scanline has been commmitted
- /// Sets up the upcoming memory addresses for the next scanline
- ///
- 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;
- }
- }
-
- ///
- /// Called at the start of HSYNC, this renders the currently built-up scanline
- ///
- 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()
- });
- }
-
- ///
- /// Called when the Z80 acknowledges an interrupt
- ///
- public void IORQA()
- {
- // bit 5 of the interrupt counter is reset
- InterruptCounter &= ~(1 << 5);
- }
-
- private int slCounter = 0;
- private int slBackup = 0;
-
- ///
- /// Fired when the CRCT flags HSYNC
- ///
- 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);
- }
-
- ///
- /// Fired when the CRCT flags VSYNC
- ///
- public void OnVSYNC()
- {
- FrameEnd = true;
- slBackup = slCounter;
- slCounter = 0;
- }
-
- public int[] ScreenBuffer;
-
- private int _virtualWidth;
- private int _virtualHeight;
- private int _bufferWidth;
- private int _bufferHeight;
-
- public int BackgroundColor => CPCHardwarePalette[0];
-
- 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 SysBufferWidth;
- public int SysBufferHeight;
-
- public int VsyncNumerator
- {
- get => 200000000;
- set { }
- }
-
- public int VsyncDenominator => 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 (var i = 0; i < excessL; i++) lCop.RemoveAt(0);
- for (var i = 0; i < excessL; i++) lCop.RemoveAt(lCop.Count - 1); //TODO should be using excessR?
- }
-
- 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 => _borderType;
- set => _borderType = value;
- }
-
- ///
- /// 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));
- 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;
- }
-
- 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();
- }
-
- ///
- /// Represents a single scanline (in characters)
- ///
- public class CharacterLine
- {
-#if false
- ///
- /// Screenmode is defined at each HSYNC (start of a new character line)
- /// Therefore we pass the mode in via constructor
- ///
- public CharacterLine(int screenMode)
- => ScreenMode = screenMode;
-#endif
-
- public int ScreenMode = 1;
- public List Phases = new List();
- public List Pixels = new List();
-
- ///
- /// Adds a new horizontal character to the list
- ///
- public void AddCharacter(Phase phase)
- {
- Phases.Add(phase);
- }
-
- public int PhaseCount => Phases.Count;
-
- public void Clear(int screenMode)
- {
- ScreenMode = screenMode;
- Phases.Clear();
- Pixels.Clear();
- }
- }
-
- [Flags]
- public enum Phase : int
- {
- ///
- /// Nothing
- ///
- NONE = 0,
- ///
- /// Border is being rendered
- ///
- BORDER = 1,
- ///
- /// Display rendered from video RAM
- ///
- DISPLAY = 2,
- ///
- /// HSYNC in progress
- ///
- HSYNC = 3,
- ///
- /// VSYNC in process
- ///
- VSYNC = 4,
- ///
- /// HSYNC occurs within a VSYNC
- ///
- HSYNCandVSYNC = 5
- }
-
- public enum GateArrayType
- {
- ///
- /// CPC 464
- /// The first version of the Gate Array is the 40007 and was released with the CPC 464
- ///
- Amstrad40007,
- ///
- /// 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)
- ///
- Amstrad40008,
- ///
- /// 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
- ///
- Amstrad40010,
- ///
- /// 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
- ///
- Amstrad40226,
- ///
- /// 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
- ///
- Amstrad40489,
- }
- }
-}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs
deleted file mode 100644
index d1f6d33d9b..0000000000
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRCT_6845.cs
+++ /dev/null
@@ -1,1100 +0,0 @@
-using BizHawk.Common;
-using BizHawk.Common.NumberExtensions;
-
-namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
-{
- ///
- /// Cathode Ray Tube Controller Chip - 6845
- /// http://www.cpcwiki.eu/index.php/CRTC
- /// https://web.archive.org/web/20170501112330/http://www.grimware.org/doku.php/documentations/devices/crtc
- ///
- public class CRCT_6845 : IPortIODevice
- {
- private CPCBase _machine { get; set; }
- private CRCTType ChipType;
-
- public delegate void CallBack();
-
- private CallBack HSYNC_Callbacks;
- private CallBack VSYNC_Callbacks;
-
- public void AttachVSYNCCallback(CallBack vCall)
- {
- VSYNC_Callbacks += vCall;
- }
-
- public void AttachHSYNCCallback(CallBack hCall)
- {
- HSYNC_Callbacks += hCall;
- }
-
- public CRCT_6845(CRCTType chipType, CPCBase machine)
- {
- _machine = machine;
- ChipType = chipType;
- Reset();
- }
-
- private const int WRITE = 0;
- private const int READ = 1;
-
- ///
- /// Denotes that HSYNC is active
- ///
- public bool HSYNC = false;
-
- ///
- /// Denotes that VSYNC is active
- ///
- public bool VSYNC = false;
-
- ///
- /// TRUE: bits outputted to screen from video RAM
- /// FALSE: current border colour is outputted
- ///
- public bool DISPTMG = true;
-
- ///
- /// 16-bit memory address lines
- /// The gate array uses this to grab the correct bits from video RAM
- ///
- public short MA;
-
- ///
- /// Vertical Character Count
- ///
- public int VCC;
-
- ///
- /// Vertical Scanline Count (within the current vertical character)
- ///
- public int VLC;
-
- /*
- * These are not accessible directlyon real hardware
- * It just makes screen generation easier to have these accessbile from the gate array
- */
-
- ///
- /// The total frame width (in characters)
- ///
- public int FrameWidth => Regs[HOR_TOTAL] + 1;
-
- ///
- /// The total frame height (in scanlines)
- ///
- public int FrameHeight => (Regs[VER_TOTAL] + 1) * (Regs[MAX_RASTER_ADDR] + 1);
-
- ///
- /// The total frame height (in scanlines)
- ///
- public int FrameHeightInChars => (Regs[VER_TOTAL] + 1);
-
- ///
- /// The width of the display area (in characters)
- ///
- public int DisplayWidth => Regs[HOR_DISPLAYED];
-
- ///
- /// The width of the display area (in scanlines)
- ///
- public int DisplayHeight => Regs[VER_DISPLAYED] * (Regs[MAX_RASTER_ADDR] + 1);
-
- ///
- /// The width of the display area (in scanlines)
- ///
- public int DisplayHeightInChars => Regs[VER_DISPLAYED];
-
- ///
- /// The character at which to start HSYNC
- ///
- public int HorizontalSyncPos => Regs[HOR_SYNC_POS];
-
- ///
- /// Width (in characters) of the HSYNC
- ///
- public int HorizontalSyncWidth => HSYNCWidth;
-
- ///
- /// The vertical scanline at which to start VSYNC
- ///
- public int VerticalSyncPos => Regs[VER_SYNC_POS] * (Regs[MAX_RASTER_ADDR] + 1);
-
- ///
- /// Height (in scanlines) of the VSYNC
- ///
- public int VerticalSyncHeight => VSYNCWidth; // * ((int)Regs[MAX_RASTER_ADDR] + 1);
-
- ///
- /// The number of scanlines in one character (MAXRASTER)
- ///
- public int ScanlinesPerCharacter => Regs[MAX_RASTER_ADDR] + 1;
-
- ///
- /// Returns the starting video page address as specified within R12
- ///
- public int VideoPageBase
- {
- get
- {
- if (!Regs[12].Bit(4) && Regs[12].Bit(5))
- return 0x8000;
-
- if (Regs[12].Bit(4) && !Regs[12].Bit(5))
- return 0x4000;
-
- if (!Regs[12].Bit(4) && !Regs[12].Bit(5))
- return 0x0000;
-
- return 0xC000;
- }
- }
-
- public int DStartHigh => Regs[DISP_START_ADDR_H];
-
- public int DStartLow => Regs[DISP_START_ADDR_L];
-
- ///
- /// Returns the video buffer size as specified within R12
- ///
- public int VideoBufferSize
- {
- get
- {
- if (Regs[12].Bit(3) && Regs[12].Bit(2))
- return 0x8000;
-
- return 0x4000;
- }
- }
-
- ///
- /// The offset into vRAM
- ///
- public int VideoRAMOffset
- {
- get
- {
- ushort combined = (ushort)(Regs[12] << 8 | Regs[13]);
- int offset = combined & 0x3ff;
- return offset;
- }
- }
-
-
- /* Easier memory functions */
-
- ///
- /// The current byte address
- ///
- public ushort CurrentByteAddress;
-
- ///
- /// ByteCounter
- ///
- public int ByteCounter;
-
- ///
- /// Set at every HSYNC
- ///
- public int LatchedRAMOffset;
-
- ///
- /// set at every HSYNC
- ///
- public int LatchedRAMStartAddress;
-
- ///
- /// set at every HSYNC
- ///
- public int LatchedScreenWidthBytes;
-
- /*
- Index Register Name Range CPC Setting Notes
- 0 Horizontal Total 00000000 63 Width of the screen, in characters. Should always be 63 (64 characters). 1 character == 1μs.
- 1 Horizontal Displayed 00000000 40 Number of characters displayed. Once horizontal character count (HCC) matches this value, DISPTMG is set to 1.
- 2 Horizontal Sync Position 00000000 46 When to start the HSync signal.
- 3 Horizontal and Vertical Sync Widths VVVVHHHH 128+14 HSync pulse width in characters (0 means 16 on some CRTC), should always be more than 8; VSync width in scan-lines. (0 means 16 on some CRTC. Not present on all CRTCs, fixed to 16 lines on these)
- 4 Vertical Total x0000000 38 Height of the screen, in characters.
- 5 Vertical Total Adjust xxx00000 0 Measured in scanlines, can be used for smooth vertical scrolling on CPC.
- 6 Vertical Displayed x0000000 25 Height of displayed screen in characters. Once vertical character count (VCC) matches this value, DISPTMG is set to 1.
- 7 Vertical Sync position x0000000 30 When to start the VSync signal, in characters.
- 8 Interlace and Skew xxxxxx00 0 00: No interlace; 01: Interlace Sync Raster Scan Mode; 10: No Interlace; 11: Interlace Sync and Video Raster Scan Mode
- 9 Maximum Raster Address xxx00000 7 Maximum scan line address on CPC can hold between 0 and 7, higher values' upper bits are ignored
- 10 Cursor Start Raster xBP00000 0 Cursor not used on CPC. B = Blink On/Off; P = Blink Period Control (Slow/Fast). Sets first raster row of character that cursor is on to invert.
- 11 Cursor End Raster xxx00000 0 Sets last raster row of character that cursor is on to invert
- 12 Display Start Address (High) xx000000 32
- 13 Display Start Address (Low) 00000000 0 Allows you to offset the start of screen memory for hardware scrolling, and if using memory from address &0000 with the firmware.
- 14 Cursor Address (High) xx000000 0
- 15 Cursor Address (Low) 00000000 0
- 16 Light Pen Address (High) xx000000 Read Only
- 17 Light Pen Address (Low) 00000000 Read Only
- */
- ///
- /// 6845 internal registers
- ///
- private byte[] Regs = new byte[18];
-
- // CRTC Register constants
- ///
- /// R0: Horizontal total character number
- /// Unit: Character
- /// Notes: Defines the width of a scanline
- ///
- public const int HOR_TOTAL = 0;
- ///
- /// R1: Horizontal displayed character number
- /// Unit: Character
- /// Notes: Defines when DISPEN goes OFF on the scanline
- ///
- public const int HOR_DISPLAYED = 1;
- ///
- /// R2: Position of horizontal sync. pulse
- /// Unit: Character
- /// Notes: Defines when the HSync goes ON on the scanline
- ///
- public const int HOR_SYNC_POS = 2;
- ///
- /// R3: Width of horizontal/vertical sync. pulses
- /// Unit: Character
- /// Notes: VSync width can only be changed on type 3 and 4
- ///
- public const int HOR_AND_VER_SYNC_WIDTHS = 3;
- ///
- /// R4: Vertical total Line character number
- /// Unit: Character
- /// Notes: Defines the height of a screen
- ///
- public const int VER_TOTAL = 4;
- ///
- /// R5: Vertical raster adjust
- /// Unit: Scanline
- /// Notes: Defines additionnal scanlines at the end of a screen
- /// can be used for smooth vertical scrolling on CPC
- ///
- public const int VER_TOTAL_ADJUST = 5;
- ///
- /// R6: Vertical displayed character number
- /// Unit: Character
- /// Notes: Define when DISPEN remains OFF until a new screen starts
- /// Height of displayed screen in characters (Once vertical character count (VCC) matches this value, DISPTMG is set to 1)
- ///
- public const int VER_DISPLAYED = 6;
- ///
- /// R7: Position of vertical sync. pulse
- /// Unit: Character
- /// Notes: Define when the VSync goes ON on a screen
- ///
- public const int VER_SYNC_POS = 7;
- ///
- /// R8: Interlaced mode
- /// Unit:
- /// Notes: 00: No interlace; 01: Interlace Sync Raster Scan Mode; 10: No Interlace; 11: Interlace Sync and Video Raster Scan Mode
- /// (crct type specific)
- ///
- public const int INTERLACE_SKEW = 8;
- ///
- /// R9: Maximum raster
- /// Unit: Scanline
- /// Notes: Defines the height of a CRTC-Char in scanlines
- ///
- public const int MAX_RASTER_ADDR = 9;
- ///
- /// R10: Cursor start raster
- /// Unit:
- /// Notes: Cursor not used on CPC.
- /// (xBP00000)
- /// B = Blink On/Off;
- /// P = Blink Period Control (Slow/Fast).
- /// Sets first raster row of character that cursor is on to invert
- ///
- public const int CUR_START_RASTER = 10;
- ///
- /// R11: Cursor end
- /// Unit:
- /// Notes: Sets last raster row of character that cursor is on to invert
- ///
- public const int CUR_END_RASTER = 11;
- ///
- /// R12: Display Start Address (High)
- /// Unit:
- /// Notes: Define the MSB of MA when a CRTC-screen starts
- ///
- public const int DISP_START_ADDR_H = 12;
- ///
- /// R13: Display Start Address (Low)
- /// Unit:
- /// Notes: Define the LSB of MA when a CRTC-screen starts
- /// Allows you to offset the start of screen memory for hardware scrolling, and if using memory from address &0000 with the firmware.
- ///
- public const int DISP_START_ADDR_L = 13;
- ///
- /// R14: Cursor Address (High)
- /// Unit:
- /// Notes: Useless on the Amstrad CPC/Plus (text-mode is not wired)
- ///
- public const int CUR_ADDR_H = 14;
- ///
- /// R15: Cursor Address (Low)
- /// Unit:
- /// Notes: Useless on the Amstrad CPC/Plus (text-mode is not wired)
- ///
- public const int CUR_ADDR_L = 15;
- ///
- /// R16: Light Pen Address (High)
- /// Unit:
- /// Notes: Hold the MSB of the cursor position when the lightpen was ON
- ///
- public const int LPEN_ADDR_H = 16;
- ///
- /// R17: Light Pen Address (Low)
- /// Unit:
- /// Notes: Hold the LSB of the cursor position when the lightpen was ON
- ///
- public const int LPEN_ADDR_L = 17;
-
- ///
- /// The currently selected register
- ///
- private int SelectedRegister;
-
- ///
- /// CPC register default values
- /// Taken from https://web.archive.org/web/20170501112330/http://www.grimware.org/doku.php/documentations/devices/crtc
- /// http://www.cantrell.org.uk/david/tech/cpc/cpc-firmware/firmware.pdf
- /// (The defaults values given here are those programmed by the firmware ROM after a cold/warm boot of the CPC/Plus)
- ///
- private readonly byte[] RegDefaults = { 63, 40, 46, 112, 38, 0, 25, 30, 0, 7, 0, 0, 48, 0, 192, 7, 0, 0 };
-
- ///
- /// Register masks
- /// 0 = WRITE
- /// 1 = READ
- ///
- private readonly byte[] CPCMask = { 255, 255, 255, 255, 127, 31, 127, 126, 3, 31, 31, 31, 63, 255, 63, 255, 63, 255 };
-
- ///
- /// Horizontal Character Count
- ///
- private int HCC;
-
- ///
- /// Internal cycle counter
- ///
- private int CycleCounter;
-
- ///
- /// Signs that we have finished the last character row
- ///
- private bool EndOfScreen;
-
- ///
- /// HSYNC pulse width (in characters)
- ///
- private int HSYNCWidth;
-
- ///
- /// Internal HSYNC counter
- ///
- private int HSYNCCounter;
-
- ///
- /// VSYNC pulse width (in characters)
- ///
- private int VSYNCWidth;
-
- ///
- /// Internal VSYNC counter
- ///
- private int VSYNCCounter;
-
- public void ClockCycle()
- {
- CheckHSYNCOff();
-
- HCC++;
-
- if (HCC == Regs[HOR_TOTAL] + 1)
- {
- // end of scanline
- HCC = 0;
-
- if (VSYNCCounter > 0)
- {
- VSYNCCounter--;
- if (VSYNCCounter == 0)
- {
- VSYNC = false;
- }
- }
-
- VLC++;
-
- if (VLC == Regs[MAX_RASTER_ADDR] + 1)
- {
- // end of rasterline
- VLC = 0;
- VCC++;
-
- if (VCC == Regs[VER_TOTAL] + 1)
- {
- // end of screen
- VCC = 0;
- }
-
- if (VCC == Regs[VER_SYNC_POS] && !VSYNC)
- {
- VSYNC = true;
- VSYNCCounter = VSYNCWidth;
- VSYNC_Callbacks();
- }
- }
- }
- else
- {
- // still on the current scanline
- if (HCC == Regs[HOR_SYNC_POS] && !HSYNC)
- {
- HSYNC = true;
- HSYNCCounter = HSYNCWidth;
- HSYNC_Callbacks();
- ByteCounter = 0;
- }
-
- if (HCC >= Regs[HOR_DISPLAYED] + 1 || VCC >= Regs[VER_DISPLAYED])
- {
- DISPTMG = false;
- }
- else
- {
- DISPTMG = true;
-
- var line = VCC;
- var row = VLC;
- var addrX = (LatchedRAMOffset * 2) + ((VCC * LatchedScreenWidthBytes) & 0x7ff) + ByteCounter;
- // remove artifacts caused by certain hardware scrolling addresses
- addrX &= 0x7ff;
- var addrY = LatchedRAMStartAddress + (2048 * VLC);
-
- //var addr = VideoPageBase + (line * (0x50)) + (row * 0x800) + (ByteCounter);
- CurrentByteAddress = (ushort)(addrX + addrY);
-
- ByteCounter += 2;
- }
- }
- }
-
- private void CheckHSYNCOff()
- {
- if (HSYNCCounter > 0)
- {
- HSYNCCounter--;
- if (HSYNCCounter == 0)
- {
- HSYNC = false;
- }
- }
- }
-
- ///
- /// Runs a CRCT clock cycle
- /// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states
- ///
- public void ClockCycle2()
- {
- if (HSYNC)
- {
- // HSYNC in progress
- HSYNCCounter++;
-
- ByteCounter = 0;
-
- if (HSYNCCounter >= HSYNCWidth)
- {
- // end of HSYNC
- HSYNCCounter = 0;
- HSYNC = false;
- }
- }
-
- if (HSYNC && HSYNCCounter == 1)
- {
-
- }
-
- // move one horizontal character
- HCC++;
-
- // check for DISPTMG
- if (HCC >= Regs[HOR_DISPLAYED] + 1)
- {
- DISPTMG = false;
- }
- else if (VCC >= Regs[VER_DISPLAYED])
- {
- DISPTMG = false;
- }
- else
- {
- DISPTMG = true;
-
- var line = VCC;
- var row = VLC;
- var addrX = (LatchedRAMOffset * 2) + ((VCC * LatchedScreenWidthBytes) & 0x7ff) + ByteCounter;
- // remove artifacts caused by certain hardware scrolling addresses
- addrX &= 0x7ff;
- var addrY = LatchedRAMStartAddress + (2048 * VLC);
-
- //var addr = VideoPageBase + (line * (0x50)) + (row * 0x800) + (ByteCounter);
- CurrentByteAddress = (ushort)(addrX + addrY);
-
- ByteCounter += 2;
- }
-
- // check for the end of the current scanline
- if (HCC == Regs[HOR_TOTAL] + 1)
- {
- // end of the current scanline
- HCC = 0;
-
-
- if (ChipType == (CRCTType)1 && VLC <= Regs[MAX_RASTER_ADDR])
- {
- // https://web.archive.org/web/20170501112330/http://www.grimware.org/doku.php/documentations/devices/crtc
- // The MA is reloaded with the value from R12 and R13 when VCC=0 and VLC=0 (that's when a new CRTC screen begin).
- // However, CRTC Type 1 keep updating the MA on every new scanline while VCC=0 (and VLC== Regs[VER_TOTAL] + 1)
- {
- VCC = 0;
- EndOfScreen = true;
- }
- }
-
- // does VSYNC need to be raised?
- if (!VSYNC)
- {
- if (VCC == Regs[VER_SYNC_POS])
- {
- VSYNC = true;
- VSYNCCounter = 0;
- VSYNC_Callbacks();
- }
- }
- }
- else
- {
- // still processing a scanline
- // check whether HSYNC needs raising
- if (!HSYNC)
- {
- if (HCC == Regs[HOR_SYNC_POS])
- {
- HSYNC = true;
- HSYNCCounter = 0;
- HSYNC_Callbacks();
- lineCounter++;
-
- LatchedRAMStartAddress = VideoPageBase;
- LatchedRAMOffset = VideoRAMOffset;
- LatchedScreenWidthBytes = DisplayWidth * 2;
-
- }
- }
- }
- }
-
- ///
- /// Runs a CRCT clock cycle
- /// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states
- ///
- public void ClockCycle1()
- {
- // HSYNC processing
- if (HSYNCCounter > 0)
- {
- HSYNCCounter--;
- if (HSYNCCounter == 0)
- HSYNC = false;
- }
-
- HCC++;
-
- if (HCC == FrameWidth)
- {
- // we have finished the current scanline
- HCC = 0;
-
- if (VSYNCCounter > 0)
- {
- VSYNCCounter--;
- if (VSYNCCounter == 0)
- VSYNC = false;
- }
-
- VLC++;
-
- if (VLC == ScanlinesPerCharacter)
- {
- // completed a vertical character
- VLC = 0;
- VCC++;
-
- if (VCC == FrameHeight)
- {
- // screen has completed
- VCC = 0;
- }
- }
-
- // check whether VSYNC should be raised
- if (VCC == VerticalSyncPos && !VSYNC)
- {
- VSYNC = true;
- VSYNCCounter = VSYNCWidth;
- VSYNC_Callbacks();
- }
- }
- else if (HCC == HorizontalSyncPos && !HSYNC)
- {
- // start of HSYNC period
- HSYNC = true;
- HSYNCCounter = HSYNCWidth;
- HSYNC_Callbacks();
- }
-
- // DISPTMG
- if (HCC >= Regs[HOR_DISPLAYED] || VCC >= Regs[VER_DISPLAYED])
- {
- DISPTMG = false;
- }
- else
- {
- DISPTMG = true;
- }
- /*
- // check for DISPTMG
- if (HCC >= Regs[HOR_DISPLAYED] + 1)
- {
- DISPTMG = false;
- }
- else if (VCC >= Regs[VER_DISPLAYED])
- {
- DISPTMG = false;
- }
- else
- {
- DISPTMG = true;
- }
- */
- }
-
- public int lineCounter = 0;
-
-
-
- ///
- /// Resets the chip
- ///
- public void Reset()
- {
- // set regs to default
- for (int i = 0; i < 18; i++)
- Regs[i] = RegDefaults[i];
-
- SelectedRegister = 0;
-
- // populate initial MA address
- MA = (short)(((Regs[DISP_START_ADDR_H]) & 0xff) << 8 | (Regs[DISP_START_ADDR_L]) & 0xff);
-
- // updates widths
- UpdateWidths();
-
- HSYNC = false;
- VSYNC = false;
-
- HSYNCCounter = 0;
- VSYNCCounter = 0;
-
- HCC = 0;
- VCC = 0;
- VLC = 0;
- }
-
- ///
- /// Selects a register
- ///
- private void RegisterSelect(int data)
- {
- SelectedRegister = data & 0x1F;
- }
-
- /*
- RegIdx Register Name Type
- 0 1 2 3 4
- 0 Horizontal Total Write Only Write Only Write Only (note 2) (note 3)
- 1 Horizontal Displayed Write Only Write Only Write Only (note 2) (note 3)
- 2 Horizontal Sync Position Write Only Write Only Write Only (note 2) (note 3)
- 3 H and V Sync Widths Write Only Write Only Write Only (note 2) (note 3)
- 4 Vertical Total Write Only Write Only Write Only (note 2) (note 3)
- 5 Vertical Total Adjust Write Only Write Only Write Only (note 2) (note 3)
- 6 Vertical Displayed Write Only Write Only Write Only (note 2) (note 3)
- 7 Vertical Sync position Write Only Write Only Write Only (note 2) (note 3)
- 8 Interlace and Skew Write Only Write Only Write Only (note 2) (note 3)
- 9 Maximum Raster Address Write Only Write Only Write Only (note 2) (note 3)
- 10 Cursor Start Raster Write Only Write Only Write Only (note 2) (note 3)
- 11 Cursor End Raster Write Only Write Only Write Only (note 2) (note 3)
- 12 Disp. Start Address (High) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
- 13 Disp. Start Address (Low) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
- 14 Cursor Address (High) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
- 15 Cursor Address (Low) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
- 16 Light Pen Address (High) Read Only Read Only Read Only Read Only (note 2) (note 3)
- 17 Light Pen Address (Low) Read Only Read Only Read Only Read Only (note 2) (note 3)
-
- 1. On type 0 and 1, if a Write Only register is read from, "0" is returned.
- 2. See the document "Extra CPC Plus Hardware Information" for more details.
- 3. CRTC type 4 is the same as CRTC type 3. The registers also repeat as they do on the type 3.
- */
-
- ///
- /// Writes to the currently selected register
- ///
- private void WriteRegister(int data)
- {
- // 16 and 17 are read only registers on all types
- if (SelectedRegister == 16 || SelectedRegister == 17)
- return;
-
- // non existing registers
- if (SelectedRegister > 17)
- return;
-
- if (SelectedRegister == DISP_START_ADDR_L)
- {
-
- }
-
- if (SelectedRegister == DISP_START_ADDR_H)
- {
-
- }
-
- if (SelectedRegister == HOR_TOTAL)
- {
- // always 63
- if (data != 63)
- return;
- }
-
- if (SelectedRegister == 1)
- {
- var d = data;
- }
-
- Regs[SelectedRegister] = (byte)(data & CPCMask[SelectedRegister]);
-
- if (SelectedRegister == HOR_AND_VER_SYNC_WIDTHS)
- {
- UpdateWidths();
- }
- }
-
- ///
- /// Reads from the currently selected register
- ///
- private bool ReadRegister(ref int data)
- {
- bool addressed = false;
- switch (SelectedRegister)
- {
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- case 10:
- case 11:
- if (ChipType is CRCTType.HD6845S or CRCTType.UM6845 or CRCTType.UM6845R)
- {
- addressed = true;
- data = 0;
- }
- break;
- case 12:
- case 13:
- addressed = true;
- if (ChipType is CRCTType.HD6845S or CRCTType.UM6845)
- data = Regs[SelectedRegister];
- else if (ChipType is CRCTType.UM6845R)
- data = 0;
- break;
- case 14:
- case 15:
- case 16:
- case 17:
- addressed = true;
- data = Regs[SelectedRegister];
- break;
-
- default:
- // registers 18-31 read as 0, on type 0 and 2. registers 18-30 read as 0 on type1, register 31 reads as 0x0ff.
- if (SelectedRegister >= 18 && SelectedRegister <= 30)
- {
- switch ((int)ChipType)
- {
- case 0:
- case 2:
- case 1:
- addressed = true;
- data = 0;
- break;
- }
- }
- else if (SelectedRegister == 31)
- {
- if (ChipType == CRCTType.UM6845R)
- {
- addressed = true;
- data = 0x0ff;
- }
- else if (ChipType is CRCTType.HD6845S or CRCTType.UM6845 or CRCTType.MC6845)
- {
- addressed = true;
- data = 0;
- }
- }
- break;
- }
-
- return addressed;
- }
-
- ///
- /// Reads from the status register
- ///
- private bool ReadStatus(ref int data)
- {
- bool addressed = false;
- switch ((int)ChipType)
- {
- case 1:
- // read status
- //todo!!
- addressed = true;
- break;
- case 0:
- case 2:
- // status reg not available
- break;
- case 3:
- case 4:
- // read from internal register instead
- addressed = ReadRegister(ref data);
- break;
- }
- return addressed;
- }
-
- ///
- /// Updates the V and H SYNC widths
- ///
- private void UpdateWidths()
- {
- switch (ChipType)
- {
- case CRCTType.HD6845S:
- // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC. Bits 3..0 define Horizontal Sync Width.
- // If 0 is programmed no HSYNC is generated.
- HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
- VSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 4) & 0x0F;
- break;
- case CRCTType.UM6845R:
- // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed no HSYNC is generated.
- HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
- VSYNCWidth = 16;
- break;
- case CRCTType.MC6845:
- // Bits 7..4 are ignored. Vertical Sync is fixed at 16 lines. Bits 3..0 define Horizontal Sync Width. If 0 is programmed this gives a HSYNC width of 16.
- HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
- if (HSYNCWidth == 0)
- HSYNCWidth = 16;
- VSYNCWidth = 16;
- break;
- case CRCTType.AMS40489:
- case CRCTType.AMS40226:
- // Bits 7..4 define Vertical Sync Width. If 0 is programmed this gives 16 lines of VSYNC.Bits 3..0 define Horizontal Sync Width.
- // If 0 is programmed this gives a HSYNC width of 16.
- HSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 0) & 0x0F;
- VSYNCWidth = (Regs[HOR_AND_VER_SYNC_WIDTHS] >> 4) & 0x0F;
- if (HSYNCWidth == 0)
- HSYNCWidth = 16;
- if (VSYNCWidth == 0)
- VSYNCWidth = 16;
- break;
- }
- }
-
- /*
- #BCXX %x0xxxx00 xxxxxxxx 6845 CRTC Index - Write
- #BDXX %x0xxxx01 xxxxxxxx 6845 CRTC Data Out - Write
- #BEXX %x0xxxx10 xxxxxxxx 6845 CRTC Status (as far as supported) Read -
- #BFXX %x0xxxx11 xxxxxxxx 6845 CRTC Data In (as far as supported) Read -
- */
-
- ///
- /// Device responds to an IN instruction
- ///
- public bool ReadPort(ushort port, ref int result)
- {
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- bool accessed = false;
-
- // The 6845 is selected when bit 14 of the I/O port address is set to "0"
- if (portUpper.Bit(6))
- return accessed;
-
- // Bit 9 and 8 of the I/O port address define the function to access
- if (portUpper.Bit(1) && !portUpper.Bit(0))
- {
- // read status register
- accessed = ReadStatus(ref result);
- }
- else if ((portUpper & 3) == 3)
- {
- // read data register
- accessed = ReadRegister(ref result);
- }
- else
- {
- result = 0;
- }
-
- return accessed;
- }
-
- ///
- /// Device responds to an OUT instruction
- ///
- public bool WritePort(ushort port, int result)
- {
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- bool accessed = false;
-
- // The 6845 is selected when bit 14 of the I/O port address is set to "0"
- if (portUpper.Bit(6))
- return accessed;
-
- var func = portUpper & 3;
-
- switch (func)
- {
- // reg select
- case 0:
- RegisterSelect(result);
- break;
-
- // data write
- case 1:
- WriteRegister(result);
- break;
- }
-
- return accessed;
- }
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("CRTC");
- ser.SyncEnum(nameof(ChipType), ref ChipType);
- ser.Sync(nameof(HSYNC), ref HSYNC);
- ser.Sync(nameof(VSYNC), ref VSYNC);
- ser.Sync(nameof(DISPTMG), ref DISPTMG);
- ser.Sync(nameof(MA), ref MA);
- ser.Sync(nameof(CurrentByteAddress), ref CurrentByteAddress);
- ser.Sync(nameof(ByteCounter), ref ByteCounter);
- ser.Sync(nameof(Regs), ref Regs, false);
- ser.Sync(nameof(SelectedRegister), ref SelectedRegister);
- ser.Sync(nameof(HCC), ref HCC);
- ser.Sync(nameof(VCC), ref VCC);
- ser.Sync(nameof(VLC), ref VLC);
- ser.Sync(nameof(CycleCounter), ref CycleCounter);
- ser.Sync(nameof(EndOfScreen), ref EndOfScreen);
- ser.Sync(nameof(HSYNCWidth), ref HSYNCWidth);
- ser.Sync(nameof(HSYNCCounter), ref HSYNCCounter);
- ser.Sync(nameof(VSYNCWidth), ref VSYNCWidth);
- ser.Sync(nameof(VSYNCCounter), ref VSYNCCounter);
- ser.EndSection();
- }
-
- ///
- /// The types of CRCT chip found in the CPC range
- ///
- public enum CRCTType
- {
- HD6845S = 0,
- UM6845 = 0,
- UM6845R = 1,
- MC6845 = 2,
- AMS40489 = 3,
- AMS40226 = 4
- }
- }
-}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTC6845.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTC6845.cs
deleted file mode 100644
index 1ded8d7298..0000000000
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTC6845.cs
+++ /dev/null
@@ -1,2278 +0,0 @@
-using System.Collections;
-using BizHawk.Common;
-using BizHawk.Common.NumberExtensions;
-
-namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
-{
- ///
- /// CATHODE RAY TUBE CONTROLLER (CRTC) IMPLEMENTATION
- /// http://www.cpcwiki.eu/index.php/CRTC
- /// http://cpctech.cpc-live.com/docs/cpcplus.html
- /// This implementation aims to emulate all the various CRTC chips that appear within
- /// the CPC, CPC+ and GX4000 ranges. The CPC community have assigned them type numbers.
- /// If different implementations share the same type number it indicates that they are functionally identical:
- ///
- /// Part No. Manufacturer Type No. Info.
- /// ------------------------------------------------------------------------------------------------------
- /// HD6845S Hitachi 0
- /// Datasheet: http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf
- /// ------------------------------------------------------------------------------------------------------
- /// UM6845 UMC 0
- /// Datasheet: http://www.cpcwiki.eu/imgs/1/13/Um6845.umc.pdf
- /// ------------------------------------------------------------------------------------------------------
- /// UM6845R UMC 1
- /// Datasheet: http://www.cpcwiki.eu/imgs/b/b5/Um6845r.umc.pdf
- /// ------------------------------------------------------------------------------------------------------
- /// MC6845 Motorola 2
- /// Datasheet: http://www.cpcwiki.eu/imgs/d/da/Mc6845.motorola.pdf & http://bitsavers.trailing-edge.com/components/motorola/_dataSheets/6845.pdf
- /// ------------------------------------------------------------------------------------------------------
- /// AMS40489 Amstrad 3 Only exists in the CPC464+, CPC6128+ and GX4000 and is integrated into a single CPC+ ASIC chip (along with the gatearray)
- /// Datasheet: {none}
- /// ------------------------------------------------------------------------------------------------------
- /// AMS40041 Amstrad 4 'Pre-ASIC' IC. The CRTC is integrated into a aingle ASIC IC with functionality being almost identical to the AMS40489
- /// (or 40226) Used in the 'Cost-Down' range of CPC464 and CPC6128 systems
- /// Datasheet: {none}
- ///
- ///
- public class CRTC6845
- {
- ///
- /// The type of CRTC we are emulating
- /// (passed in through contructor)
- ///
- private CRTCType ChipType;
-
- ///
- /// The only constructor
- ///
- public CRTC6845(CRTCType type)
- {
- ChipType = type;
- }
-
- ///
- /// The ClK isaTTUMOS-compatible input used to synchronize all CRT' functions except for the processor interface.
- /// An external dot counter is used to derive this signal which is usually the character rate in an alphanumeric CRT.
- /// The active transition is high-to-low
- ///
- public bool CLK => _CLK;
-
- private bool _CLK;
- ///
- /// The RES input is used to Reset the CRTC. An input low level on RES forces CRTC into following status:
- /// (A) All the counters in CRTC are cleared and the device stops the display operation.
- /// (C) Control registers in CRTC are not affected and remain unchanged.
- /// This signal is different from other M6800 family in the following functions:
- /// (A) RES signal has capability of reset function only. when LPSTB is at low level.
- /// (B) After RES has gone down to low level, output s ignals of MAO -MA13 and RAO - RA4, synchronizing with CLK low level, goes down to low level.
- /// (At least 1 cycle CLK signal is necessary for reset.)
- /// (C) The CRTC starts the Display operation immediately after the release of RES signal.
- ///
- public bool RESET => _RESET;
-
- private bool _RESET;
- ///
- /// Light Pen Strobe (LPSTR) - This high impedance TTLIMOS compatible input latches the cu rrent Refresh Addresses in the Register File.
- /// Latching is on the low to high edge and is synchronized internally to character clock.
- ///
- public bool LPSTB => _LPSTB;
-
- private bool _LPSTB;
-
- // State output lines
- ///
- /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation.
- /// This signal determines the vertical position of the displayed text.
- ///
- public bool VSYNC => _VSYNC;
-
- private bool _VSYNC;
- ///
- /// This TTL compatible output is an active high signal which drives the monitor directly or is fed to Video Processing Logic for composite generation.
- /// This signal determines the horizontal position of the displayed text.
- ///
- public bool HSYNC => _HSYNC;
-
- private bool _HSYNC;
- ///
- /// This TTL compatible output is an active high signal which indicates the CRTC is providing addressing in the active Display Area.
- ///
- public bool DISPTMG => _DISPTMG;
-
- private bool _DISPTMG;
- ///
- /// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal.
- ///
- public bool CUDISP => _CUDISP;
-
- private bool _CUDISP;
-
- // Refresh memory addresses
- /*
- Refresh Memory Addresses (MAO-MA13) - These 14 outputs are used to refresh the CRT screen with pages of
- data located within a 16K block of refresh memory. These outputs drive a TTL load and 30pF. A high level on
- MAO-MA 13 is a logical "1."
- */
- public bool MA0 => LinearAddress.Bit(0);
- public bool MA1 => LinearAddress.Bit(1);
- public bool MA2 => LinearAddress.Bit(2);
- public bool MA3 => LinearAddress.Bit(3);
- public bool MA4 => LinearAddress.Bit(4);
- public bool MA5 => LinearAddress.Bit(5);
- public bool MA6 => LinearAddress.Bit(6);
- public bool MA7 => LinearAddress.Bit(7);
- public bool MA8 => LinearAddress.Bit(8);
- public bool MA9 => LinearAddress.Bit(9);
- public bool MA10 => LinearAddress.Bit(10); // cpcwiki would suggest that this isnt connected in the CPC range
- public bool MA11 => LinearAddress.Bit(11); // cpcwiki would suggest that this isnt connected in the CPC range
- public bool MA12 => LinearAddress.Bit(12); // cpcwiki would suggest that this is connected in the CPC range but not used
- public bool MA13 => LinearAddress.Bit(13); // cpcwiki would suggest that this is connected in the CPC range but not used
-
- // Row addresses for character generators
- /*
- Raster Addresses (RAO-RA4) - These 5 outputs from the internal Raster Counter address the Character ROM
- for the row of a character. These outputs drive a TTL load and 30pF. A high level (on RAO-RA4) is a logical "1."
- */
- public bool RA0 => RowSelects.Bit(0);
- public bool RA1 => RowSelects.Bit(1);
- public bool RA2 => RowSelects.Bit(2);
- public bool RA3 => RowSelects.Bit(3); // cpcwiki would suggest that this isnt connected in the CPC range
- public bool RA4 => RowSelects.Bit(4); // cpcwiki would suggest that this isnt connected in the CPC range
-
- ///
- /// This 16-bit property emulates how the Amstrad CPC Gate Array is connected up to the CRTC
- /// Built from R12, R13 and CLK
- ///
- /*
- Memory Address Signal Signal source Signal name
- A15 6845 MA13
- A14 6845 MA12
- A13 6845 RA2
- A12 6845 RA1
- A11 6845 RA0
- A10 6845 MA9
- A9 6845 MA8
- A8 6845 MA7
- A7 6845 MA6
- A6 6845 MA5
- A5 6845 MA4
- A4 6845 MA3
- A3 6845 MA2
- A2 6845 MA1
- A1 6845 MA0
- A0 Gate-Array CLK
- */
- public ushort AddressLineCPC
- {
- get
- {
- BitArray MA = new BitArray(16);
- MA[0] = _CLK;
- MA[1] = MA0;
- MA[2] = MA1;
- MA[3] = MA2;
- MA[4] = MA3;
- MA[5] = MA4;
- MA[6] = MA5;
- MA[7] = MA6;
- MA[8] = MA7;
- MA[9] = MA8;
- MA[10] = MA9;
- MA[11] = RA0;
- MA[12] = RA1;
- MA[13] = RA2;
- MA[14] = MA12;
- MA[15] = MA13;
- ushort[] array = new ushort[1];
- MA.CopyTo(array, 0);
- return array[0];
- }
- }
-
- ///
- /// Character pos address (0 index).
- /// Feeds the MA lines
- ///
- private int LinearAddress;
-
- ///
- /// Generated by the Vertical Control
- /// Feeds the RA lines
- ///
- private int RowSelects;
-
- ///
- /// Horizontal Counter
- ///
- private int _CharacterCTR;
- private int CharacterCTR
- {
- get => _CharacterCTR;
- set
- {
- if (value > 255)
- _CharacterCTR = value - 255;
- }
- }
-
- ///
- /// HSYNC Counter
- ///
- private int _HorizontalSyncWidthCTR;
- private int HorizontalSyncWidthCTR
- {
- get => _HorizontalSyncWidthCTR;
- set
- {
- if (value > 15)
- _HorizontalSyncWidthCTR = value - 15;
- }
- }
-
- ///
- /// VSYNC Counter
- ///
- private int _VerticalSyncWidthCTR;
- private int VerticalSyncWidthCTR
- {
- get => _VerticalSyncWidthCTR;
- set
- {
- if (value > 15)
- _VerticalSyncWidthCTR = value - 15;
- }
- }
-
- ///
- /// Vertical Character Counter
- ///
- private int _LineCTR;
- private int LineCTR
- {
- get => _LineCTR;
- set
- {
- if (value > 127)
- _LineCTR = value - 127;
- }
- }
-
- ///
- /// Vertical Raster Counter
- ///
- private int _RasterCTR;
- private int RasterCTR
- {
- get => _RasterCTR;
- set
- {
- if (value > 31)
- _RasterCTR = value - 31;
- }
- }
-
- ///
- /// The CRTC latches the Display Start H & L address at different times
- /// (depending on the chip type)
- ///
- private int StartAddressLatch;
-
- ///
- /// The currently selected register
- ///
- private byte AddressRegister;
-
- ///
- /// The internal register
- /// The Address Register is a 5 bit write-only register used as an "indirect" or "pointer" register.
- /// Its contents are the address of one of the other 18 registers in the file.When RS and CS are low,
- /// the Address Register itself is addressed.When RS is high, the Register File is accessed.
- ///
- private byte[] Register = new byte[18];
-
- ///
- /// Internal Status Register specific to the Type 1 UM6845R
- ///
- private byte StatusRegister;
-
- ///
- /// Not really a true status register, but values are returned on types 3 and 4
- /// depending on the current CRTC status
- ///
- private byte AsicStatusRegister1;
- ///
- /// Not really a true status register, but values are returned on types 3 and 4
- /// depending on the current CRTC status
- ///
- private byte AsicStatusRegister2;
-
- /*
- RegIdx Register Name Type
- 0 1 2 3 4
- 0 Horizontal Total Write Only Write Only Write Only (note 2) (note 3)
- 1 Horizontal Displayed Write Only Write Only Write Only (note 2) (note 3)
- 2 Horizontal Sync Position Write Only Write Only Write Only (note 2) (note 3)
- 3 H and V Sync Widths Write Only Write Only Write Only (note 2) (note 3)
- 4 Vertical Total Write Only Write Only Write Only (note 2) (note 3)
- 5 Vertical Total Adjust Write Only Write Only Write Only (note 2) (note 3)
- 6 Vertical Displayed Write Only Write Only Write Only (note 2) (note 3)
- 7 Vertical Sync position Write Only Write Only Write Only (note 2) (note 3)
- 8 Interlace and Skew Write Only Write Only Write Only (note 2) (note 3)
- 9 Maximum Raster Address Write Only Write Only Write Only (note 2) (note 3)
- 10 Cursor Start Raster Write Only Write Only Write Only (note 2) (note 3)
- 11 Cursor End Raster Write Only Write Only Write Only (note 2) (note 3)
- 12 Disp. Start Address (High) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
- 13 Disp. Start Address (Low) Read/Write Write Only Write Only Read/Write (note 2) (note 3)
- 14 Cursor Address (High) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
- 15 Cursor Address (Low) Read/Write Read/Write Read/Write Read/Write (note 2) (note 3)
- 16 Light Pen Address (High) Read Only Read Only Read Only Read Only (note 2) (note 3)
- 17 Light Pen Address (Low) Read Only Read Only Read Only Read Only (note 2) (note 3)
-
- 1. On type 0 and 1, if a Write Only register is read from, "0" is returned.
- 2. See the document "Extra CPC Plus Hardware Information" for more details.
- 3. CRTC type 4 is the same as CRTC type 3. The registers also repeat as they do on the type 3.
- */
-
- /* CPC:
- #BCXX %x0xxxx00 xxxxxxxx 6845 CRTC Index - Write
- #BDXX %x0xxxx01 xxxxxxxx 6845 CRTC Data Out - Write
- #BEXX %x0xxxx10 xxxxxxxx 6845 CRTC Status (as far as supported) Read -
- #BFXX %x0xxxx11 xxxxxxxx 6845 CRTC Data In (as far as supported) Read -
-
- The Read/Write functions below are geared toward Amstrad CPC only
- They could be overridden for a different implementation if needs be
- */
-
- ///
- /// CPU (or other device) reads from the 8-bit databus
- ///
- public virtual bool ReadPort(ushort port, ref int result)
- {
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- bool accessed = false;
-
- // The 6845 is selected when bit 14 of the I/O port address is set to "0"
- if (portUpper.Bit(6))
- return accessed;
-
- // Bit 9 and 8 of the I/O port address define the function to access
- if (portUpper.Bit(1) && !portUpper.Bit(0))
- {
- // read status register
- accessed = ReadStatus(ref result);
- }
- else if ((portUpper & 3) == 3)
- {
- // read data register
- accessed = ReadRegister(ref result);
- }
- else
- {
- result = 0;
- }
-
- return accessed;
- }
-
- ///
- /// CPU (or other device) writes to the 8-bit databus
- ///
- public virtual bool WritePort(ushort port, int value)
- {
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- bool accessed = false;
-
- // The 6845 is selected when bit 14 of the I/O port address is set to "0"
- if (portUpper.Bit(6))
- return accessed;
-
- var func = portUpper & 3;
-
- switch (func)
- {
- // reg select
- case 0:
- SelectRegister(value);
- break;
-
- // data write
- case 1:
- WriteRegister(value);
- break;
- }
-
- return accessed;
- }
-
- ///
- /// Runs a clock cycle for the current chip type
- /// CPC will call this every 1Mhz
- /// Equates to 1 generated character (2 bytes of data)
- /// Based on the various CRCT FUNCTIONAL BLOCK DIAGRAMs in the datasheets
- ///
- public void CycleClock()
- {
- switch ((int)ChipType)
- {
- case 0:
- ClockCycle_Type0();
- break;
- case 2:
- ClockCycle_Type2();
- break;
- default:
- ClockCycle_Generic();
- break;
- }
- }
-
- ///
- /// Type dependent
- /// Either a static value or calculated from R3
- ///
- private int HSYNCWidth
- {
- get
- {
- switch ((int)ChipType)
- {
- case 0:
- case 1:
- return HSYNCWidth_Type0_1;
- default:
- return HSYNCWidth_Type2_3_4;
- }
- }
- }
-
- ///
- /// Type dependent
- /// Either a static value or calculated from R3
- ///
- private int VSYNCWidth
- {
- get
- {
- switch ((int)ChipType)
- {
- case 1:
- case 2:
- return VSYNCWidth_Type1_2;
- default:
- return VSYNCWidth_Type0_3_4;
- }
- }
- }
-
- ///
- /// Selects a specific register
- ///
- private void SelectRegister(int value)
- {
- var v = (byte)((byte)value & 0x1F);
- if (v > 0 && v < 18)
- {
- AddressRegister = v;
- }
- }
-
- ///
- /// Attempts to read from the currently selected register
- ///
- private bool ReadRegister(ref int data)
- {
- switch ((int)ChipType)
- {
- case 0: return ReadRegister_Type0(ref data);
- case 1: return ReadRegister_Type1(ref data);
- case 2: return ReadRegister_Type2(ref data);
- case 3:
- case 4: return ReadRegister_Type3_4(ref data);
- default: return false;
- }
- }
-
- ///
- /// Attempts to write to the currently selected register
- ///
- private void WriteRegister(int data)
- {
- switch ((int)ChipType)
- {
- case 0: WriteRegister_Type0(data); break;
- case 1: WriteRegister_Type1(data); break;
- case 2: WriteRegister_Type2(data); break;
- case 3:
- case 4: WriteRegister_Type3_4(data); break;
- }
- }
-
- ///
- /// Attempts to read from the internal status register (if present)
- ///
- private bool ReadStatus(ref int data)
- {
- switch ((int)ChipType)
- {
- case 1: return ReadStatus_Type1(ref data);
- case 3:
- case 4: return ReadStatus_Type3_4(ref data);
- default: return false;
- }
- }
-
- ///
- /// The status of the DisplayEnableSkew bit(s) in R8
- ///
- private int DISPTMGSkew
- {
- get
- {
- var val = Register[INTERLACE_MODE];
- int res = 0;
- switch ((int)ChipType)
- {
- // HD6845 & UM6845
- case 0:
- // Bits 5 and 4 determine the skew
- res = (val & 0x30) >> 4;
- if (res > 2)
- return -1;
- break;
-
- // UMR6845R
- case 1:
- return 0;
-
- // MC6845
- case 2:
- return 0;
-
- // AMS chips
- case 3:
- case 4:
- break;
- }
-
- return res;
- }
- }
-
- ///
- /// The status of the CursorSkew bit(s) in R8
- ///
- private int CUDISPSkew
- {
- get
- {
- var val = Register[INTERLACE_MODE];
- int res = 0;
- switch ((int)ChipType)
- {
- // HD6845 & UM6845
- case 0:
- // Bits 5 and 4 determine the skew
- res = (val & 0xC0) >> 6;
- if (res > 2)
- return -1;
- break;
-
- // UMR6845R
- case 1:
- return 0;
-
- // MC6845
- case 2:
- return 0;
-
- // AMS chips
- case 3:
- case 4:
- break;
- }
-
- return res;
- }
- }
-
- ///
- /// The currently selected Interlace Mode (based on R8)
- /// Looks to be the same for all chip types
- ///
- private InterlaceMode CurrentInterlaceMode
- {
- get
- {
- if (!Register[INTERLACE_MODE].Bit(0))
- {
- return InterlaceMode.NormalSyncMode;
- }
- else if (Register[INTERLACE_MODE].Bit(0))
- {
- if (Register[INTERLACE_MODE].Bit(1))
- {
- return InterlaceMode.InterlaceSyncAndVideoMode;
- }
- else
- {
- return InterlaceMode.InterlaceSyncMode;
- }
- }
-
- return InterlaceMode.NormalSyncMode;
- }
- }
-
- ///
- /// Gets the combined value of R12 & R13
- ///
- private int StartAddressRegisterValue
- {
- get
- {
- var Reg13 = Register[START_ADDR_L];
- var Reg12 = (byte)(Register[START_ADDR_H] & 0x3f);
- return (Reg12 << 8) + Reg13;
- }
- }
-
- ///
- /// Gets the combined value of R14 & R15
- ///
- private int CursorAddressRegisterValue
- {
- get
- {
- var reg15 = Register[CURSOR_L];
- var reg14 = (byte)(Register[CURSOR_H] & 0x3f);
- return (reg14 << 8) + reg15;
- }
- }
-
- ///
- /// Current programmed HSYNC width for Type 0 (HD6845S & UM6845) & Type 1 (UM6845R)
- ///
- // Bits 3..0 define Horizontal Sync Width.
- // If 0 is programmed no HSYNC is generated.
- private int HSYNCWidth_Type0_1 => (Register[SYNC_WIDTHS] >> 0) & 0x0F;
-
- ///
- /// Current programmed HSYNC width for Type 2 (MC6845), 3 (AMS40489) & 4 (pre-ASIC)
- ///
- private int HSYNCWidth_Type2_3_4
- {
- get
- {
- // Bits 3..0 define Horizontal Sync Width.
- // If 0 is programmed this gives a HSYNC width of 16
- var width = (Register[SYNC_WIDTHS] >> 0) & 0x0F;
- if (width == 0)
- width = 16;
- return width;
- }
- }
-
- ///
- /// Current programmed VSYNC width for Type 0 (HD6845S & UM6845), 3 (AMS40489) & 4 (pre-ASIC)
- ///
- private int VSYNCWidth_Type0_3_4
- {
- get
- {
- // Bits 7..4 define Vertical Sync Width
- // If 0 is programmed this gives 16 lines of VSYNC
- var width = (Register[SYNC_WIDTHS] >> 4) & 0x0F;
- if (width == 0)
- width = 16;
- return width;
- }
- }
-
- ///
- /// Current programmed VSYNC width for Type 1 (UM6845R) & 2 (MC6845)
- ///
- // Bits 7..4 are ignored.
- // Vertical Sync is fixed at 16 lines.
- private int VSYNCWidth_Type1_2 => 16;
-
- ///
- /// Read Register (HD6845S & UM6845)
- ///
- private bool ReadRegister_Type0(ref int data)
- {
- // Type 0 - write only register returns 0 when it is read from
- switch (AddressRegister)
- {
- // read-only registers
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- case 10:
- case 11:
- data = 0;
- break;
- case 12: // Start Address H (6-bit)
- case 14: // Cursor H (6-bit)
- case 16: // Light Pen H (6-bit)
- data = Register[AddressRegister] & 0x3F;
- break;
- case 13: // Start Address L (8-bit)
- case 15: // Cursor L (8-bit)
- case 17: // Light Pen L (8-bit)
- data = Register[AddressRegister];
- break;
- default:
- if (AddressRegister > 17 && AddressRegister < 32)
- {
- data = 0;
- }
- else
- {
- return false;
- }
- break;
- }
- return true;
- }
-
- ///
- /// Read Register (UM6845R)
- ///
- private bool ReadRegister_Type1(ref int data)
- {
- // Type 1 - write only register returns 0 when it is read from
- switch (AddressRegister)
- {
- // read-only registers
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- case 10:
- case 11:
- case 12:
- case 13:
- data = 0;
- break;
- case 14: // Cursor H (6-bit)
- data = Register[AddressRegister] & 0x3F;
- break;
- case 16: // Light Pen H (6-bit)
- data = Register[AddressRegister] & 0x3F;
- // reading from R16 resets bit6 of the status register
- StatusRegister &= byte.MaxValue ^ (1 << 6);
- break;
- case 15: // Cursor L (8-bit)
- data = Register[AddressRegister];
- break;
- case 17: // Light Pen L (8-bit)
- data = Register[AddressRegister];
- // reading from R17 resets bit6 of the status register
- StatusRegister &= byte.MaxValue ^ (1 << 6);
- break;
- case 31: // Dummy Register. Datasheet describes this as N/A but CPCWIKI suggests that reading from it return 0x0FF;
- data = 0xff;
- break;
- default:
- if (AddressRegister > 17 && AddressRegister < 31)
- {
- data = 0;
- }
- else
- {
- return false;
- }
- break;
- }
- return true;
- }
-
- ///
- /// Read Register (MC6845)
- ///
- private bool ReadRegister_Type2(ref int data)
- {
- switch (AddressRegister)
- {
- // read-only registers - type 2 does not respond
- case 0:
- case 1:
- case 2:
- case 3:
- case 4:
- case 5:
- case 6:
- case 7:
- case 8:
- case 9:
- case 10:
- case 11:
- case 12:
- case 13:
- return false;
- case 14: // Cursor H (6-bit)
- data = Register[AddressRegister] & 0x3F;
- break;
- case 16: // Light Pen H (6-bit)
- data = Register[AddressRegister] & 0x3F;
- break;
- case 15: // Cursor L (8-bit)
- data = Register[AddressRegister];
- break;
- case 17: // Light Pen L (8-bit)
- data = Register[AddressRegister];
- break;
- default:
- if (AddressRegister > 17 && AddressRegister < 32)
- {
- data = 0;
- }
- else
- {
- return false;
- }
- break;
- }
- return true;
- }
-
- ///
- /// Read Register (AMS40489 & pre-ASIC)
- ///
- private bool ReadRegister_Type3_4(ref int data)
- {
- // unsure of the register sizes at the moment
- // for now we will just read and write 8-bit values
- switch (AddressRegister)
- {
- case 6:
- case 7:
- case 14:
- case 15:
- case 22:
- case 23:
- case 30:
- case 31:
- // returns 0
- data = 0;
- break;
- case 0:
- case 8:
- case 16:
- case 24:
- // returns R16
- data = Register[16];
- break;
- case 1:
- case 9:
- case 17:
- case 25:
- // returns R17
- data = Register[17];
- break;
- case 4:
- case 12:
- case 20:
- case 28:
- // returns R12
- data = Register[12];
- break;
- case 5:
- case 13:
- case 21:
- case 29:
- // returns R13
- data = Register[13];
- break;
- case 2:
- case 10:
- case 18:
- case 26:
- // ASIC status 1
- data = AsicStatusRegister1;
- break;
- case 3:
- case 11:
- case 19:
- case 27:
- // ASIC status 2
- data = AsicStatusRegister2;
- break;
- }
- return true;
- }
-
- ///
- /// Write Active Register (HD6845S & UM6845)
- ///
- private void WriteRegister_Type0(int data)
- {
- byte v = (byte)data;
- switch (AddressRegister)
- {
- case 0: // 8-bit registers
- case 1:
- case 2:
- case 3:
- case 13:
- case 15:
- Register[AddressRegister] = v;
- break;
- case 4: // 7-bit registers
- case 6:
- case 7:
- case 10:
- Register[AddressRegister] = (byte)(v & 0x7F);
- break;
- case 12: // 6-bit registers
- case 14:
- Register[AddressRegister] = (byte)(v & 0x3F);
- break;
- case 5: // 5-bit registers
- case 9:
- case 11:
- Register[AddressRegister] = (byte)(v & 0x1F);
- break;
- case 8: // Interlace & skew masks bits 2 & 3
- Register[AddressRegister] = (byte)(v & 0xF3);
- break;
- }
- }
-
- ///
- /// Write Active Register (HD6845S & UM6845)
- ///
- private void WriteRegister_Type1(int data)
- {
- byte v = (byte)data;
- switch (AddressRegister)
- {
- case 0: // 8-bit registers
- case 1:
- case 2:
- case 13:
- case 15:
- Register[AddressRegister] = v;
- break;
- case 4: // 7-bit registers
- case 6:
- case 7:
- case 10:
- Register[AddressRegister] = (byte)(v & 0x7F);
- break;
- case 12: // 6-bit registers
- case 14:
- Register[AddressRegister] = (byte)(v & 0x3F);
- break;
- case 5: // 5-bit registers
- case 9:
- case 11:
- Register[AddressRegister] = (byte)(v & 0x1F);
- break;
- case 3: // 4-bit register
- Register[AddressRegister] = (byte)(v & 0x0F);
- break;
- case 8: // Interlace & skew - 2bit
- Register[AddressRegister] = (byte)(v & 0x03);
- break;
- }
- }
-
- ///
- /// Write Active Register (MC6845)
- ///
- private void WriteRegister_Type2(int data)
- {
- byte v = (byte)data;
- switch (AddressRegister)
- {
- case 0: // 8-bit registers
- case 1:
- case 2:
- case 13:
- case 15:
- Register[AddressRegister] = v;
- break;
- case 4: // 7-bit registers
- case 6:
- case 7:
- case 10:
- Register[AddressRegister] = (byte)(v & 0x7F);
- break;
- case 12: // 6-bit registers
- case 14:
- Register[AddressRegister] = (byte)(v & 0x3F);
- break;
- case 5: // 5-bit registers
- case 9:
- case 11:
- Register[AddressRegister] = (byte)(v & 0x1F);
- break;
- case 3: // 4-bit register
- Register[AddressRegister] = (byte)(v & 0x0F);
- break;
- case 8: // Interlace & skew - 2bit
- Register[AddressRegister] = (byte)(v & 0x03);
- break;
- }
- }
-
- ///
- /// Write Active Register (MC6845)
- ///
- private void WriteRegister_Type3_4(int data)
- {
- // unsure of the register sizes at the moment
- // for now we will just read and write 8-bit values
- byte v = (byte)data;
- switch (AddressRegister)
- {
- case 16:
- case 17:
- // read only registers
- return;
- default:
- if (AddressRegister < 16)
- {
- Register[AddressRegister] = v;
- }
- else
- {
- // read only dummy registers
- return;
- }
- break;
- }
- }
-
- ///
- /// Read Status Register (UM6845R)
- /// This is fully implemented
- ///
- private bool ReadStatus_Type1(ref int data)
- {
- // Bit6: Set when latched LPEN strobe input is received / Reset when R17 or R16 is read from
- // Bit5: Set when CRTC is in vblank / Reset when frame is started (VCC = 0)
- data = StatusRegister & 0x60;
- return true;
- }
-
- ///
- /// Read Status Register (AMS40489 and costdown pre-ASIC)
- /// Status Register is unavailable but attempts to read will return the currently
- /// selected register data instead
- ///
- private bool ReadStatus_Type3_4(ref int data)
- {
- return ReadRegister(ref data);
- }
-
- ///
- /// Read Status Register (HD6845S & UM6845)
- /// No status register available
- ///
- private bool ReadStatus_Unavailable(ref int data)
- {
- return false;
- }
-
- /* persistent switch signals */
- private bool s_VS;
- private bool s_HDISP;
- private bool s_VDISP;
- private bool s_HSYNC;
-
- /* Other chip counters */
- ///
- /// Linear Address Generator counter latch
- ///
- private int LAG_Counter_Latch;
-
- ///
- /// Linear Address Generator row counter latch
- ///
- private int LAG_Counter_RowLatch;
-
- ///
- /// Linear Address Generator counter
- ///
- private int LAG_Counter;
-
- private int DISPTMG_Delay_Counter;
- private int CUDISP_Delay_Counter;
- private int CUR_Field_Counter;
-
- ///
- /// Runs a generic CRTC cycle
- ///
- private void ClockCycle_Generic()
- {
-
- }
-
- ///
- /// Runs a Type0 Clock Cycle
- /// Type 0 is the only chip that implements display and cursor skew
- /// http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf
- ///
- private void ClockCycle_Type0()
- {
- /* non-persistent clock signals */
- bool c_VT = false;
- bool c_VTOTAL = false;
- bool c_HMAX = false;
- bool c_HDISP = false;
- bool c_RASMAX = false;
- bool c_VTAMAX = false;
- bool c_HALFHTOTAL = false;
- bool c_CURMATCH = false;
- bool c_CURSKEW = false;
-
- // we are going to clock everything individually but simulate everything
- // happeneing at once (exactly like the chip would do)
-
- /* Character Counter */
- CharacterCTR++;
- if (CharacterCTR == Register[H_DISPLAYED])
- {
- c_HDISP = true;
- s_HDISP = false;
- }
- if (CharacterCTR == Register[H_TOTAL])
- {
- c_HMAX = true;
- s_HDISP = true;
- }
- if (c_HMAX)
- {
- CharacterCTR = 0;
- }
- if (CharacterCTR == Register[H_SYNC_POS])
- {
- s_HSYNC = true;
- }
- if (CharacterCTR == Register[H_TOTAL] / 2)
- {
- c_HALFHTOTAL = true;
- }
-
- /* Horizontal Sync Width Counter */
- if (s_HSYNC)
- {
- HorizontalSyncWidthCTR++;
- if (HorizontalSyncWidthCTR == HSYNCWidth)
- {
- s_HSYNC = false;
- HorizontalSyncWidthCTR = 0;
- }
- }
-
- /* Raster Counter */
- if (c_HMAX)
- {
- RasterCTR++;
- if (RasterCTR == Register[MAX_SL_ADDRESS])
- {
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (RasterCTR == Register[V_DISPLAYED])
- {
- // this will probably never happen, but the Hd6845 block diagram
- // suggests that it is actually wired up this way
- s_VDISP = false;
- }
- if (RasterCTR == Register[V_TOTAL_ADJUST])
- {
- c_VTAMAX = true;
- }
- }
-
- /* Line Counter */
- if (c_RASMAX)
- {
- LineCTR++;
- if (LineCTR == Register[MAX_SL_ADDRESS])
- {
- // again this seems unneccessary, but the Hd6845 block diagram suggests that
- // it is indeed wired this way
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (LineCTR == Register[V_DISPLAYED])
- {
- s_VDISP = false;
- }
- if (LineCTR == Register[V_TOTAL])
- {
- c_VTOTAL = true;
- }
- if (LineCTR == Register[V_SYNC_POS])
- {
- s_VS = true;
- VerticalSyncWidthCTR = 0;
- }
- }
-
- /* Vertical Sync Width Counter */
- if (c_RASMAX && s_VS)
- {
- VerticalSyncWidthCTR++;
- if (VerticalSyncWidthCTR == VSYNCWidth)
- {
- s_VS = false;
- }
- }
-
- /* VTOTAL Control */
- // todo - interlace logic
- if (c_VTOTAL)
- {
- // vertical total has been reached
- LineCTR = 0;
- RasterCTR = 0;
- // reload start address
- StartAddressLatch = StartAddressRegisterValue;
- c_VT = true;
- s_VDISP = true;
- }
- if (c_VTAMAX)
- {
- // extra adjust rasterlines have all been outputted (or there were none)
- // activate the Vdisplay switch
- LineCTR = 0;
- RasterCTR = 0;
- c_VT = true;
- s_VDISP = true;
- }
-
- /* Interlace Control */
- // the interlace control generates the RA0-RA4 row selects
- // this is based on the VT clock and VS switch,
- // the value of the Raster Counter and the current interlace mode
- // it is also clocked by a CO circuit when the horizontal character counter == HALF of the H_Total register value
- // (this is for the interlace sync & video mode I believe)
- // It also responsible for generating the signal on the VSYNC pin
- if (s_VS)
- {
- _VSYNC = true;
- }
- if (c_VT)
- {
- _VSYNC = false;
- }
- if (c_HMAX)
- {
- RowSelects = RasterCTR;
- }
- if (c_HALFHTOTAL)
- {
- // we are half way horizontally across the screen
- // interlace sync and video logic should go here (todo)
- }
-
- /* Linear Address Generator */
- // counter is incremented with every CLK signal
- LAG_Counter++;
-
- if (c_HDISP)
- {
- // horizontal displayed reached - latch this address
- // this is needed when moving to the next vertical character
- // (the CRTC continues counting during the border and HSYNC periods)
- LAG_Counter_Latch = LAG_Counter;
- }
- if (c_HMAX)
- {
- // end of the current raster line
- if (c_RASMAX)
- {
- // this is last raster in the current line
- // counter will continue on from the last row latch
- LAG_Counter = LAG_Counter_Latch;
- // latch this value to be used in all the raster lines in the following line
- LAG_Counter_RowLatch = LAG_Counter;
- }
- else
- {
- // still within the line
- // every raster will generate the same address sequence
- LAG_Counter = LAG_Counter_RowLatch;
- }
- }
- if (c_VT)
- {
- // the VT clock has been received from the VCONTROL
- // LAG counters reset
- LAG_Counter_Latch = 0;
- LAG_Counter_RowLatch = 0;
- LAG_Counter = 0;
- }
-
- // setup MA0-MA13 outputs based on internal counters and programmed start address
- // (this is latched elsewhere and the timing of this is CRTC-type dependent)
- LinearAddress = StartAddressLatch + LAG_Counter;
-
- if (LinearAddress == CursorAddressRegisterValue)
- {
- c_CURMATCH = true;
- }
-
- /* HSYNC */
- _HSYNC = s_HSYNC;
-
- /* DISPTMG skew control */
- if (DISPTMGSkew < 0)
- {
- // no output
- _DISPTMG = false;
- DISPTMG_Delay_Counter = 0;
- }
- else if (s_HDISP && s_VDISP)
- {
- // AND gate feeding the skew control is active
- if (DISPTMGSkew > 0)
- {
- // skew value is set
- if (DISPTMG_Delay_Counter >= DISPTMGSkew)
- {
- // we have finished the start skew
- _DISPTMG = true;
- }
- else
- {
- // start skew still happening
- DISPTMG_Delay_Counter++;
- _DISPTMG = false;
- }
- }
- else
- {
- // no skew set
- _DISPTMG = true;
- }
- }
- else
- {
- // AND gate is inactive - process any possible skew leadout
- if (DISPTMGSkew > 0)
- {
- // skew value is set
- if (DISPTMG_Delay_Counter > 0)
- {
- // leadout skew is still in effect
- DISPTMG_Delay_Counter--;
- _DISPTMG = true;
- }
- else
- {
- // leadout skew has finished
- DISPTMG_Delay_Counter = 0;
- _DISPTMG = false;
- }
- }
- else
- {
- // no skew programmed
- DISPTMG_Delay_Counter = 0;
- _DISPTMG = false;
- }
- }
-
- /* Cursor Control */
- if (s_HDISP && s_VDISP)
- {
- // AND gate is active - cursor control is clocked
- CUR_Field_Counter++;
- bool curOutput = false;
-
- // info from registers
- var curStartRaster = Register[CURSOR_START] & 0x1f;
- var curEndRaster = Register[CURSOR_END] & 0x1f;
- var curDisplayMode = (Register[CURSOR_START] & 0x60) >> 5;
-
- switch (curDisplayMode)
- {
- // Non-blink
- case 0:
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Cursor non-display
- case 1:
- curOutput = false;
- break;
- // Blink 1/16 field rate
- case 2:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Blink 1/32 field rate
- case 3:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- }
-
- if (curOutput)
- {
- c_CURSKEW = true;
- }
- }
- else
- {
- // end of the display
- CUR_Field_Counter = 0;
- }
-
- /* Cursor Skew Control */
- if (c_CURSKEW)
- {
- if (CUDISPSkew < 0)
- {
- // no output
- _CUDISP = false;
- CUDISP_Delay_Counter = 0;
- }
- else
- {
- if (CUDISPSkew > 0)
- {
- // skew value is set
- if (CUDISP_Delay_Counter >= CUDISPSkew)
- {
- // we have finished the start skew
- _CUDISP = true;
- }
- else
- {
- // start skew still happening
- CUDISP_Delay_Counter++;
- _CUDISP = false;
- }
- }
- else
- {
- // no skew set
- _CUDISP = true;
- }
- }
- }
- else
- {
- // process any possible skew leadout
- if (CUDISPSkew > 0)
- {
- // skew value is set
- if (CUDISP_Delay_Counter > 0)
- {
- // leadout skew is still in effect
- CUDISP_Delay_Counter--;
- _CUDISP = true;
- }
- else
- {
- // leadout skew has finished
- CUDISP_Delay_Counter = 0;
- _CUDISP = false;
- }
- }
- else
- {
- // no skew programmed
- CUDISP_Delay_Counter = 0;
- _CUDISP = false;
- }
- }
-
- /* Light Pen Control */
- if (LPSTB)
- {
- // strobe has been detected
- // latch the current address into the light pen registers (R16 & R17)
- Register[LIGHT_PEN_L] = (byte)(LinearAddress & 0xff);
- Register[LIGHT_PEN_H] = (byte)((LinearAddress >> 8) & 0x3f);
-
- _LPSTB = false;
- }
- }
-
- ///
- /// Runs a Type1 Clock Cycle
- /// There doesnt seem to be a block diagram in the datasheets for this, so will use the type0
- /// implementation with a few changes
- /// Type 1 has no skew program bit functionality
- /// However, it does implement a status register
- ///
- private void ClockCycle_Type1()
- {
- /* non-persistent clock signals */
- bool c_VT = false;
- bool c_VTOTAL = false;
- bool c_HMAX = false;
- bool c_HDISP = false;
- bool c_RASMAX = false;
- bool c_VTAMAX = false;
- bool c_HALFHTOTAL = false;
- bool c_CURMATCH = false;
-
- // we are going to clock everything individually but simulate everything
- // happeneing at once (exactly like the chip would do)
-
- /* Character Counter */
- CharacterCTR++;
- if (CharacterCTR == Register[H_DISPLAYED])
- {
- c_HDISP = true;
- s_HDISP = false;
- }
- if (CharacterCTR == Register[H_TOTAL])
- {
- c_HMAX = true;
- s_HDISP = true;
- }
- if (c_HMAX)
- {
- CharacterCTR = 0;
- }
- if (CharacterCTR == Register[H_SYNC_POS])
- {
- s_HSYNC = true;
- }
- if (CharacterCTR == Register[H_TOTAL] / 2)
- {
- c_HALFHTOTAL = true;
- }
-
- /* Horizontal Sync Width Counter */
- if (s_HSYNC)
- {
- HorizontalSyncWidthCTR++;
- if (HorizontalSyncWidthCTR == HSYNCWidth)
- {
- s_HSYNC = false;
- HorizontalSyncWidthCTR = 0;
- }
- }
-
- /* Raster Counter */
- if (c_HMAX)
- {
- RasterCTR++;
- if (RasterCTR == Register[MAX_SL_ADDRESS])
- {
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (RasterCTR == Register[V_DISPLAYED])
- {
- // this will probably never happen, but the Hd6845 block diagram
- // suggests that it is actually wired up this way
- s_VDISP = false;
- }
- if (RasterCTR == Register[V_TOTAL_ADJUST])
- {
- c_VTAMAX = true;
- }
- }
-
- /* Line Counter */
- if (c_RASMAX)
- {
- LineCTR++;
- if (LineCTR == Register[MAX_SL_ADDRESS])
- {
- // again this seems unneccessary, but the Hd6845 block diagram suggests that
- // it is indeed wired this way
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (LineCTR == Register[V_DISPLAYED])
- {
- s_VDISP = false;
- }
- if (LineCTR == Register[V_TOTAL])
- {
- c_VTOTAL = true;
- }
- if (LineCTR == Register[V_SYNC_POS])
- {
- s_VS = true;
- VerticalSyncWidthCTR = 0;
- }
- }
-
- /* Vertical Sync Width Counter */
- if (c_RASMAX && s_VS)
- {
- VerticalSyncWidthCTR++;
- if (VerticalSyncWidthCTR == VSYNCWidth)
- {
- s_VS = false;
- }
- }
-
- /* VTOTAL Control */
- // todo - interlace logic
- if (c_VTOTAL)
- {
- // vertical total has been reached
- LineCTR = 0;
- RasterCTR = 0;
- // reload start address
- StartAddressLatch = StartAddressRegisterValue;
- c_VT = true;
- s_VDISP = true;
- }
- if (c_VTAMAX)
- {
- // extra adjust rasterlines have all been outputted (or there were none)
- // activate the Vdisplay switch
- LineCTR = 0;
- RasterCTR = 0;
- c_VT = true;
- s_VDISP = true;
- }
-
- /* Interlace Control */
- // the interlace control generates the RA0-RA4 row selects
- // this is based on the VT clock and VS switch,
- // the value of the Raster Counter and the current interlace mode
- // it is also clocked by a CO circuit when the horizontal character counter == HALF of the H_Total register value
- // (this is for the interlace sync & video mode I believe)
- // It also responsible for generating the signal on the VSYNC pin
- if (s_VS)
- {
- _VSYNC = true;
- // status register bit 5 'vertical blanking'
- StatusRegister |= 1 << 5;
- }
- if (c_VT)
- {
- _VSYNC = false;
- // status register bit 5 'vertical blanking'
- StatusRegister &= byte.MaxValue ^ (1 << 5);
- }
- if (c_HMAX)
- {
- RowSelects = RasterCTR;
- }
- if (c_HALFHTOTAL)
- {
- // we are half way horizontally across the screen
- // interlace sync and video logic should go here (todo)
- }
-
- /* Linear Address Generator */
- // counter is incremented with every CLK signal
- LAG_Counter++;
-
- if (c_HDISP)
- {
- // horizontal displayed reached - latch this address
- // this is needed when moving to the next vertical character
- // (the CRTC continues counting during the border and HSYNC periods)
- LAG_Counter_Latch = LAG_Counter;
- }
- if (c_HMAX)
- {
- // end of the current raster line
- if (c_RASMAX)
- {
- // this is last raster in the current line
- // counter will continue on from the last row latch
- LAG_Counter = LAG_Counter_Latch;
- // latch this value to be used in all the raster lines in the following line
- LAG_Counter_RowLatch = LAG_Counter;
- }
- else
- {
- // still within the line
- // every raster will generate the same address sequence
- LAG_Counter = LAG_Counter_RowLatch;
- }
- }
- if (c_VT)
- {
- // the VT clock has been received from the VCONTROL
- // LAG counters reset
- LAG_Counter_Latch = 0;
- LAG_Counter_RowLatch = 0;
- LAG_Counter = 0;
- }
-
- // setup MA0-MA13 outputs based on internal counters and programmed start address
- // (this is latched elsewhere and the timing of this is CRTC-type dependent)
- LinearAddress = StartAddressLatch + LAG_Counter;
-
- if (LinearAddress == CursorAddressRegisterValue)
- {
- c_CURMATCH = true;
- }
-
- /* HSYNC */
- _HSYNC = s_HSYNC;
-
- /* DISPTMG */
- if (s_HDISP && s_VDISP)
- {
- _DISPTMG = true;
- }
- else
- {
- _DISPTMG = false;
- }
-
- /* Cursor Control */
- if (s_HDISP && s_VDISP)
- {
- // AND gate is active - cursor control is clocked
- CUR_Field_Counter++;
- bool curOutput = false;
-
- // info from registers
- var curStartRaster = Register[CURSOR_START] & 0x1f;
- var curEndRaster = Register[CURSOR_END] & 0x1f;
- var curDisplayMode = (Register[CURSOR_START] & 0x60) >> 5;
-
- switch (curDisplayMode)
- {
- // Non-blink
- case 0:
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Cursor non-display
- case 1:
- curOutput = false;
- break;
- // Blink 1/16 field rate
- case 2:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Blink 1/32 field rate
- case 3:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- }
-
- if (curOutput)
- {
- _CUDISP = true;
- }
- else
- {
- _CUDISP = false;
- }
- }
- else
- {
- // end of the display
- CUR_Field_Counter = 0;
- }
-
- /* Light Pen Control */
- if (LPSTB)
- {
- // strobe has been detected
- // latch the current address into the light pen registers (R16 & R17)
- Register[LIGHT_PEN_L] = (byte)(LinearAddress & 0xff);
- Register[LIGHT_PEN_H] = (byte)((LinearAddress >> 8) & 0x3f);
-
- // set the 'LPEN register full' bit in the status register
- StatusRegister |= 1 << 6;
-
- _LPSTB = false;
- }
- }
-
- ///
- /// Runs a Type2 Clock Cycle
- /// The MC6845 does have a functional block diagram
- /// http://www.cpcwiki.eu/imgs/d/da/Mc6845.motorola.pdf
- /// The implementation looks a little simpler than the type 0
- /// It has NO status register and NO skew program bit support
- /// HOWEVER, there are some glaring ommissions in the block diagram,
- /// so I am using a modified type 0/1 implementation for now
- ///
- private void ClockCycle_Type2()
- {
- /* non-persistent clock signals */
- bool c_VT = false;
- bool c_VTOTAL = false;
- bool c_HMAX = false;
- bool c_HDISP = false;
- bool c_RASMAX = false;
- bool c_VTAMAX = false;
- bool c_HALFHTOTAL = false;
- bool c_CURMATCH = false;
-
- // we are going to clock everything individually but simulate everything
- // happeneing at once (exactly like the chip would do)
-
- /* Character Counter */
- CharacterCTR++;
- if (CharacterCTR == Register[H_DISPLAYED])
- {
- c_HDISP = true;
- s_HDISP = false;
- }
- if (CharacterCTR == Register[H_TOTAL])
- {
- c_HMAX = true;
- s_HDISP = true;
- }
- if (c_HMAX)
- {
- CharacterCTR = 0;
- }
- if (CharacterCTR == Register[H_SYNC_POS])
- {
- s_HSYNC = true;
- }
- if (CharacterCTR == Register[H_TOTAL] / 2)
- {
- c_HALFHTOTAL = true;
- }
-
- /* Horizontal Sync Width Counter */
- if (s_HSYNC)
- {
- HorizontalSyncWidthCTR++;
- if (HorizontalSyncWidthCTR == HSYNCWidth)
- {
- s_HSYNC = false;
- HorizontalSyncWidthCTR = 0;
- }
- }
-
- /* Raster Counter */
- if (c_HMAX)
- {
- RasterCTR++;
- if (RasterCTR == Register[MAX_SL_ADDRESS])
- {
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (RasterCTR == Register[V_DISPLAYED])
- {
- // this will probably never happen, but the Hd6845 block diagram
- // suggests that it is actually wired up this way
- s_VDISP = false;
- }
- if (RasterCTR == Register[V_TOTAL_ADJUST])
- {
- c_VTAMAX = true;
- }
- }
-
- /* Line Counter */
- if (c_RASMAX)
- {
- LineCTR++;
- if (LineCTR == Register[MAX_SL_ADDRESS])
- {
- // again this seems unneccessary, but the Hd6845 block diagram suggests that
- // it is indeed wired this way
- c_RASMAX = true;
- RasterCTR = 0;
- }
- if (LineCTR == Register[V_DISPLAYED])
- {
- s_VDISP = false;
- }
- if (LineCTR == Register[V_TOTAL])
- {
- c_VTOTAL = true;
- }
- if (LineCTR == Register[V_SYNC_POS])
- {
- s_VS = true;
- VerticalSyncWidthCTR = 0;
- }
- }
-
- /* Vertical Sync Width Counter */
- if (c_RASMAX && s_VS)
- {
- VerticalSyncWidthCTR++;
- if (VerticalSyncWidthCTR == VSYNCWidth)
- {
- s_VS = false;
- }
- }
-
- /* VTOTAL Control */
- // todo - interlace logic
- if (c_VTOTAL)
- {
- // vertical total has been reached
- LineCTR = 0;
- RasterCTR = 0;
- // reload start address
- StartAddressLatch = StartAddressRegisterValue;
- c_VT = true;
- s_VDISP = true;
- }
- if (c_VTAMAX)
- {
- // extra adjust rasterlines have all been outputted (or there were none)
- // activate the Vdisplay switch
- LineCTR = 0;
- RasterCTR = 0;
- c_VT = true;
- s_VDISP = true;
- }
-
- /* Interlace Control */
- // the interlace control generates the RA0-RA4 row selects
- // this is based on the VT clock and VS switch,
- // the value of the Raster Counter and the current interlace mode
- // it is also clocked by a CO circuit when the horizontal character counter == HALF of the H_Total register value
- // (this is for the interlace sync & video mode I believe)
- // It also responsible for generating the signal on the VSYNC pin
- if (s_VS)
- {
- _VSYNC = true;
- }
- if (c_VT)
- {
- _VSYNC = false;
- }
- if (c_HMAX)
- {
- RowSelects = RasterCTR;
- }
- if (c_HALFHTOTAL)
- {
- // we are half way horizontally across the screen
- // interlace sync and video logic should go here (todo)
- }
-
- /* Linear Address Generator */
- // counter is incremented with every CLK signal
- LAG_Counter++;
-
- if (c_HDISP)
- {
- // horizontal displayed reached - latch this address
- // this is needed when moving to the next vertical character
- // (the CRTC continues counting during the border and HSYNC periods)
- LAG_Counter_Latch = LAG_Counter;
- }
- if (c_HMAX)
- {
- // end of the current raster line
- if (c_RASMAX)
- {
- // this is last raster in the current line
- // counter will continue on from the last row latch
- LAG_Counter = LAG_Counter_Latch;
- // latch this value to be used in all the raster lines in the following line
- LAG_Counter_RowLatch = LAG_Counter;
- }
- else
- {
- // still within the line
- // every raster will generate the same address sequence
- LAG_Counter = LAG_Counter_RowLatch;
- }
- }
- if (c_VT)
- {
- // the VT clock has been received from the VCONTROL
- // LAG counters reset
- LAG_Counter_Latch = 0;
- LAG_Counter_RowLatch = 0;
- LAG_Counter = 0;
- }
-
- // setup MA0-MA13 outputs based on internal counters and programmed start address
- // (this is latched elsewhere and the timing of this is CRTC-type dependent)
- LinearAddress = StartAddressLatch + LAG_Counter;
-
- if (LinearAddress == CursorAddressRegisterValue)
- {
- c_CURMATCH = true;
- }
-
- /* HSYNC */
- _HSYNC = s_HSYNC;
-
- /* DISPTMG */
- if (s_HDISP && s_VDISP)
- {
- _DISPTMG = true;
- }
- else
- {
- _DISPTMG = false;
- }
-
- /* Cursor Control */
- if (s_HDISP && s_VDISP)
- {
- // AND gate is active - cursor control is clocked
- CUR_Field_Counter++;
- bool curOutput = false;
-
- // info from registers
- var curStartRaster = Register[CURSOR_START] & 0x1f;
- var curEndRaster = Register[CURSOR_END] & 0x1f;
- var curDisplayMode = (Register[CURSOR_START] & 0x60) >> 5;
-
- switch (curDisplayMode)
- {
- // Non-blink
- case 0:
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Cursor non-display
- case 1:
- curOutput = false;
- break;
- // Blink 1/16 field rate
- case 2:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- // Blink 1/32 field rate
- case 3:
- // not yet implemented
- if (RasterCTR >= curStartRaster && RasterCTR <= curEndRaster && c_CURMATCH)
- curOutput = true;
- break;
- }
-
- if (curOutput)
- {
- _CUDISP = true;
- }
- else
- {
- _CUDISP = false;
- }
- }
- else
- {
- // end of the display
- CUR_Field_Counter = 0;
- }
-
- /* Light Pen Control */
- if (LPSTB)
- {
- // strobe has been detected
- // latch the current address into the light pen registers (R16 & R17)
- Register[LIGHT_PEN_L] = (byte)(LinearAddress & 0xff);
- Register[LIGHT_PEN_H] = (byte)((LinearAddress >> 8) & 0x3f);
-
- _LPSTB = false;
- }
- }
-
- ///
- /// Runs a Type3or4 Clock Cycle
- ///
- private void ClockCycle_Type3_4()
- {
-
- }
-
- /* Horizontal Timing Register Constants */
- ///
- /// This 8 bit write-only register determines the horizontal frequency of HS.
- /// It is the total of displayed plus non-displayed character time units minus one.
- ///
- private const int H_TOTAL = 0;
- ///
- /// This 8 bit write-only register determines the number of displayed characters per horizontal line.
- ///
- private const int H_DISPLAYED = 1;
- ///
- /// This 8 bit write-only register determines the horizontal sync postiion on the horizontal line.
- ///
- private const int H_SYNC_POS = 2;
- ///
- /// This 4 bit write-only register determines the width of the HS pulse. It may not be apparent why this width needs to be programmed.However,
- /// consider that all timing widths must be programmed as multiples of the character clock period which varies.If HS width were fixed as an integral
- /// number of character times, it would vary with character rate and be out of tolerance for certain monitors.
- /// The rate programmable feature allows compensating HS width.
- /// NOTE: Dependent on chiptype this also may include VSYNC width - check the UpdateWidths() method
- ///
- private const int SYNC_WIDTHS = 3;
-
- /* Vertical Timing Register Constants */
- ///
- /// The vertical frequency of VS is determined by both R4 and R5.The calculated number of character I ine times is usual I y an integer plus a fraction to
- /// get exactly a 50 or 60Hz vertical refresh rate. The integer number of character line times minus one is programmed in the 7 bit write-only Vertical Total Register;
- /// the fraction is programmed in the 5 bit write-only Vertical Scan Adjust Register as a number of scan line times.
- ///
- private const int V_TOTAL = 4;
- private const int V_TOTAL_ADJUST = 5;
- ///
- /// This 7 bit write-only register determines the number of displayed character rows on the CRT screen, and is programmed in character row times.
- ///
- private const int V_DISPLAYED = 6;
- ///
- /// This 7 bit write-only register determines the vertical sync position with respect to the reference.It is programmed in character row times.
- ///
- private const int V_SYNC_POS = 7;
- ///
- /// This 2 bit write-only register controls the raster scan mode(see Figure 11 ). When bit 0 and bit 1 are reset, or bit 0 is reset and bit 1 set,
- /// the non· interlace raster scan mode is selected.Two interlace modes are available.Both are interlaced 2 fields per frame.When bit 0 is set and bit 1 is reset,
- /// the interlace sync raster scan mode is selected.Also when bit 0 and bit 1 are set, the interlace sync and video raster scan mode is selected.
- ///
- private const int INTERLACE_MODE = 8;
- ///
- /// This 5 bit write·only register determines the number of scan lines per character row including spacing.
- /// The programmed value is a max address and is one less than the number of scan l1nes.
- ///
- private const int MAX_SL_ADDRESS = 9;
-
- /* Other Main Register Constants */
- ///
- /// This 7 bit write-only register controls the cursor format(see Figure 10). Bit 5 is the blink timing control.When bit 5 is low, the blink frequency is 1/16 of the
- /// vertical field rate, and when bit 5 is high, the blink frequency is 1/32 of the vertical field rate.Bit 6 is used to enable a blink.
- /// The cursor start scan line is set by the lower 5 bits.
- ///
- private const int CURSOR_START = 10;
- ///
- /// This 5 bit write-only register sets the cursor end scan line
- ///
- private const int CURSOR_END = 11;
- ///
- /// Start Address Register is a 14 bit write-only register which determines the first address put out as a refresh address after vertical blanking.
- /// It consists of an 8 bit lower register, and a 6 bit higher register.
- ///
- private const int START_ADDR_H = 12;
- private const int START_ADDR_L = 13;
- ///
- /// This 14 bit read/write register stores the cursor location.This register consists of an 8 bit lower and 6 bit higher register.
- ///
- private const int CURSOR_H = 14;
- private const int CURSOR_L = 15;
- ///
- /// This 14 bit read -only register is used to store the contents of the Address Register(H & L) when the LPSTB input pulses high.
- /// This register consists of an 8 bit lower and 6 bit higher register.
- ///
- private const int LIGHT_PEN_H = 16;
- private const int LIGHT_PEN_L = 17;
-
- ///
- /// The types of CRCT chip found in the CPC range
- ///
- public enum CRTCType
- {
- HD6845S = 0,
- UM6845 = 0,
- UM6845R = 1,
- MC6845 = 2,
- AMS40489 = 3,
- AMS40226 = 4
- }
-
- ///
- /// The available interlace modes in the CRCT
- ///
- private enum InterlaceMode
- {
- NormalSyncMode,
- InterlaceSyncMode,
- InterlaceSyncAndVideoMode
- }
-
- ///
- /// Cursor display modes
- ///
- private enum CursorControl
- {
- NonBlink,
- CursorNonDisplay,
- Blink1_16,
- Blink1_32
- }
-
- ///
- /// Vid Display RAM Addressing Mode
- ///
- private enum VideoDisplayRAMAddressing
- {
- StraightBinary = 0,
- RowColumn = 1
- }
-
- ///
- /// Vid Display RAM Acccess Mode
- ///
- private enum VideoDisplayRAMAccess
- {
- SharedMemory,
- TransparentMemory
- }
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("CRCT");
- ser.SyncEnum(nameof(ChipType), ref ChipType);
- ser.Sync(nameof(_VSYNC), ref _VSYNC);
- ser.Sync(nameof(_HSYNC), ref _HSYNC);
- ser.Sync(nameof(_DISPTMG), ref _DISPTMG);
- ser.Sync(nameof(_CUDISP), ref _CUDISP);
- ser.Sync(nameof(_CLK), ref _CLK);
- ser.Sync(nameof(_RESET), ref _RESET);
- ser.Sync(nameof(_LPSTB), ref _LPSTB);
- ser.Sync(nameof(AddressRegister), ref AddressRegister);
- ser.Sync(nameof(Register), ref Register, false);
- ser.Sync(nameof(StatusRegister), ref StatusRegister);
- ser.Sync(nameof(_CharacterCTR), ref _CharacterCTR);
- ser.Sync(nameof(_HorizontalSyncWidthCTR), ref _HorizontalSyncWidthCTR);
- ser.Sync(nameof(_LineCTR), ref _LineCTR);
- ser.Sync(nameof(_RasterCTR), ref _RasterCTR);
- ser.Sync(nameof(StartAddressLatch), ref StartAddressLatch);
- //ser.Sync(nameof(VDisplay), ref VDisplay);
- //ser.Sync(nameof(HDisplay), ref HDisplay);
- ser.Sync(nameof(RowSelects), ref RowSelects);
- ser.Sync(nameof(DISPTMG_Delay_Counter), ref DISPTMG_Delay_Counter);
- ser.Sync(nameof(CUDISP_Delay_Counter), ref CUDISP_Delay_Counter);
- ser.Sync(nameof(AsicStatusRegister1), ref AsicStatusRegister1);
- ser.Sync(nameof(AsicStatusRegister2), ref AsicStatusRegister2);
- ser.Sync(nameof(LAG_Counter), ref LAG_Counter);
- ser.Sync(nameof(LAG_Counter_Latch), ref LAG_Counter_Latch);
- ser.Sync(nameof(LAG_Counter_RowLatch), ref LAG_Counter_RowLatch);
- ser.Sync(nameof(s_VS), ref s_VS);
- ser.Sync(nameof(s_HDISP), ref s_VS);
- ser.Sync(nameof(s_VDISP), ref s_VDISP);
- ser.Sync(nameof(s_HSYNC), ref s_HSYNC);
- ser.Sync(nameof(CUR_Field_Counter), ref CUR_Field_Counter);
- //ser.Sync(nameof(VS), ref VS);
- ser.EndSection();
- }
- }
-}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs
deleted file mode 100644
index 1a727651af..0000000000
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Display/CRTDevice.cs
+++ /dev/null
@@ -1,637 +0,0 @@
-using BizHawk.Common;
-using BizHawk.Common.NumberExtensions;
-using BizHawk.Emulation.Common;
-
-namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
-{
- ///
- /// Render pixels to the screen
- ///
- public class CRTDevice : IVideoProvider
- {
- private readonly CPCBase _machine;
- private CRCT_6845 CRCT => _machine.CRCT;
- private AmstradGateArray GateArray => _machine.GateArray;
-
- public CRTDevice(CPCBase machine)
- {
- _machine = machine;
- CurrentLine = new ScanLine(this);
-
- CRCT.AttachHSYNCCallback(OnHSYNC);
- CRCT.AttachVSYNCCallback(OnVSYNC);
- }
-
- ///
- /// The standard CPC Pallete (ordered by firmware #)
- /// http://www.cpcwiki.eu/index.php/CPC_Palette
- ///
- 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
- };
-
- ///
- /// The standard CPC Pallete (ordered by hardware #)
- /// http://www.cpcwiki.eu/index.php/CPC_Palette
- ///
- 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
- };
-
-
- ///
- /// The current scanline that is being added to
- /// (will be processed and committed to the screen buffer every HSYNC)
- ///
- public ScanLine CurrentLine;
-
- ///
- /// The number of top border scanlines to ommit when rendering
- ///
- public int TopLinesToTrim = 20;
-
- ///
- /// Count of rendered scanlines this frame
- ///
- public int ScanlineCounter = 0;
-
- ///
- /// Video buffer processing
- ///
- public int[] ProcessVideoBuffer()
- {
- return ScreenBuffer;
- }
-
- ///
- /// Sets up buffers and the like at the start of a frame
- ///
- public void SetupVideo()
- {
- if (BufferHeight == 576)
- return;
-
- BufferWidth = 800;
- BufferHeight = 576;
-
- VirtualWidth = BufferWidth / 2;
- VirtualHeight = BufferHeight / 2;
-
- ScreenBuffer = new int[BufferWidth * BufferHeight];
- }
-
- ///
- /// Fired when the CRCT flags HSYNC
- ///
- public void OnHSYNC()
- {
-
- }
-
- ///
- /// Fired when the CRCT flags VSYNC
- ///
- public void OnVSYNC()
- {
-
- }
-
- ///
- /// Video output buffer
- ///
- public int[] ScreenBuffer;
-
- private int _virtualWidth;
- private int _virtualHeight;
- private int _bufferWidth;
- private int _bufferHeight;
-
- public int BackgroundColor => CPCHardwarePalette[0];
-
- 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 => GateArray.Z80ClockSpeed * 50;
- set { }
- }
-
- public int VsyncDenominator => GateArray.Z80ClockSpeed;
-
- public int[] GetVideoBuffer() => ProcessVideoBuffer()!;
-
- public void SetupScreenSize()
- {
- BufferWidth = 1024; // 512;
- BufferHeight = 768;
- VirtualHeight = BufferHeight;
- VirtualWidth = BufferWidth;
- ScreenBuffer = new int[BufferWidth * BufferHeight];
- croppedBuffer = ScreenBuffer;
- }
-
- protected int[] croppedBuffer;
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("CRT");
- ser.Sync("BufferWidth", ref _bufferWidth);
- ser.Sync("BufferHeight", ref _bufferHeight);
- ser.Sync("VirtualHeight", ref _virtualHeight);
- ser.Sync("VirtualWidth", ref _virtualWidth);
- ser.Sync(nameof(ScreenBuffer), ref ScreenBuffer, false);
- ser.Sync(nameof(ScanlineCounter), ref ScanlineCounter);
- ser.EndSection();
- }
- }
-
- ///
- /// Represents a single scanline buffer
- ///
- public class ScanLine
- {
- ///
- /// Array of character information
- ///
- public Character[] Characters;
-
- ///
- /// The screenmode that was set at the start of this scanline
- ///
- public int ScreenMode = 1;
-
- ///
- /// The scanline number (0 based)
- ///
- public int LineIndex;
-
- ///
- /// The calling CRT device
- ///
- private readonly CRTDevice CRT;
-
- public ScanLine(CRTDevice crt)
- {
- Reset();
- CRT = crt;
- }
-
- // To be run after scanline has been fully processed
- public void InitScanline(int screenMode, int lineIndex)
- {
- Reset();
- ScreenMode = screenMode;
- LineIndex = lineIndex;
- }
-
- ///
- /// Adds a single scanline character into the matrix
- ///
- public void AddScanlineCharacter(int index, RenderPhase phase, byte vid1, byte vid2, int[] pens)
- {
- if (index >= 64)
- {
- return;
- }
-
- switch (phase)
- {
- case RenderPhase.BORDER:
- AddBorderValue(index, CRTDevice.CPCHardwarePalette[pens[16]]);
- break;
- case RenderPhase.DISPLAY:
- AddDisplayValue(index, vid1, vid2, pens);
- break;
- default:
- AddSyncValue(index, phase);
- break;
- }
- }
-
- ///
- /// Adds a HSYNC, VSYNC or HSYNC+VSYNC character into the scanline
- ///
- private void AddSyncValue(int charIndex, RenderPhase phase)
- {
- Characters[charIndex].Phase = phase;
- Characters[charIndex].Pixels = new int[0];
- }
-
- ///
- /// Adds a border character into the scanline
- ///
- private void AddBorderValue(int charIndex, int colourValue)
- {
- Characters[charIndex].Phase = RenderPhase.BORDER;
-
- switch (ScreenMode)
- {
- case 0:
- Characters[charIndex].Pixels = new int[4];
- break;
- case 1:
- Characters[charIndex].Pixels = new int[8];
- break;
- case 2:
- Characters[charIndex].Pixels = new int[16];
- break;
- case 3:
- Characters[charIndex].Pixels = new int[8];
- break;
- }
-
-
-
- for (int i = 0; i < Characters[charIndex].Pixels.Length; i++)
- {
- Characters[charIndex].Pixels[i] = colourValue;
- }
- }
-
- ///
- /// Adds a display character into the scanline
- /// Pixel matrix is calculated based on the current ScreenMode
- ///
- public void AddDisplayValue(int charIndex, byte vid1, byte vid2, int[] pens)
- {
- Characters[charIndex].Phase = RenderPhase.DISPLAY;
-
- // generate pixels based on screen mode
- switch (ScreenMode)
- {
- // 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels)
- // RECT
- case 0:
- Characters[charIndex].Pixels = new int[16];
-
- int m0Count = 0;
-
- int pix = vid1 & 0xaa;
- pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2));
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- pix = vid1 & 0x55;
- pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3)));
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
-
- pix = vid2 & 0xaa;
- pix = ((pix & 0x80) >> 7) | ((pix & 0x08) >> 2) | ((pix & 0x20) >> 3) | ((pix & 0x02 << 2));
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- pix = vid2 & 0x55;
- pix = (((pix & 0x40) >> 6) | ((pix & 0x04) >> 1) | ((pix & 0x10) >> 2) | ((pix & 0x01 << 3)));
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[pix]];
- /*
- int m0B0P0i = vid1 & 0xaa;
- int m0B0P0 = ((m0B0P0i & 0x80) >> 7) | ((m0B0P0i & 0x08) >> 2) | ((m0B0P0i & 0x20) >> 3) | ((m0B0P0i & 0x02 << 2));
- int m0B0P1i = vid1 & 85;
- int m0B0P1 = ((m0B0P1i & 0x40) >> 6) | ((m0B0P1i & 0x04) >> 1) | ((m0B0P1i & 0x10) >> 2) | ((m0B0P1i & 0x01 << 3));
-
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P0]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P0]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P1]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B0P1]];
-
- int m0B1P0i = vid2 & 170;
- int m0B1P0 = ((m0B1P0i & 0x80) >> 7) | ((m0B1P0i & 0x08) >> 2) | ((m0B1P0i & 0x20) >> 3) | ((m0B1P0i & 0x02 << 2));
- int m0B1P1i = vid2 & 85;
- int m0B1P1 = ((m0B1P1i & 0x40) >> 6) | ((m0B1P1i & 0x04) >> 1) | ((m0B1P1i & 0x10) >> 2) | ((m0B1P1i & 0x01 << 3));
-
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P0]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]];
- Characters[charIndex].Pixels[m0Count++] = CRTDevice.CPCHardwarePalette[pens[m0B1P1]];
- */
- break;
-
- // 2 bits per pixel - 2 bytes - 8 pixels (16 CRT pixels)
- // SQUARE
- case 1:
- Characters[charIndex].Pixels = new int[8];
-
- int m1Count = 0;
-
- int m1B0P0 = (((vid1 & 0x80) >> 7) | ((vid1 & 0x08) >> 2));
- int m1B0P1 = (((vid1 & 0x40) >> 6) | ((vid1 & 0x04) >> 1));
- int m1B0P2 = (((vid1 & 0x20) >> 5) | ((vid1 & 0x02)));
- int m1B0P3 = (((vid1 & 0x10) >> 4) | ((vid1 & 0x01) << 1));
-
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P0]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P1]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P2]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B0P3]];
-
- int m1B1P0 = (((vid2 & 0x80) >> 7) | ((vid2 & 0x08) >> 2));
- int m1B1P1 = (((vid2 & 0x40) >> 6) | ((vid2 & 0x04) >> 1));
- int m1B1P2 = (((vid2 & 0x20) >> 5) | ((vid2 & 0x02)));
- int m1B1P3 = (((vid2 & 0x10) >> 4) | ((vid2 & 0x01) << 1));
-
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P0]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P1]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P2]];
- Characters[charIndex].Pixels[m1Count++] = CRTDevice.CPCHardwarePalette[pens[m1B1P3]];
- break;
-
- // 1 bit per pixel - 2 bytes - 16 pixels (16 CRT pixels)
- // RECT
- case 2:
- Characters[charIndex].Pixels = new int[16];
-
- int m2Count = 0;
-
- int[] pixBuff = new int[16];
-
- for (int bit = 7; bit >= 0; bit--)
- {
- int val = vid1.Bit(bit) ? 1 : 0;
- Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]];
-
- }
- for (int bit = 7; bit >= 0; bit--)
- {
- int val = vid2.Bit(bit) ? 1 : 0;
- Characters[charIndex].Pixels[m2Count++] = CRTDevice.CPCHardwarePalette[pens[val]];
- }
- break;
-
- // 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels)
- // RECT
- case 3:
- Characters[charIndex].Pixels = new int[4];
-
- int m3Count = 0;
-
- int m3B0P0i = vid1 & 170;
- int m3B0P0 = ((m3B0P0i & 0x80) >> 7) | ((m3B0P0i & 0x08) >> 2) | ((m3B0P0i & 0x20) >> 3) | ((m3B0P0i & 0x02 << 2));
- int m3B0P1i = vid1 & 85;
- int m3B0P1 = ((m3B0P1i & 0x40) >> 6) | ((m3B0P1i & 0x04) >> 1) | ((m3B0P1i & 0x10) >> 2) | ((m3B0P1i & 0x01 << 3));
-
- Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P0]];
- Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B0P1]];
-
- int m3B1P0i = vid1 & 170;
- int m3B1P0 = ((m3B1P0i & 0x80) >> 7) | ((m3B1P0i & 0x08) >> 2) | ((m3B1P0i & 0x20) >> 3) | ((m3B1P0i & 0x02 << 2));
- int m3B1P1i = vid1 & 85;
- int m3B1P1 = ((m3B1P1i & 0x40) >> 6) | ((m3B1P1i & 0x04) >> 1) | ((m3B1P1i & 0x10) >> 2) | ((m3B1P1i & 0x01 << 3));
-
- Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P0]];
- Characters[charIndex].Pixels[m3Count++] = CRTDevice.CPCHardwarePalette[pens[m3B1P1]];
- break;
- }
- }
-
- ///
- /// Returns the number of pixels decoded in this scanline (border and display)
- ///
- private int GetPixelCount()
- {
- int cnt = 0;
-
- foreach (var c in Characters)
- {
- if (c.Pixels != null)
- cnt += c.Pixels.Length;
- }
-
- return cnt;
- }
-
- ///
- /// Called at the start of HSYNC
- /// Processes and adds the scanline to the Screen Buffer
- ///
- public void CommitScanline()
- {
- int hScale = 1;
- int vScale = 1;
-
- switch (ScreenMode)
- {
- case 0:
- hScale = 1;
- vScale = 2;
- break;
- case 1:
- case 3:
- hScale = 2;
- vScale = 2;
- break;
-
- case 2:
- hScale = 1;
- vScale = 2;
- break;
- }
-
- int hPix = GetPixelCount() * hScale;
- //int hPix = GetPixelCount() * 2;
- int leftOver = CRT.BufferWidth - hPix;
- int lPad = leftOver / 2;
- int rPad = lPad;
- int rem = leftOver % 2;
- if (rem != 0)
- rPad += rem;
-
- if (LineIndex < CRT.TopLinesToTrim)
- {
- return;
- }
-
- // render out the scanline
- int pCount = (LineIndex - CRT.TopLinesToTrim) * vScale * CRT.BufferWidth;
-
- // vScale
- for (int s = 0; s < vScale; s++)
- {
- // left padding
- for (int lP = 0; lP < lPad; lP++)
- {
- CRT.ScreenBuffer[pCount++] = 0;
- }
-
- // border and display
- foreach (var c in Characters)
- {
- if (c.Pixels == null || c.Pixels.Length == 0)
- continue;
-
- for (int p = 0; p < c.Pixels.Length; p++)
- {
- // hScale
- for (int h = 0; h < hScale; h++)
- {
- CRT.ScreenBuffer[pCount++] = c.Pixels[p];
- }
-
- //CRT.ScreenBuffer[pCount++] = c.Pixels[p];
- }
- }
-
- // right padding
- for (int rP = 0; rP < rPad; rP++)
- {
- CRT.ScreenBuffer[pCount++] = 0;
- }
-
- if (pCount != hPix)
- {
-
- }
-
- CRT.ScanlineCounter++;
- }
- }
-
- public void Reset()
- {
- ScreenMode = 1;
- Characters = new Character[64];
-
- for (int i = 0; i < Characters.Length; i++)
- {
- Characters[i] = new Character();
- }
- }
- }
-
- ///
- /// Contains data relating to one character written on one scanline
- ///
- public class Character
- {
- ///
- /// Array of pixels generated for this character
- ///
- public int[] Pixels;
-
- ///
- /// The type (NONE/BORDER/DISPLAY/HSYNC/VSYNC/HSYNC+VSYNC
- ///
- public RenderPhase Phase = RenderPhase.NONE;
-
- public Character()
- {
- Pixels = new int[0];
- }
- }
-
- [Flags]
- public enum RenderPhase : int
- {
- ///
- /// Nothing
- ///
- NONE = 0,
- ///
- /// Border is being rendered
- ///
- BORDER = 1,
- ///
- /// Display rendered from video RAM
- ///
- DISPLAY = 2,
- ///
- /// HSYNC in progress
- ///
- HSYNC = 3,
- ///
- /// VSYNC in process
- ///
- VSYNC = 4,
- ///
- /// HSYNC occurs within a VSYNC
- ///
- HSYNCandVSYNC = 5
- }
-}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/GateArray.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/GateArray.cs
new file mode 100644
index 0000000000..e20443f2e5
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/GateArray.cs
@@ -0,0 +1,1168 @@
+
+using BizHawk.Common;
+using BizHawk.Common.NumberExtensions;
+using BizHawk.Emulation.Cores.Components.Z80A;
+using System.Linq;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// * 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
+ /// http://bread80.com/2021/06/03/understanding-the-amstrad-cpc-video-ram-and-gate-array-subsystem/
+ /// https://cpctech.cpcwiki.de/docs/crtcnew.html
+ ///
+ public class GateArray : IPortIODevice
+ {
+ private readonly CPCBase _machine;
+ private Z80A CPU => _machine.CPU;
+ private CRTC CRTC => _machine.CRTC;
+
+ private CRTScreen CRT => _machine.CRTScreen;
+ private IPSG PSG => _machine.AYDevice;
+ private ushort BUSRQ => CPU.MEMRQ[CPU.bus_pntr];
+ private GateArrayType GateArrayType;
+
+ ///
+ /// True when the frame has ended
+ ///
+ public bool FrameEnd;
+
+ ///
+ /// Length of a GA frame in 1MHz clock cycles
+ ///
+ public int FrameLength => (MAX_SCREEN_WIDTH_PIXELS * TOTAL_DISPLAY_SCANLINES) / 16;
+
+ ///
+ /// Clock speed of the Z80 in Hz
+ ///
+ public double Z80ClockSpeed => 4_000_000;
+
+ ///
+ /// The current GA clock count within the current frame
+ /// Set to -1 at the start of a new frame
+ ///
+ public int GAClockCounter
+ {
+ get { return _GAClockCounter; }
+ set { _GAClockCounter = value; }
+ }
+ private int _GAClockCounter;
+
+
+ ///
+ /// Previous frame clock count. Latched at the end of the frame (VSYNC off)
+ ///
+ public int LastGAFrameClocks
+ {
+ get { return _lastGAFrameClocks; }
+ set { _lastGAFrameClocks = value; }
+ }
+ private int _lastGAFrameClocks;
+
+
+ ///
+ /// 0-15: Pen Registers
+ /// 16: Border Colour
+ ///
+ private int[] _colourRegisters = new int[17];
+
+ ///
+ /// The currently selected Pen
+ ///
+ private int _currentPen;
+
+ ///
+ /// All CPC colour information
+ /// Based on: https://www.grimware.org/doku.php/documentations/devices/gatearray
+ ///
+ private CPCColourData[] CPCPalette = new CPCColourData[32];
+
+ private void SetPalette(GateArrayType gaType)
+ {
+ switch (gaType)
+ {
+ case GateArrayType.Amstrad40007:
+ case GateArrayType.Amstrad40008:
+ case GateArrayType.Amstrad40010:
+
+ // non-asic
+ CPCPalette = new CPCColourData[]
+ {
+ new CPCColourData { IndexFirmware = 0, IndexINKR = 0b01011000, IndexASIC = 0x000, Red = 0.0, Green = 0.9615, Blue = 0.4808 },
+ new CPCColourData { IndexFirmware = 1, IndexINKR = 0b01000100, IndexASIC = 0x006, Red = 0.0, Green = 0.9615, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 2, IndexINKR = 0b01010101, IndexASIC = 0x00F, Red = 4.8077, Green = 0.9615, Blue = 95.6731 },
+ new CPCColourData { IndexFirmware = 3, IndexINKR = 0b01011100, IndexASIC = 0x060, Red = 42.3077, Green = 0.9615, Blue = 0.4808 },
+ new CPCColourData { IndexFirmware = 4, IndexINKR = 0b01011000, IndexASIC = 0x066, Red = 41.3462, Green = 0.9615, Blue = 40.8654 },
+ new CPCColourData { IndexFirmware = 5, IndexINKR = 0b01011101, IndexASIC = 0x06F, Red = 42.3077, Green = 0.9615, Blue = 94.7115 },
+ new CPCColourData { IndexFirmware = 6, IndexINKR = 0b01001100, IndexASIC = 0x0F0, Red = 95.1923, Green = 1.9231, Blue = 2.4038 },
+ new CPCColourData { IndexFirmware = 7, IndexINKR = 0b01000101, IndexASIC = 0x0F6, Red = 94.2308, Green = 0.9615, Blue = 40.8654 },
+ new CPCColourData { IndexFirmware = 8, IndexINKR = 0b01001101, IndexASIC = 0x0FF, Red = 95.1923, Green = 0.9615, Blue = 95.6731 },
+ new CPCColourData { IndexFirmware = 9, IndexINKR = 0b01010110, IndexASIC = 0x600, Red = 0.9615, Green = 47.1154, Blue = 0.4808 },
+ new CPCColourData { IndexFirmware = 10, IndexINKR = 0b01000110, IndexASIC = 0x606, Red = 0.0, Green = 47.1154, Blue = 40.8654 },
+ new CPCColourData { IndexFirmware = 11, IndexINKR = 0b01010111, IndexASIC = 0x60F, Red = 4.8077, Green = 48.0769, Blue = 95.6731 },
+ new CPCColourData { IndexFirmware = 12, IndexINKR = 0b01011110, IndexASIC = 0x660, Red = 43.2692, Green = 48.0769, Blue = 0.4808 },
+ new CPCColourData { IndexFirmware = 13, IndexINKR = 0b01000000, IndexASIC = 0x666, Red = 43.2692, Green = 49.0385, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 14, IndexINKR = 0b01011111, IndexASIC = 0x66F, Red = 43.2692, Green = 48.0769, Blue = 96.6346 },
+ new CPCColourData { IndexFirmware = 15, IndexINKR = 0b01001110, IndexASIC = 0x6F0, Red = 95.1923, Green = 49.0385, Blue = 5.2885 },
+ new CPCColourData { IndexFirmware = 16, IndexINKR = 0b01000111, IndexASIC = 0x6F6, Red = 95.1923, Green = 49.0385, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 17, IndexINKR = 0b01001111, IndexASIC = 0x6FF, Red = 98.0769, Green = 50.0, Blue = 97.5962 },
+ new CPCColourData { IndexFirmware = 18, IndexINKR = 0b01010010, IndexASIC = 0xF00, Red = 0.9615, Green = 94.2308, Blue = 0.4808 },
+ new CPCColourData { IndexFirmware = 19, IndexINKR = 0b01000010, IndexASIC = 0xF06, Red = 0.0, Green = 95.1923, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 20, IndexINKR = 0b01010011, IndexASIC = 0xF0F, Red = 5.7692, Green = 95.1923, Blue = 94.7115 },
+ new CPCColourData { IndexFirmware = 21, IndexINKR = 0b01011010, IndexASIC = 0xF60, Red = 44.2308, Green = 96.1538, Blue = 1.4423 },
+ new CPCColourData { IndexFirmware = 22, IndexINKR = 0b01011001, IndexASIC = 0xF66, Red = 44.2308, Green = 95.1923, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 23, IndexINKR = 0b01011011, IndexASIC = 0xF6F, Red = 44.2308, Green = 95.1923, Blue = 95.6731 },
+ new CPCColourData { IndexFirmware = 24, IndexINKR = 0b01001010, IndexASIC = 0xFF0, Red = 95.1923, Green = 95.1923, Blue = 5.2885 },
+ new CPCColourData { IndexFirmware = 25, IndexINKR = 0b01000011, IndexASIC = 0xFF6, Red = 95.1923, Green = 95.1923, Blue = 42.7885 },
+ new CPCColourData { IndexFirmware = 26, IndexINKR = 0b01001011, IndexASIC = 0xFFF, Red = 100.0, Green = 95.1923, Blue = 97.5962 },
+
+ new CPCColourData { IndexFirmware = 27, IndexINKR = 0b01000001, IndexASIC = 0x666, Red = 43.2692, Green = 48.0769, Blue = 42.7885 },
+ new CPCColourData { IndexFirmware = 28, IndexINKR = 0b01001000, IndexASIC = 0x0F6, Red = 95.1923, Green = 0.9615, Blue = 40.8654 },
+ new CPCColourData { IndexFirmware = 29, IndexINKR = 0b01001001, IndexASIC = 0xFF6, Red = 95.1923, Green = 95.1923, Blue = 41.8269 },
+ new CPCColourData { IndexFirmware = 30, IndexINKR = 0b01010000, IndexASIC = 0x006, Red = 0.0, Green = 0.9615, Blue = 40.8654 },
+ new CPCColourData { IndexFirmware = 31, IndexINKR = 0b01010001, IndexASIC = 0xF06, Red = 0.9615, Green = 95.1923, Blue = 41.8269 },
+ }.OrderBy(x => x.IndexHardware).ToArray();
+
+ break;
+ case GateArrayType.Amstrad40226:
+ case GateArrayType.Amstrad40489:
+
+ // asic
+
+
+ break;
+ }
+ }
+
+ ///
+ /// 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 0
+ Colors.ARGB(0x00, 0x00, 0x80), // Blue 1
+ Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue 2
+ Colors.ARGB(0x80, 0x00, 0x00), // Red 3
+ Colors.ARGB(0x80, 0x00, 0x80), // Magenta 4
+ Colors.ARGB(0x80, 0x00, 0xFF), // Mauve 5
+ Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red 6
+ Colors.ARGB(0xFF, 0x00, 0x80), // Purple 7
+ Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta 8
+ Colors.ARGB(0x00, 0x80, 0x00), // Green 9
+ Colors.ARGB(0x00, 0x80, 0x80), // Cyan 10
+ Colors.ARGB(0x00, 0x80, 0xFF), // Sky Blue 11
+ Colors.ARGB(0x80, 0x80, 0x00), // Yellow 12
+ Colors.ARGB(0x80, 0x80, 0x80), // White 13
+ Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue 14
+ Colors.ARGB(0xFF, 0x80, 0x00), // Orange 15
+ Colors.ARGB(0xFF, 0x80, 0x80), // Pink 16
+ Colors.ARGB(0xFF, 0x80, 0xFF), // Pastel Magenta 17
+ Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green 18
+ Colors.ARGB(0x00, 0xFF, 0x80), // Sea Green 19
+ Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan 20
+ Colors.ARGB(0x80, 0xFF, 0x00), // Lime 21
+ Colors.ARGB(0x80, 0xFF, 0x80), // Pastel Green 22
+ Colors.ARGB(0x80, 0xFF, 0xFF), // Pastel Cyan 23
+ Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow 24
+ Colors.ARGB(0xFF, 0xFF, 0x80), // Pastel Yellow 25
+ Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White 26
+ };
+
+ ///
+ /// 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
+ };
+
+ ///
+ /// 4bit Screen Mode Value
+ /// - Mode 0, 160x200 resolution, 16 colours
+ /// - Mode 1, 320x200 resolution, 4 colours
+ /// - Mode 2, 640x200 resolution, 2 colours
+ /// - Mode 3, 160x200 resolution, 4 colours (undocumented)
+ ///
+ /// When screenmode is updated it will take effect after the next HSync
+ ///
+ private byte _screenMode;
+
+ ///
+ /// 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
+ ///
+ public byte PENR
+ {
+ get => _PENR;
+ set
+ {
+ _PENR = value;
+ if (_PENR.Bit(4))
+ {
+ // border select
+ _currentPen = 16;
+ }
+ else
+ {
+ // pen select
+ _currentPen = _PENR & 0b0000_1111;
+ }
+ }
+ }
+ private byte _PENR;
+
+ ///
+ /// 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
+ ///
+ public byte INKR
+ {
+ get => _INKR;
+ set
+ {
+ _INKR = value;
+ if (_currentPen == 16)
+ {
+
+ }
+ _colourRegisters[_currentPen] = _INKR & 0b0001_1111;
+ }
+ }
+ private byte _INKR;
+
+ ///
+ /// 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)
+ ///
+ public byte RMR
+ {
+ get => _RMR;
+ set
+ {
+ _RMR = value;
+
+ // Upper ROM paging
+ _machine.UpperROMPaged = !_RMR.Bit(3);
+
+ // Lower ROM paging
+ _machine.LowerROMPaged = !_RMR.Bit(2);
+
+ // Interrupt generation control
+ if (_RMR.Bit(4))
+ {
+ // reset interrupt counter
+ _r52 = 0;
+ }
+ }
+ }
+ private byte _RMR;
+
+ ///
+ /// Set when the VSYNC signal is detected from the CRTC
+ ///
+ private bool GA_VSYNC;
+
+ ///
+ /// Set when the HSYNC signal is detected from the CRCT
+ ///
+ private bool GA_HSYNC;
+
+ ///
+ /// VSYNC signal that is generated by the GA and combined with C_HSYNC before being sent to the CRT
+ ///
+ private bool C_VSYNC;
+
+ ///
+ /// HSYNC signal that is generated by the GA and combined with C_VSYNC before being sent to the CRT
+ ///
+ private bool C_HSYNC;
+
+ ///
+ /// Gatearray is outputting black colour during vsync
+ ///
+ private bool C_VSYNC_Black;
+
+ ///
+ /// Gatearray is outputting black colour during hsync
+ ///
+ private bool C_HSYNC_Black;
+
+ public int interruptsPerFrame { get; set; }
+
+ ///
+ /// GA raster counter incremented at the end of every HSYNC signal from the CRTC
+ /// (interrupt counter)
+ ///
+ private int R52
+ {
+ get => _r52 & 0x3F;
+ set
+ {
+ _r52 = value & 0x3F;
+
+ if (_r52 > 51)
+ {
+ _r52 = 0;
+ }
+
+ if (_r52 == 0)
+ {
+ // The GATE ARRAY sends an interrupt request when R52=0
+ CPU.FlagI = true;
+ interruptsPerFrame++;
+ }
+ else if (GA_VSYNC && _r52 == 2)
+ {
+ // Two HSYNC’s after the start of VSYNC:
+ if (_r52.Bit(5))
+ {
+ // An interrupt is requested by the GATE ARRAY from the Z80A only if bit 5 of R52 is 1
+ CPU.FlagI = true;
+ interruptsPerFrame++;
+ }
+
+ // R52 is set to 0 unconditionally
+ _r52 = 0;
+ }
+ }
+ }
+ private int _r52;
+
+ ///
+ /// GA counter that counts the number of HSYNC signals that have been detected during the VSYNC period
+ ///
+ private int V26
+ {
+ get => _v26 & 0x3F;
+ set
+ {
+ _v26 = value & 0x3F;
+
+ if (_v26 == 2)
+ {
+ // GA activates C-SYNC
+ C_VSYNC = true;
+ }
+
+ if (_v26 == 6)
+ {
+ C_VSYNC = false;
+ }
+
+ if (_v26 == 26)
+ {
+ C_VSYNC_Black = false;
+ // vsync completed
+ GA_VSYNC = false;
+ FrameEnd = true;
+ LastGAFrameClocks = GAClockCounter;
+ }
+
+
+ }
+ }
+ private int _v26;
+
+ ///
+ /// Counts the number of CRTC characters processed during a (CRTC) HSYNC signal
+ ///
+ private int H06
+ {
+ get => _h06 & 0xFF;
+ set
+ {
+ _h06 = value & 0xFF;
+
+ if (_h06 == 2)
+ {
+ C_HSYNC = true;
+
+ // latch screenmode
+ _screenMode = (byte)(_RMR & 0x03);
+ }
+
+ if (_h06 == 6)
+ {
+ C_HSYNC = false;
+ }
+ }
+ }
+ private int _h06;
+
+
+ public GateArray(CPCBase machine, GateArrayType gateArrayType)
+ {
+ _machine = machine;
+ GateArrayType = gateArrayType;
+ CRTC.AttachHSYNCOnCallback(OnHSYNCOn);
+ CRTC.AttachHSYNCOffCallback(OnHSYNCOff);
+ CRTC.AttachVSYNCOnCallback(OnVSYNCOn);
+ CRTC.AttachVSYNCOffCallback(OnVSYNCOff);
+
+ SetPalette(GateArrayType);
+
+ Reset();
+ }
+
+ ///
+ /// Called when the Z80 acknowledges an interrupt
+ ///
+ public void IORQA()
+ {
+ // an armed (pending) interrupt is acknowledged/authorised by the CPU
+ // R52 Bit5 is reset
+ _r52 &= ~(1 << 5);
+ CPU.FlagI = false;
+ }
+
+ ///
+ /// Fired when rising edge of CRTC HSYNC signal is detected
+ ///
+ public void OnHSYNCOn()
+ {
+ // CRTC character counter initialised
+ H06 = 0;
+ // gate array hsync is enabled
+ GA_HSYNC = true;
+ // hsync black colour enabled
+ C_HSYNC_Black = true;
+ }
+
+ ///
+ /// Fired when falling edge of CRTC HSYNC signal is detected
+ ///
+ public void OnHSYNCOff()
+ {
+ GA_HSYNC = false;
+
+ // turn off composite hsync
+ C_HSYNC = false;
+
+ // disable composite black
+ C_HSYNC_Black = false;
+
+ // The 6-bit counter is incremented after each HSYNC from the CRTC
+ // (When standard CRTC display settings are used, this is equivalent to counting scan-lines)
+ R52++;
+
+ if (GA_VSYNC)
+ {
+ // vsync is active - count hsyncs
+ V26++;
+ }
+ }
+
+ ///
+ /// Fired when CRTC VSYNC active signal is detected
+ ///
+ public void OnVSYNCOn()
+ {
+ // hsync counter initialised
+ V26 = 0;
+ // gate array vsync is enabled
+ GA_VSYNC = true;
+ // black colour enabled for vsync
+ C_VSYNC_Black = true;
+ }
+
+ ///
+ /// Fired when falling edge of CRTC VSYNC signal is detected
+ ///
+ public void OnVSYNCOff()
+ {
+ //GA_VSYNC = false;
+ // this is effectively the start of a new frame from a bizhawk perspective
+ // latch the current frame clock count
+ LastGAFrameClocks = GAClockCounter;
+
+ // reset the frame clock counter
+ //FrameEnd = true;
+ //GAClockCounter = -1;
+
+
+ // interrupts should be syncronised with the start of the frame now (i.e. InterruptCounter = 0)
+ // CRT beam position should be at the start of the display area
+ }
+
+ ///
+ /// 16 MHz XTAL crystal that clocks the GA
+ /// We will use this as a 4bit clock counter
+ ///
+ private int _xtal;
+
+ ///
+ /// Temporary storage for the first video data byte read from memory
+ ///
+ private byte _videoDataByte1;
+
+ ///
+ /// Temporaryy storage for the second video data byte read from memory
+ ///
+ private byte _videoDataByte2;
+
+ ///
+ /// Two bytes of video data are read over 16 GA clocks.
+ /// During the following 16 GA clocks, the video data is output to the screen
+ ///
+ private byte[] _videoData = new byte[2];
+
+ private ushort[] _videoAddr = new ushort[2];
+
+
+ private byte[] _videoDataBuffer = new byte[64];
+ private int VideoDataPntr
+ {
+ get => _videoDataPntr & 0x3F;
+ set => _videoDataPntr = value & 0x3F;
+ }
+ private int _videoDataPntr = 0;
+
+ private void LatchVideoByte(byte data)
+ {
+ VideoDataPntr++;
+ _videoDataBuffer[VideoDataPntr] = data;
+ }
+
+
+ ///
+ /// Gate array is clocked at 16MHz (which is also the pixel clock)
+ /// It implements a 4-phase clock that is in charge of clocking the following devices:
+ ///
+ /// * CPU: 4MHz (4 GA clocks per z80 PHI clock)
+ /// * CRTC: 1MHz (16 GA clocks per CRTC clock)
+ /// * PSG: 1MHz (16 GA clocks per PSG clock)
+ ///
+ /// Regardless of screen mode, the GA will output a single pixel every 1 GA clock (so a 16MHz pixel clock)
+ /// It constantly reads video data at 2MHz (8 GA clocks per byte)
+ ///
+ /// So:
+ /// - 16 GA clocks (1MHz) takes 1 microsecond, one CRTC character
+ /// - 8 GA clocks (2MHz) takes 0.5 microseconds, a byte of video data is read and 8 pixels are output
+ /// - So each CRTC character is 16 pixels wide
+ ///
+ public void Clock()
+ {
+ // Gatearray should be constantly outputting a single pixel of video data ever 1/16th of a microsecond
+ // (so every 1 GA clock cycle)
+ // We will do this for now to get the accuracy right, but will probably need to optimise this down the line
+ //OutputPixel(0);
+ GAClockCounter++;
+
+
+ // Based on timing oscilloscope traces from
+ // https://bread80.com
+ // and section 16.2.2 of ACCC1.8
+ switch (_xtal)
+ {
+ case 15:
+ // /CPU_ADDR LOW
+ // /RAS HIGH
+
+ if (GA_HSYNC)
+ {
+ H06++;
+ }
+
+ break;
+
+ case 0:
+
+ OutputByte(0);
+
+ // READY HIGH (Z80 /WAIT is inactive)
+ CPU.FlagW = false;
+ // /RAS LOW
+
+ // /CCLK LOW
+ CRTC.CLK = false;
+ CRTC.Clock();
+
+ // PHI HIGH (1)
+ CPU.ExecuteOne();
+ break;
+
+ case 1:
+ // /CAS_ADDR LOW
+
+ // RAM is outputting video data and the gatearray should be latching it in
+ LatchVideoByte(_machine.FetchScreenMemory(CRTC.MA_Address));
+ //_videoData[1] = _videoDataByte2;
+ //_videoDataByte2 = _machine.FetchScreenMemory(CRTC.MA_Address);
+
+
+
+ break;
+
+ case 2:
+ // PHI LOW (1)
+ break;
+
+ case 3:
+ break;
+
+ case 4:
+ // READY LOW (Z80 /WAIT is active)
+ CPU.FlagW = true;
+
+ // PHI HIGH (2)
+ CPU.ExecuteOne();
+ /*
+ if (BUSRQ == 1) // PCh
+ {
+ // Z80 will sample /WAIT during the cycle it intends to access the bus when reading opcodes
+ // but will sample *before* the cycle for other memory accesses
+
+ // opcode fetch memory action upcoming - CPU will wait
+ CPU.TotalExecutedCycles++;
+ }
+ else
+ {
+ // no fetch, or non-opcode fetch - CPU does not wait
+ CPU.ExecuteOne();
+ }
+ */
+
+ break;
+
+ case 5:
+ // /RAS HIGH
+ // /CPU_ADDR HIGH
+ // /CAS_ADDR HIGH
+ break;
+
+ case 6:
+ // PHI LOW (2)
+ break;
+
+ case 7:
+ // /RAS LOW
+ // /CAS_ADDR LOW
+ break;
+
+ case 8:
+
+ OutputByte(0);
+
+ // PHI HIGH (3)
+ CPU.ExecuteOne();
+
+ /*
+ if (BUSRQ > 0)
+ {
+ // memory action upcoming - CPU clock is halted
+ CPU.TotalExecutedCycles++;
+ }
+ else
+ {
+ CPU.ExecuteOne();
+ }
+ */
+ break;
+
+ case 9:
+ break;
+
+ case 10:
+ // PHI LOW (3)
+ break;
+
+ case 11:
+ // /CCLK HIGH
+ CRTC.CLK = true;
+
+ // RAM is outputting video data and the gatearray should be latching it in
+ LatchVideoByte(_machine.FetchScreenMemory(CRTC.MA_Address));
+ //_videoData[0] = _videoDataByte1;
+ //_videoDataByte1 = _machine.FetchScreenMemory(CRTC.MA_Address);
+ break;
+
+ case 12:
+ // PHI HIGH (4)
+ CPU.ExecuteOne();
+
+ /*
+ if (BUSRQ > 0)
+ {
+ // memory action upcoming - CPU clock is halted
+ CPU.TotalExecutedCycles++;
+ }
+ else
+ {
+ CPU.ExecuteOne();
+ }
+ */
+ break;
+
+ case 13:
+ break;
+
+ case 14:
+ break;
+ }
+
+
+
+ _xtal++;
+
+ // enforce 4-bit wraparound
+ _xtal &= 0x0F;
+
+
+ /*
+ if (GA_VSYNC)
+ {
+ FrameEnd = true;
+ LastGAFrameClocks = GAClockCounter;
+ }
+ */
+ }
+
+ private void OutputByte(int byteOffset)
+ {
+ int pen = 0;
+ int colour = 0;
+
+ var dataByte = _videoDataBuffer[VideoDataPntr + byteOffset];
+
+ //var vid = new CPCColourData();
+
+ for (int pixIndex = 0; pixIndex < 8; pixIndex++)
+ {
+ /*
+ if (C_HSYNC)
+ vid.C_HSYNC = true;
+
+ if (C_VSYNC)
+ vid.C_VSYNC = true;
+ */
+
+ if (C_VSYNC_Black)
+ {
+ colour = 0;
+ //vid.ARGB = 0;
+ }
+ else if (C_HSYNC_Black)
+ {
+ colour = 0;
+ //vid.ARGB = 0;
+ }
+ else if (!CRTC.DISPTMG)
+ {
+ //colour = CPCHardwarePalette[_colourRegisters[16]];
+ colour = CPCPalette[_colourRegisters[16]].ARGB;
+ //vid = CPCPalette[_colourRegisters[16]];
+ }
+ else if (CRTC.DISPTMG)
+ {
+ // http://www.cpcmania.com/Docs/Programming/Painting_pixels_introduction_to_video_memory.htm
+ switch (_screenMode)
+ {
+ // Mode 0, 4-bits per pixel, 160x200 resolution, 16 colours
+ // ------------------------------------------------------------------
+ // Video Byte Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ // Pixel: | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
+ // Pixel Bit Enc.: | 0 | 0 | 2 | 2 | 1 | 1 | 3 | 3 |
+ // Pixel Timing: | 0 | 1 |
+ // ------------------------------------------------------------------
+ case 0:
+ pen = pixIndex < 4
+ ? ((dataByte & 0x80) >> 7) | ((dataByte & 0x08) >> 2) | ((dataByte & 0x20) >> 3) | ((dataByte & 0x02) << 2)
+ : ((dataByte & 0x40) >> 6) | ((dataByte & 0x04) >> 1) | ((dataByte & 0x10) >> 2) | ((dataByte & 0x01) << 3);
+ break;
+
+ // Mode 1, 2-bits per pixel, 320x200 resolution, 4 colours
+ // ------------------------------------------------------------------
+ // Video Byte Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ // Pixel: | 0 | 1 | 2 | 3 | 0 | 1 | 2 | 3 |
+ // Pixel Bit Enc.: | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
+ // Pixel Timing: | 0 | 1 | 2 | 3 |
+ // ------------------------------------------------------------------
+ case 1:
+ switch (pixIndex)
+ {
+ // pixel 0
+ case 0:
+ case 1:
+ pen = ((dataByte & 0x80) >> 7) | ((dataByte & 0x08) >> 2);
+ break;
+
+ case 2:
+ case 3:
+ pen = ((dataByte & 0x40) >> 6) | ((dataByte & 0x04) >> 1);
+ break;
+
+ case 4:
+ case 5:
+ pen = ((dataByte & 0x20) >> 5) | (dataByte & 0x02);
+ break;
+
+ case 6:
+ case 7:
+ pen = ((dataByte & 0x10) >> 4) | ((dataByte & 0x01) << 1);
+ break;
+
+ }
+ break;
+
+ // Mode 2, 1-bit per pixel, 640x200 resolution, 2 colours
+ // ------------------------------------------------------------------
+ // Video Byte Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ // Pixel: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ // Pixel Bit Enc.: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ // Pixel Timing: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ // ------------------------------------------------------------------
+ case 2:
+ switch (pixIndex)
+ {
+ case 0:
+ pen = dataByte.Bit(7) ? 1 : 0;
+ break;
+
+ case 1:
+ pen = dataByte.Bit(6) ? 1 : 0;
+ break;
+
+ case 2:
+ pen = dataByte.Bit(5) ? 1 : 0;
+ break;
+
+ case 3:
+ pen = dataByte.Bit(4) ? 1 : 0;
+ break;
+
+ case 4:
+ pen = dataByte.Bit(3) ? 1 : 0;
+ break;
+
+ case 5:
+ pen = dataByte.Bit(2) ? 1 : 0;
+ break;
+
+ case 6:
+ pen = dataByte.Bit(1) ? 1 : 0;
+ break;
+
+ case 7:
+ pen = dataByte.Bit(0) ? 1 : 0;
+ break;
+ }
+ break;
+
+ // Mode 3, 2-bits per pixel, 160x200 resolution, 4 colours (undocumented)
+ // ------------------------------------------------------------------
+ // Video Byte Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+ // Pixel: | 0 | 1 | x | x | 0 | 1 | x | x |
+ // Pixel Bit Enc.: | 0 | 0 | x | x | 1 | 1 | x | x |
+ // Pixel Timing: | 0 | 1 |
+ // ------------------------------------------------------------------
+ case 3:
+ pen = pixIndex < 4
+ ? ((dataByte & 0x80) >> 7) | ((dataByte & 0x08) >> 2)
+ : ((dataByte & 0x40) >> 6) | ((dataByte & 0x04) >> 1);
+ break;
+ }
+
+ //colour = CPCHardwarePalette[_colourRegisters[pen]];
+ colour = CPCPalette[_colourRegisters[pen]].ARGB;
+ //vid = CPCPalette[_colourRegisters[pen]];
+ }
+
+ CRT.VideoClock(colour, -1, C_HSYNC, C_VSYNC);
+ //CRT.VideoClock(vid, -1);
+ }
+ }
+
+
+
+ ///
+ /// Device responds to an IN instruction
+ ///
+ public bool ReadPort(ushort port, ref int result)
+ {
+ switch (GateArrayType)
+ {
+ case GateArrayType.Amstrad40489:
+ // CPC+ and GX4000 return 0x79 for all reads to the gate array (according to mame)
+ result = 0x79;
+ return true;
+
+ default:
+ // Gate array is OUT only
+ return false;
+ }
+ }
+
+
+ public bool GateArrayUnlocked { get; set; }
+
+ ///
+ /// Device responds to an OUT instruction
+ ///
+ public bool WritePort(ushort port, int result)
+ {
+ var portUpper = (byte)(port >> 8);
+ var 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;
+
+ if (!result.Bit(7) && !result.Bit(6))
+ {
+ // PENR register
+ PENR = (byte)result;
+ }
+
+ if (!result.Bit(7) && result.Bit(6))
+ {
+ // INKR register
+ INKR = (byte)result;
+ }
+
+ if (result.Bit(7) && !result.Bit(6))
+ {
+ if (result.Bit(5) && GateArrayUnlocked)
+ {
+ // ASIC & Advanced ROM mapping (unlocked ASIC only)
+ }
+ else
+ {
+ // RMR (or RMR ghost) register
+ RMR = (byte)result;
+ }
+ }
+
+ /*
+ // Gate array functions are selected by decoding the top two bits (6 and 7) of the data byte sent
+ switch ((byte)(result >> 6))
+ {
+ case 0b_00:
+ // PENR register
+ PENR = (byte)result;
+ break;
+
+ case 0b_01:
+ // INKR register
+ INKR = (byte)result;
+ break;
+
+ case 0b_10:
+
+ if (result.Bit(5) && GateArrayUnlocked)
+ {
+ // ASIC & Advanced ROM mapping (unlocked ASIC only)
+ }
+ else
+ {
+ // RMR (or RMR ghost) register
+ RMR = (byte)result;
+ }
+ break;
+
+ case 0b_11:
+ // RAMR register
+ RAMR = (byte)result;
+ break;
+ }
+
+ */
+
+ return true;
+ }
+
+ public void Reset()
+ {
+
+ }
+
+ ///
+ /// CPC always has a 64 character total width (including HSYNC and VSYNC)
+ ///
+ private const int MAX_SCR_CHA_WIDTH = 64;
+
+ ///
+ /// Maximum number of scanlines on the screen (including HSYNC and VSYNC)
+ ///
+ private const int MAX_SCREEN_SCANLINES = 312;
+
+ ///
+ /// 16 pixels per character, each pixel is output at 16MHz - so each CRTC character is 1 microsecond
+ ///
+ private const int PIXEL_WIDTH_PER_CHAR = 16;
+
+ ///
+ /// Maximum screen width in pixels
+ ///
+ private const int MAX_SCREEN_WIDTH_PIXELS = PIXEL_WIDTH_PER_CHAR * MAX_SCR_CHA_WIDTH; // 1024 pixels
+
+ ///
+ /// Beam renders 2 scanlines at a time
+ ///
+ private const int TOTAL_DISPLAY_SCANLINES = MAX_SCREEN_SCANLINES * 2;
+
+
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("GateArray");
+ ser.Sync(nameof(_PENR), ref _PENR);
+ ser.Sync(nameof(_INKR), ref _INKR);
+ ser.Sync(nameof(_RMR), ref _RMR);
+ ser.Sync(nameof(_colourRegisters), ref _colourRegisters, false);
+ ser.Sync(nameof(_currentPen), ref _currentPen);
+ ser.Sync(nameof(_screenMode), ref _screenMode);
+ ser.Sync(nameof(_v26), ref _v26);
+ ser.Sync(nameof(_h06), ref _h06);
+ ser.Sync(nameof(_r52), ref _r52);
+ ser.Sync(nameof(C_HSYNC), ref C_HSYNC);
+ ser.Sync(nameof(C_HSYNC_Black), ref C_HSYNC_Black);
+ ser.Sync(nameof(C_VSYNC), ref C_VSYNC);
+ ser.Sync(nameof(C_VSYNC_Black), ref C_VSYNC_Black);
+ ser.Sync(nameof(_xtal), ref _xtal);
+ ser.Sync(nameof(_videoData), ref _videoData, false);
+ ser.Sync(nameof(_videoDataByte1), ref _videoDataByte1);
+ ser.Sync(nameof(_videoDataByte2), ref _videoDataByte2);
+ ser.Sync(nameof(GA_VSYNC), ref GA_VSYNC);
+ ser.Sync(nameof(GA_HSYNC), ref GA_HSYNC);
+ ser.Sync(nameof(GAClockCounter), ref _GAClockCounter);
+ ser.Sync(nameof(LastGAFrameClocks), ref _lastGAFrameClocks);
+ ser.EndSection();
+ }
+ }
+
+ ///
+ /// Data structure for holding
+ ///
+ public class CPCColourData
+ {
+ ///
+ /// CPC Firmware Index (also defines the green screen luminosity)
+ ///
+ public int IndexFirmware { get; set; }
+
+ ///
+ /// CPC Hardware Index
+ ///
+ public int IndexHardware => IndexINKR & 0b00011111;
+
+ ///
+ /// CPC Hardware Palette Index (the INKR number)
+ ///
+ public int IndexINKR { get; set; }
+
+ ///
+ /// 12-bit ASIC index value
+ ///
+ public int IndexASIC { get; set; }
+
+ ///
+ /// RED channel percentage
+ ///
+ public double Red { get; set; }
+
+ ///
+ /// GREEN channel percentage
+ ///
+ public double Green { get; set; }
+
+ ///
+ /// BLUE channel percentage
+ ///
+ public double Blue { get; set; }
+
+ ///
+ /// .NET ARGB value
+ ///
+ public int ARGB
+ {
+ get
+ {
+ var r = (int)(Red * 255 / 100);
+ var g = (int)(Green * 255 / 100);
+ var b = (int)(Blue * 255 / 100);
+
+ return (r << 16) | (g << 8) | b;
+ }
+ set
+ {
+ int r = (value >> 16) & 0xFF;
+ int g = (value >> 8) & 0xFF;
+ int b = value & 0xFF;
+
+ Red = r * 100 / 255;
+ Green = g * 100 / 255;
+ Blue = b * 100 / 255;
+ }
+ }
+
+ ///
+ /// Composite VSYNC - set when CSYNC is pulsed
+ ///
+ public bool C_VSYNC { get; set; }
+
+ ///
+ /// Composite HSYNC - set when CSYNC is pulsed
+ ///
+ public bool C_HSYNC { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PAL16L8.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PAL16L8.cs
new file mode 100644
index 0000000000..a20e5c1d0d
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PAL16L8.cs
@@ -0,0 +1,72 @@
+using BizHawk.Common;
+using BizHawk.Common.NumberExtensions;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// Programmable Array Logic (PAL) device
+ /// The C6128's second page of 64KB of RAM is controlled by this chip
+ ///
+ public class PAL16L8 : IPortIODevice
+ {
+ private readonly CPCBase _machine;
+
+ ///
+ /// PAL MMR Register
+ /// 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 MMR register enable
+ /// 6 1 MMR register enable
+ /// 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
+ ///
+ public byte MMR => _MMR;
+ private byte _MMR;
+
+ public PAL16L8(CPCBase machine)
+ {
+ _machine = machine;
+ }
+
+ public bool ReadPort(ushort port, ref int result)
+ {
+ // this is write-only
+ return false;
+ }
+
+ public bool WritePort(ushort port, int result)
+ {
+ if (result.Bit(7) && result.Bit(6))
+ {
+ _MMR = (byte)result;
+ return true;
+ }
+ return false;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("PAL");
+ ser.Sync(nameof(MMR), ref _MMR);
+ ser.EndSection();
+ }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI/PPI_8255.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI_8255.cs
similarity index 92%
rename from src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI/PPI_8255.cs
rename to src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI_8255.cs
index 535c111003..42dab20405 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI/PPI_8255.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/PPI_8255.cs
@@ -12,8 +12,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public class PPI_8255 : IPortIODevice
{
private readonly CPCBase _machine;
- private CRCT_6845 CRTC => _machine.CRCT;
- private AmstradGateArray GateArray => _machine.GateArray;
+ private CRTC CRTC => _machine.CRTC;
private IPSG PSG => _machine.AYDevice;
private DatacorderDevice Tape => _machine.TapeDevice;
private IKeyboard Keyboard => _machine.KeyboardDevice;
@@ -141,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
bool isSet = data.Bit(0);
// get the bit in PortC that we wish to change
- var bit = (data >> 1) & 7;
+ int bit = (data >> 1) & 7;
// modify this bit
if (isSet)
@@ -202,7 +201,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
// build the PortB output
// start with every bit reset
- BitArray rBits = new BitArray(8);
+ var rBits = new BitArray(8);
// Bit0 - Vertical Sync ("1"=VSYNC active, "0"=VSYNC inactive)
if (CRTC.VSYNC)
@@ -253,7 +252,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
val &= 0x0f;
// isolate control bits
- var v = Regs[PORT_C] & 0xc0;
+ int v = Regs[PORT_C] & 0xc0;
if (v == 0xc0)
{
@@ -302,14 +301,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public bool ReadPort(ushort port, ref int result)
{
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- // The 8255 responds to bit 11 reset with A10 and A12-A15 set
- //if (portUpper.Bit(3))
- //return false;
-
- var PPIFunc = (port & 0x0300) >> 8; // portUpper & 3;
+ int PPIFunc = (port & 0x0300) >> 8; // portUpper & 3;
switch (PPIFunc)
{
@@ -346,14 +338,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public bool WritePort(ushort port, int result)
{
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
- // The 8255 responds to bit 11 reset with A10 and A12-A15 set
- if (portUpper.Bit(3))
+ if (port.Bit(11))
return false;
- var PPIFunc = portUpper & 3;
+ int PPIFunc = (port >> 8) & 3;
switch (PPIFunc)
{
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Screen.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Screen.cs
new file mode 100644
index 0000000000..2d69348664
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Hardware/Screen.cs
@@ -0,0 +1,328 @@
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ ///
+ /// Basic emulation of a PAL Amstrad CPC CRT screen
+ ///
+ /// Decoding of C-SYNC pulses are not emulated properly,
+ /// we just assume that so long as the C-VSYNC and C-HSYNC periods are correct, the screen will display.
+ ///
+ /// References used:
+ /// - https://www.cpcwiki.eu/index.php/GT64/GT65
+ /// - https://www.cpcwiki.eu/index.php/CTM640/CTM644
+ /// - https://martin.hinner.info/vga/pal.html
+ /// - https://uzebox.org/forums/viewtopic.php?t=11062
+ /// - https://www.cpcwiki.eu/forum/emulators/wish-60hz60fps-support-in-emulators-thought-on-emulating-a-crt/
+ /// - https://www.batsocks.co.uk/readme/video_timing.htm
+ /// - https://web.archive.org/web/20170202185019/https://www.retroleum.co.uk/PALTVtimingandvoltages.html
+ /// - https://web.archive.org/web/20131125145905/http://lipas.uwasa.fi/~f76998/video/modes/
+ /// - https://cpcrulez.fr/coding_grimware-the_mighty_crtc_6845.htm
+ /// - https://www.monitortests.com/blog/timing-parameters-explained/
+ ///
+ public class CRTScreen : IVideoProvider
+ {
+ ///
+ /// The type of monitor to emulate
+ ///
+ public ScreenType ScreenType { get; }
+
+ ///
+ /// Checked and reset in the emulator loop. If true, framend processing happens
+ ///
+ public bool FrameEnd { get; set; }
+
+ ///
+ /// Total line period in microseconds
+ ///
+ private const int LINE_PERIOD = 64;
+
+ ///
+ /// Total horizontal time in pixels
+ ///
+ private const int TOTAL_PIXELS = LINE_PERIOD * PIXEL_TIME;
+
+ ///
+ /// Arbirary processing buffer width
+ ///
+ private const int FRAMEBUFFER_MAX_WIDTH = 1024;
+
+ ///
+ /// Total scanlines per frame
+ /// This is:
+ /// - 625 in interlaced mode (25Hz)
+ /// - 312 in non-interlaced mode (50.08Hz)
+ ///
+ private const int TOTAL_LINES = 625;
+
+ ///
+ /// The number or pixels being rendered every microsecond
+ ///
+ private const int PIXEL_TIME = 16;
+
+ public CRTScreen(ScreenType screenType, AmstradCPC.BorderType bordertype)
+ {
+ ScreenType = screenType;
+
+ TrimLeft = 121;
+ TrimTop = 39;
+ TrimRight = 87;
+ TrimBottom = 10;
+
+ switch (bordertype)
+ {
+ case AmstradCPC.BorderType.Visible:
+ TrimLeft += 25;
+ TrimTop += 45;
+ TrimRight += 40;
+ TrimBottom += 35;
+ break;
+ }
+ }
+
+ ///
+ /// X position of the electron gun (inluding sync areas)
+ ///
+ private int _gunPosH;
+
+ ///
+ /// Position in frame time on the X-axis
+ ///
+ private int _timePosH;
+
+ ///
+ /// Y position of the electron gun (inluding sync areas)
+ ///
+ private int _gunPosV;
+
+ ///
+ /// Position in frame time on the V-Axis
+ ///
+ private int _timePosV;
+
+ private int _verticalTiming;
+ private bool _isVsync;
+ private bool _frameEndPending;
+
+ ///
+ /// Should be called at the pixel clock rate (in the case of the Amstrad CPC, 16MHz)
+ ///
+ public void VideoClock(int colour, int field, bool cHsync = false, bool cVsync = false)
+ {
+ // Beam moves continuously downwards at 50 Hz and left to right with a HSYNC pulse every 15625Hz
+ // Horizontal and vertical movement are independent.
+
+ // PAL Horizontal:
+ // - Beam moves right at a rate of 16 pixels per usec
+ // - As soon as an HSYNC signal is detected, the PLL capacitor discharges and the beam moves back to the left
+ // - If an HSYNC is not detected in time, the PLL will eventually move the beam back to the left
+ // - https://uzebox.org/forums/viewtopic.php?t=11062 suggests that the horizontal line time if this happens is somewhere around 14000Hz (71.43 usec)
+ //
+ // PAL Vertical
+ // - Beam moves down at a rate of 1 line per 64 microseconds (although this movement covers two scanlines)
+ // - As soon as a VSYNC signal is detected, the PLL capacitor discharges and the beam moves back to the top (at the same horizontal position that VSYNC started)
+ // - If a VSYNC is not detected in time, the PLL will eventually move the beam back to the top
+ // - Unsure what the vertical screen time for this action is.
+
+ int hHold = 118; // pixels - approx (71.43 - 64) * 16
+ int vHold = 3; // scanlines
+
+
+ // * horizontal movement *
+ // gun moves left to right at a rate of 16 pixels per usec
+ _timePosH++;
+ _verticalTiming++;
+
+ if (cHsync)
+ {
+ // hsync signal detected - beam is moved to the left and held there until the signal is released
+ _gunPosH = 0;
+ _timePosH = 0;
+ }
+ else if (_timePosH == TOTAL_PIXELS + hHold)
+ {
+ // hsync signal not detected in time - PLL forces the beam back to the left
+ _gunPosH = 0;
+ _timePosH = 0;
+ }
+ else
+ {
+ _gunPosH++;
+
+ if (_gunPosH >= FRAMEBUFFER_MAX_WIDTH)
+ {
+ // gun is at the rightmost position of the screen and is being held there
+ _gunPosH = FRAMEBUFFER_MAX_WIDTH - 1;
+ }
+ }
+
+ // * vertical movement *
+ // gun moves downwards at a rate of 1 line per 64 microseconds - 1024 pixel times (although this movement covers two scanlines)
+ if (!cVsync && _verticalTiming % TOTAL_PIXELS == 0)
+ {
+ // gun moves down
+ _gunPosV += 2;
+ _verticalTiming = 0;
+
+ if (_gunPosV >= TOTAL_LINES - 1)
+ {
+ // gun is at the bottom of the screen and is being held there
+ _gunPosV = TOTAL_LINES - 1;
+ }
+ }
+
+ if (cVsync && !_isVsync)
+ {
+ // initial vsync signal detected - beam is moved back to the top of the screen and held there until the signal is released
+ _gunPosV = 0;
+ _isVsync = true;
+ FrameEnd = true;
+ HackHPos();
+ }
+ else if (cVsync)
+ {
+ // vsync signal ongoing - beam is held at the top of the screen
+ _gunPosV = 0;
+ //HackHPos();
+ }
+ else if (_gunPosV == TOTAL_LINES + vHold)
+ {
+ // vsync signal not detected in time - PLL forces the beam back to the top
+ _gunPosV = 0;
+ _isVsync = false;
+ FrameEnd = true;
+ HackHPos();
+ }
+ else
+ {
+ // no vsync
+ _isVsync = false;
+ }
+
+ void HackHPos()
+ {
+ // the field length is actually 312.5 scanlines, so HSYNC happens half way through a scanline
+ // this is for interlace mode and the first field starts half a scanline later
+ // the second field should start at the same time as HSYNC
+ // for now, we'll reset _verticalTiming to 0 when vertical flyback happens just so the screen isnt broken half way
+ // and deal with interlacing later
+ if (_verticalTiming == 665)
+ {
+ _verticalTiming = 0;
+ }
+ }
+
+ // video output
+ if (!cHsync && !cVsync)
+ {
+ int currPos = (_gunPosV * FRAMEBUFFER_MAX_WIDTH) + _gunPosH;
+ int nextPos = ((_gunPosV + 1) * FRAMEBUFFER_MAX_WIDTH) + _gunPosH;
+ _frameBuffer[currPos] = colour;
+
+ if (nextPos < TOTAL_LINES * FRAMEBUFFER_MAX_WIDTH)
+ _frameBuffer[nextPos] = colour;
+ }
+ }
+
+ public void Reset()
+ {
+ _gunPosH = 0;
+ _gunPosV = 0;
+ }
+
+ private int HDisplayable => FRAMEBUFFER_MAX_WIDTH - TrimLeft - TrimRight;
+ private int VDisplayable => TOTAL_LINES - TrimTop - TrimBottom;
+
+ private readonly int TrimLeft;
+ private readonly int TrimRight;
+ private readonly int TrimTop;
+ private readonly int TrimBottom;
+
+ ///
+ /// Working buffer that encapsulates the entire PAL frame time
+ ///
+ private readonly int[] _frameBuffer = new int[FRAMEBUFFER_MAX_WIDTH * TOTAL_LINES];
+
+
+ public int BackgroundColor => 0;
+ public int VsyncNumerator => 16_000_000; // pixel clock
+ public int VsyncDenominator => 319_488; // 1024 * 312
+
+ public int BufferWidth => HDisplayable;
+ public int BufferHeight => VDisplayable;
+ public int VirtualWidth => HDisplayable;
+ public int VirtualHeight => (int)(VDisplayable * GetVerticalModifier(HDisplayable, VDisplayable, 4.0 / 3.0));
+
+
+ public int[] GetVideoBuffer() => ClampBuffer(_frameBuffer, FRAMEBUFFER_MAX_WIDTH, TOTAL_LINES, TrimLeft, TrimTop, TrimRight, TrimBottom);
+
+ private static int[] ClampBuffer(int[] buffer, int originalWidth, int originalHeight, int trimLeft, int trimTop, int trimRight, int trimBottom)
+ {
+ int newWidth = originalWidth - trimLeft - trimRight;
+ int newHeight = originalHeight - trimTop - trimBottom;
+ int[] newBuffer = new int[newWidth * newHeight];
+
+ for (int y = 0; y < newHeight; y++)
+ {
+ for (int x = 0; x < newWidth; x++)
+ {
+ int originalIndex = (y + trimTop) * originalWidth + (x + trimLeft);
+ int newIndex = y * newWidth + x;
+ newBuffer[newIndex] = buffer[originalIndex];
+ }
+ }
+
+ return newBuffer;
+ }
+
+ private static double GetVerticalModifier(int bufferWidth, int bufferHeight, double targetAspectRatio)
+ {
+ double currentAspectRatio = (double)bufferWidth / bufferHeight;
+ double verticalModifier = currentAspectRatio / targetAspectRatio;
+ return verticalModifier;
+ }
+
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("CRTScreen");
+ ser.Sync(nameof(_gunPosH), ref _gunPosH);
+ ser.Sync(nameof(_timePosH), ref _timePosH);
+ ser.Sync(nameof(_gunPosV), ref _gunPosV);
+ ser.Sync(nameof(_timePosV), ref _timePosV);
+ ser.Sync(nameof(_isVsync), ref _isVsync);
+ ser.Sync(nameof(_frameEndPending), ref _frameEndPending);
+ ser.Sync(nameof(_verticalTiming), ref _verticalTiming);
+ ser.EndSection();
+ }
+ }
+
+ ///
+ /// The type of Amstrad CRT to emulate
+ ///
+ public enum ScreenType
+ {
+ ///
+ /// CTM640/CTM644
+ /// Amstrad colour monitors
+ ///
+ CTM064x,
+
+ ///
+ /// GT64/GT65/GT65-2
+ /// Amstrad green screen monitors
+ ///
+ GT6x
+ }
+
+
+ public class VideoData
+ {
+ public int R { get; set; }
+ public int G { get; set; }
+ public int B { get; set; }
+ public bool C_HSYNC { get; set; }
+ public bool C_VSYNC { get; set; }
+ }
+}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.Port.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.Port.cs
index d32a2ad4bf..b09e07cc8c 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.Port.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.Port.cs
@@ -1,4 +1,5 @@
-using System.Collections;
+
+using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
@@ -13,38 +14,87 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public override byte ReadPort(ushort port)
{
- BitArray portBits = new BitArray(BitConverter.GetBytes(port));
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
+ int finalResult = 0;
+ int result = 0;
+ bool deviceResponse = false;
- int result = 0xff;
+ var devs = DecodeINPort(port);
- if (DecodeINPort(port) == PortDevice.GateArray)
- {
- GateArray.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.CRCT)
- {
- CRCT.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.ROMSelect)
+ foreach (var d in devs)
{
+ if (d == PortDevice.PPI)
+ {
+ PPI.ReadPort(port, ref result);
+ finalResult |= result;
+ deviceResponse = true;
+ }
- }
- else if (DecodeINPort(port) == PortDevice.Printer)
- {
+ if (d == PortDevice.Expansion)
+ {
+ if (!port.Bit(7))
+ {
+ // FDC
+ if (port.Bit(8) && !port.Bit(0))
+ {
+ // FDC status register
+ UPDDiskDevice.ReadStatus(ref result);
+ }
+ else if (port.Bit(8) && port.Bit(0))
+ {
+ // FDC data register
+ UPDDiskDevice.ReadData(ref result);
+ }
- }
- else if (DecodeINPort(port) == PortDevice.PPI)
- {
- PPI.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.Expansion)
- {
+ finalResult |= result;
+ deviceResponse = true;
+ }
+ }
+ if (d == PortDevice.GateArray && !deviceResponse)
+ {
+ // ACCC 4.4.2
+ // The GATE ARRAY is write-only, and the RD pin is in the inactive state, which implies that a read
+ // on this circuit is not considered. At best, a high impedance state available on the data bus is recovered.
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.CRCT)
+ {
+ // ACCC 4.4.2
+ // However, the CRTCs are not connected to the Z80A's RD and WR pins, so there is no detection of the I/O direction.
+ // Consequently, if a read instruction is used on a write register of the CRTC, then a data is sent to the CRTC
+ // (whatever is on the data bus). "it would be risky to trust the returned value".
+ CRTC.WritePort(port, CPU.Regs[CPU.DB]);
+ result = CPU.Regs[CPU.DB];
+
+ if (!deviceResponse)
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.ROMSelect && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.Printer && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.PAL && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
}
- return (byte)result;
+ return (byte)finalResult;
}
///
@@ -53,11 +103,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public override void WritePort(ushort port, byte value)
{
- BitArray portBits = new BitArray(BitConverter.GetBytes(port));
- BitArray dataBits = new BitArray(BitConverter.GetBytes(value));
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
var devs = DecodeOUTPort(port);
foreach (var d in devs)
@@ -66,13 +111,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
GateArray.WritePort(port, value);
}
- else if (d == PortDevice.RAMManagement)
+ else if (d == PortDevice.PAL)
{
// not present in the unexpanded CPC464
}
else if (d == PortDevice.CRCT)
{
- CRCT.WritePort(port, value);
+ CRTC.WritePort(port, value);
}
else if (d == PortDevice.ROMSelect)
{
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs
index 5de6d0080f..1932118a34 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC464/CPC464.cs
@@ -16,12 +16,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
CPC = cpc;
CPU = cpu;
- FrameLength = 79872;
+ CRTC = CRTC.Create(0);
+ GateArray = new GateArray(this, GateArrayType.Amstrad40008);
+ CRTScreen = new CRTScreen(ScreenType.CTM064x, borderType);
+
+ FrameLength = GateArray.FrameLength / 4;
- CRCT = new CRCT_6845(CRCT_6845.CRCTType.MC6845, this);
- //CRT = new CRTDevice(this);
- GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007);
PPI = new PPI_8255(this);
+ PAL = new PAL16L8(this);
TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, FrameLength);
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.Port.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.Port.cs
index 537a18f525..40419f7c07 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.Port.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.Port.cs
@@ -1,7 +1,5 @@
using BizHawk.Common.NumberExtensions;
-using System.Collections;
-
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
///
@@ -15,51 +13,87 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public override byte ReadPort(ushort port)
{
- BitArray portBits = new BitArray(BitConverter.GetBytes(port));
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
+ int finalResult = 0;
+ int result = 0;
+ bool deviceResponse = false;
- int result = 0xff;
+ var devs = DecodeINPort(port);
- if (DecodeINPort(port) == PortDevice.GateArray)
+ foreach (var d in devs)
{
- GateArray.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.CRCT)
- {
- CRCT.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.ROMSelect)
- {
-
- }
- else if (DecodeINPort(port) == PortDevice.Printer)
- {
-
- }
- else if (DecodeINPort(port) == PortDevice.PPI)
- {
- PPI.ReadPort(port, ref result);
- }
- else if (DecodeINPort(port) == PortDevice.Expansion)
- {
- if (!port.Bit(7))
+ if (d == PortDevice.PPI)
{
- // FDC
- if (port.Bit(8) && !port.Bit(0))
+ PPI.ReadPort(port, ref result);
+ finalResult |= result;
+ deviceResponse = true;
+ }
+
+ if (d == PortDevice.Expansion)
+ {
+ if (!port.Bit(7))
{
- // FDC status register
- UPDDiskDevice.ReadStatus(ref result);
- }
- if (port.Bit(8) && port.Bit(0))
- {
- // FDC data register
- UPDDiskDevice.ReadData(ref result);
+ // FDC
+ if (port.Bit(8) && !port.Bit(0))
+ {
+ // FDC status register
+ UPDDiskDevice.ReadStatus(ref result);
+ }
+ else if (port.Bit(8) && port.Bit(0))
+ {
+ // FDC data register
+ UPDDiskDevice.ReadData(ref result);
+ }
+
+ finalResult |= result;
+ deviceResponse = true;
}
}
+
+ if (d == PortDevice.GateArray && !deviceResponse)
+ {
+ // ACCC 4.4.2
+ // The GATE ARRAY is write-only, and the RD pin is in the inactive state, which implies that a read
+ // on this circuit is not considered. At best, a high impedance state available on the data bus is recovered.
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.CRCT)
+ {
+ // ACCC 4.4.2
+ // However, the CRTCs are not connected to the Z80A's RD and WR pins, so there is no detection of the I/O direction.
+ // Consequently, if a read instruction is used on a write register of the CRTC, then a data is sent to the CRTC
+ // (whatever is on the data bus). "it would be risky to trust the returned value".
+ CRTC.WritePort(port, CPU.Regs[CPU.DB]);
+ result = CPU.Regs[CPU.DB];
+
+ if (!deviceResponse)
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.ROMSelect && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.Printer && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
+
+ if (d == PortDevice.PAL && !deviceResponse)
+ {
+ // TODO: confirm this is a write-only port
+ result = 0xFF;
+ finalResult |= result;
+ }
}
- return (byte)result;
+ return (byte)finalResult;
}
///
@@ -68,11 +102,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public override void WritePort(ushort port, byte value)
{
- BitArray portBits = new BitArray(BitConverter.GetBytes(port));
- BitArray dataBits = new BitArray(BitConverter.GetBytes(value));
- byte portUpper = (byte)(port >> 8);
- byte portLower = (byte)(port & 0xff);
-
var devs = DecodeOUTPort(port);
foreach (var d in devs)
@@ -81,19 +110,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
GateArray.WritePort(port, value);
}
- else if (d == PortDevice.RAMManagement)
+ else if (d == PortDevice.PAL)
{
- if (value.Bit(7) && value.Bit(6))
- {
- RAMConfig = value & 0x07;
-
- // additional 64K bank index
- var b64 = value & 0x38;
- }
+ PAL.WritePort(port, value);
}
else if (d == PortDevice.CRCT)
{
- CRCT.WritePort(port, value);
+ CRTC.WritePort(port, value);
}
else if (d == PortDevice.ROMSelect)
{
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs
index dd3b297bcc..19b06252b0 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPC6128/CPC6128.cs
@@ -16,12 +16,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
CPC = cpc;
CPU = cpu;
- FrameLength = 79872;
+ CRTC = CRTC.Create(0);
+ GateArray = new GateArray(this, GateArrayType.Amstrad40010);
+ CRTScreen = new CRTScreen(ScreenType.CTM064x, borderType);
+
+ FrameLength = GateArray.FrameLength / 4;
- CRCT = new CRCT_6845(CRCT_6845.CRCTType.MC6845, this);
- //CRT = new CRTDevice(this);
- GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007);
PPI = new PPI_8255(this);
+ PAL = new PAL16L8(this);
TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, FrameLength);
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Memory.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Memory.cs
index 80cb7bcd9f..f4127049b2 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Memory.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Memory.cs
@@ -50,13 +50,13 @@
///
/// The currently selected RAM config
///
- public int RAMConfig;
+ public int RAMConfig => PAL.MMR & 0b0000_0111;
///
/// Always 0 on a CPC6128
/// On a machine with more than 128K RAM (standard memory expansion) this selects each additional 64K above the first upper 64K
///
- public int RAM64KBank;
+ public int RAM64KBank => PAL.MMR & 0b0011_1000;
///
/// Simulates reading from the bus
@@ -96,7 +96,7 @@
public abstract void InitROM(RomData[] romData);
///
- /// ULA reads the memory at the specified address
+ /// Gate Array reads the memory at the specified address
/// (No memory contention)
///
public virtual byte FetchScreenMemory(ushort addr)
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Port.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Port.cs
index d70afd9101..18b5431ee9 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Port.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.Port.cs
@@ -26,32 +26,32 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// https://web.archive.org/web/20090808085929/http://www.cepece.info/amstrad/docs/iopord.html
/// http://www.cpcwiki.eu/index.php/I/O_Port_Summary
///
- protected virtual PortDevice DecodeINPort(ushort port)
+ protected virtual List DecodeINPort(ushort port)
{
- PortDevice dev = PortDevice.Unknown;
+ var devs = new List();
if (!port.Bit(15) && port.Bit(14))
- dev = PortDevice.GateArray;
+ devs.Add(PortDevice.GateArray);
- else if (!port.Bit(15))
- dev = PortDevice.RAMManagement;
+ if (!port.Bit(15))
+ devs.Add(PortDevice.PAL);
- else if (!port.Bit(14))
- dev = PortDevice.CRCT;
+ if (!port.Bit(14))
+ devs.Add(PortDevice.CRCT);
- else if (!port.Bit(13))
- dev = PortDevice.ROMSelect;
+ if (!port.Bit(13))
+ devs.Add(PortDevice.ROMSelect);
- else if (!port.Bit(12))
- dev = PortDevice.Printer;
+ if (!port.Bit(12))
+ devs.Add(PortDevice.Printer);
- else if (!port.Bit(11))
- dev = PortDevice.PPI;
+ if (!port.Bit(11))
+ devs.Add(PortDevice.PPI);
- else if (!port.Bit(10))
- dev = PortDevice.Expansion;
+ if (!port.Bit(10))
+ devs.Add(PortDevice.Expansion);
- return dev;
+ return devs;
}
///
@@ -62,13 +62,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
protected virtual List DecodeOUTPort(ushort port)
{
- List devs = new List();
+ var devs = new List();
if (!port.Bit(15) && port.Bit(14))
devs.Add(PortDevice.GateArray);
if (!port.Bit(15))
- devs.Add(PortDevice.RAMManagement);
+ devs.Add(PortDevice.PAL);
+
+ if (!port.Bit(15))
+ devs.Add(PortDevice.PAL);
if (!port.Bit(14))
devs.Add(PortDevice.CRCT);
@@ -95,7 +98,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
Unknown,
GateArray,
- RAMManagement,
+ PAL,
CRCT,
ROMSelect,
Printer,
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs
index 37401b2bc0..5b5e6bd757 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/CPCBase.cs
@@ -53,23 +53,28 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
/// The Cathode Ray Tube Controller chip
///
- public CRCT_6845 CRCT { get; set; }
+ public CRTC CRTC { get; set; }
///
/// The Amstrad gate array
///
- public AmstradGateArray GateArray { get; set; }
+ public GateArray GateArray { get; set; }
- // ///
- // /// Renders pixels to the screen
- // ///
- // public CRTDevice CRT { get; set; }
+ ///
+ /// The CRT screen
+ ///
+ public CRTScreen CRTScreen { get; set; }
///
/// The PPI contoller chip
///
public PPI_8255 PPI { get; set; }
+ ///
+ /// PAL16L8 Programmable Logic Array Circuit
+ ///
+ public PAL16L8 PAL { get; set; }
+
///
/// The length of a standard frame in CPU cycles
///
@@ -80,11 +85,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public bool FrameCompleted;
- ///
- /// Overflow from the previous frame (in Z80 cycles)
- ///
- public int OverFlow;
-
///
/// The total number of frames rendered
///
@@ -103,7 +103,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
/// Gets the current frame cycle according to the CPU tick count
///
- public virtual long CurrentFrameCycle => GateArray.FrameClock; // CPU.TotalExecutedCycles - LastFrameStartCPUTick;
+ public virtual long CurrentFrameCycle => GateArray.GAClockCounter; // GateArray.FrameClock; // CPU.TotalExecutedCycles - LastFrameStartCPUTick;
///
/// Non-Deterministic bools
@@ -125,9 +125,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
///
public virtual void ExecuteFrame(bool render, bool renderSound)
{
- GateArray.FrameEnd = false;
- CRCT.lineCounter = 0;
-
InputRead = false;
_render = render;
_renderSound = renderSound;
@@ -144,17 +141,24 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
PollInput();
- //CRT.SetupVideo();
- //CRT.ScanlineCounter = 0;
+ GateArray.GAClockCounter = 0;
+ GateArray.FrameEnd = false;
- while (!GateArray.FrameEnd)
+ while (!CRTScreen.FrameEnd)
{
- GateArray.ClockCycle();
+ GateArray.Clock();
// cycle the tape device
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
TapeDevice.TapeCycle();
}
+
+ CRTScreen.FrameEnd = false;
+
+ var ipf = GateArray.interruptsPerFrame;
+ GateArray.interruptsPerFrame = 0;
+ double nops = GateArray.LastGAFrameClocks / 16.0;
+
// we have reached the end of a frame
LastFrameStartCPUTick = CPU.TotalExecutedCycles; // - OverFlow;
@@ -171,6 +175,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
CPC.IsLagFrame = !InputRead;
// FDC debug
+ /*
if (UPDDiskDevice != null && UPDDiskDevice.writeDebug)
{
// only write UPD log every second
@@ -181,8 +186,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
//System.IO.File.WriteAllText(UPDDiskDevice.outputfile, UPDDiskDevice.outputString);
}
}
+ */
- GateArray.FrameClock = 0;
+ // setup GA for next frame
+ GateArray.GAClockCounter = 0;
}
///
@@ -297,7 +304,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
ser.BeginSection("CPCMachine");
ser.Sync(nameof(FrameCompleted), ref FrameCompleted);
- ser.Sync(nameof(OverFlow), ref OverFlow);
ser.Sync(nameof(FrameCount), ref FrameCount);
ser.Sync(nameof(_frameCycles), ref _frameCycles);
ser.Sync(nameof(inputRead), ref inputRead);
@@ -317,12 +323,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Sync(nameof(UpperROMPosition), ref UpperROMPosition);
ser.Sync(nameof(UpperROMPaged), ref UpperROMPaged);
ser.Sync(nameof(LowerROMPaged), ref LowerROMPaged);
- ser.Sync(nameof(RAMConfig), ref RAMConfig);
- ser.Sync(nameof(RAM64KBank), ref RAM64KBank);
- CRCT.SyncState(ser);
- //CRT.SyncState(ser);
+ CRTC.SyncState(ser);
GateArray.SyncState(ser);
+ PPI.SyncState(ser);
+ PAL.SyncState(ser);
KeyboardDevice.SyncState(ser);
TapeBuzzer.SyncState(ser);
AYDevice.SyncState(ser);
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/GateArrayBase.cs b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/GateArrayBase.cs
deleted file mode 100644
index 0111de039b..0000000000
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/Machine/GateArrayBase.cs
+++ /dev/null
@@ -1,475 +0,0 @@
-using BizHawk.Common;
-using BizHawk.Emulation.Common;
-using BizHawk.Emulation.Cores.Components.Z80A;
-
-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;
-
- private readonly CPCBase _machine;
- private Z80A CPU => _machine.CPU;
- private CRCT_6845 CRCT => _machine.CRCT;
- private IPSG PSG => _machine.AYDevice;
-
- ///
- /// 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;
-
- ///
- /// 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
- };
-
- 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;
- }
- }
- }
- }
-
- private readonly int[] PenColours;
- private int CurrentPen;
- private int ScreenMode;
- private int INTScanlineCnt;
- //private int VSYNCDelyCnt;
-
- private readonly int[][] Lookup = new int[4][];
-
- //private bool DoModeUpdate;
-
- //private int LatchedMode;
- //private int buffPos;
-
- public bool FrameEnd;
-
- public bool WaitLine;
-
- ///
- /// 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;
- }
- }
- }
-
- ///
- /// 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;
- }
-
-
- public void Reset()
- {
- CurrentPen = 0;
- ScreenMode = 1;
- for (int i = 0; i < 17; i++)
- PenColours[i] = 0;
- INTScanlineCnt = 0;
- //VSYNCDelyCnt = 0;
- }
-
- ///
- /// 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;
- }
-
- ///
- /// 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;
- }
-}
diff --git a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/readme.md b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/readme.md
index 48122d9d3b..a8bd8243cf 100644
--- a/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/readme.md
+++ b/src/BizHawk.Emulation.Cores/Computers/AmstradCPC/readme.md
@@ -1,10 +1,9 @@
-## CPCHawk
+# CPCHawk
-Still very much work in progress....
+Work has started up again as of Sep2024.
-#### In Place
-
-* CPC464, CPC6128
+## In Place
+* CPC464, CPC6128 (default)
* Port IO decoding
* i8255 Programmable Peripheral Interface (PPI) chip emulation
* AY-3-8912 PSG (and Port IO)
@@ -12,19 +11,94 @@ Still very much work in progress....
* FDC and FDD devices
* .DSK image parsing and identification (to auto differenciate from ZX Spectrum disk bootloader)
-#### There, but needs more work
-* CRCT (Cathode Ray Tube Controller) chip emulation
+## Right now
+* Gate array implementation undergoing a full re-write
+* CRTC implementations are being re-written (based on https://shaker.logonsystem.eu/ACCC1.8-EN.pdf)
+* CRT screen emulation in progress
* Amstrad Gate Array chip emulation
-* Video rendering (modes 0, 1, 2 & 3)
-* Datacorder (tape) device
-* .CDT tape image file support
+* Z80 timing verification
-#### Not Yet
+## Future..
* CPC664, CPC464plus, CPC6128plus, GX4000 models
* Expansion IO
+* Working Datacorder (tape) emulation and handling of .CDT files
+* Memory expansions
+* External peripherals (Speech Synthesizers etc..)
+* Optimisation of EVERYTHING
-#### Known Issues
-* The CRCT and Gatearray chips are undergoing a re-write at the moment, so video emulation is nowhere near accurate (yet)
-* .CDT tape image files will nearly always fail to load - timing conversion is still needed (so the tape device 'plays back' at the wrong speed)
+## Known Issues
+* Lots of things
+
+## References in use
+* https://shaker.logonsystem.eu/ACCC1.8-EN.pdf
+* https://www.cpcwiki.eu/forum/amstrad-cpc-hardware/need-plustest-dsk-testbench-9-output-on-original-cpc-6128/
+
+## Test suites
+
+
+### WinAPU PlusTest (http://www.winape.net/download/plustest.zip)
+
+#### Test 1: ASIC Sprite test
+Plus models and GX4000 only - awaiting implementation
+
+#### Test 2: Monitor HSYNC test
+Failing
+
+#### Test 3: Horizontal Split and Soft-Scroll test
+Failing
+
+#### Test 4: Interrupt, VSync and R52 timing test
+Unknown
+
+#### Test 5: Instruction timing test
+
+| Prefix | OPC | Inst. | Comments |
+|:----:|:----:|:-----:|:------------:|
+|NONE| D3:4 | OUT A | |
+|NONE| DB:4 | IN A | |
+|NONE| D3:5 | OUT A | |
+|NONE| DB:5 | IN A | |
+|ED| 41:3 | OUT (C), B | |
+|ED| 49:3 | OUT (C), C | |
+|ED| 51:3 | OUT (C), D | |
+|ED| 59:3 | OUT (C), E | |
+|ED| 61:3 | OUT (C), H | |
+|ED| 69:3 | OUT (C), L | |
+|ED| 71:3 | OUT (C), 0 | |
+|ED| 79:3 | OUT (C), A | |
+|ED| A2:6 | INI | |
+|ED| A3:6 | OUTI | |
+|ED| AA:6 | IND | |
+|ED| B2:7/6| INIR | |
+|ED| B3:7/6| OTIR | |
+|ED| BA:7/6| INDR | |
+|ED| BB:7/6| OTDR | |
+|DD CB| D3:5| SET 2, (ix+d), e | |
+|DD CB| DB:5| SET 3, (ix+d), e | |
+
+Everything else passes. Almost certainly the problems observed relate to IO timing.
+
+#### Test 6: Register 0 test
+Unknown
+
+#### Test 7: Register 4 test
+Unknown
+
+#### Test 8: Register 9 test
+Unknown
+
+#### Test 9: Interrupt Wait test
+
+
+
+### Amstrad Diagnostics (https://github.com/llopis/amstrad-diagnostics)
+
+Program stalls. Likely because the firmware vertical flyback checks are not happening at the right time.
+Might be fixed when port timing is correct.
+
+
+### Shaker Tests (https://shaker.logonsystem.eu/shaker26.dsk)
+
+Need to correct CRTC emulation and port access timing before even attempting these.
-Asnivor
diff --git a/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/GateArrayType.cs b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/GateArrayType.cs
new file mode 100644
index 0000000000..45af999c81
--- /dev/null
+++ b/src/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/GateArrayType.cs
@@ -0,0 +1,38 @@
+namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
+{
+ public enum GateArrayType
+ {
+ ///
+ /// CPC 464
+ /// The first version of the Gate Array is the 40007 and was released with the CPC 464
+ ///
+ Amstrad40007,
+ ///
+ /// 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)
+ ///
+ Amstrad40008,
+ ///
+ /// 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
+ ///
+ Amstrad40010,
+ ///
+ /// 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
+ ///
+ Amstrad40226,
+ ///
+ /// 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
+ ///
+ Amstrad40489,
+ }
+}