CPCHawk: Start of new CRCT and Gatearray implementations

This commit is contained in:
Asnivor 2018-09-19 14:35:22 +01:00
parent a7e0e728a7
commit 95e565c545
17 changed files with 2296 additions and 278 deletions

View File

@ -686,8 +686,8 @@ namespace BizHawk.Client.Common
nextComm,
xmlGame.Assets.Select(a => a.Value), //.First(),
cpcGI, // GameInfo.NullInstance,
(AmstradCPC.AmstradCPCSettings)GetCoreSettings<ZXSpectrum>(),
(AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings<ZXSpectrum>());
(AmstradCPC.AmstradCPCSettings)GetCoreSettings<AmstradCPC>(),
(AmstradCPC.AmstradCPCSyncSettings)GetCoreSyncSettings<AmstradCPC>());
break;
case "PSX":
var entries = xmlGame.AssetFullPaths;

View File

@ -38,12 +38,15 @@
this.determEmucheckBox1 = new System.Windows.Forms.CheckBox();
this.lblAutoLoadText = new System.Windows.Forms.Label();
this.autoLoadcheckBox1 = new System.Windows.Forms.CheckBox();
this.lblBorderInfo = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.borderTypecomboBox1 = new System.Windows.Forms.ComboBox();
this.SuspendLayout();
//
// OkBtn
//
this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OkBtn.Location = new System.Drawing.Point(247, 375);
this.OkBtn.Location = new System.Drawing.Point(249, 432);
this.OkBtn.Name = "OkBtn";
this.OkBtn.Size = new System.Drawing.Size(60, 23);
this.OkBtn.TabIndex = 3;
@ -55,7 +58,7 @@
//
this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.CancelBtn.Location = new System.Drawing.Point(313, 375);
this.CancelBtn.Location = new System.Drawing.Point(315, 432);
this.CancelBtn.Name = "CancelBtn";
this.CancelBtn.Size = new System.Drawing.Size(60, 23);
this.CancelBtn.TabIndex = 4;
@ -80,7 +83,7 @@
this.MachineSelectionComboBox.FormattingEnabled = true;
this.MachineSelectionComboBox.Location = new System.Drawing.Point(12, 62);
this.MachineSelectionComboBox.Name = "MachineSelectionComboBox";
this.MachineSelectionComboBox.Size = new System.Drawing.Size(361, 21);
this.MachineSelectionComboBox.Size = new System.Drawing.Size(363, 21);
this.MachineSelectionComboBox.TabIndex = 13;
this.MachineSelectionComboBox.SelectionChangeCommitted += new System.EventHandler(this.MachineSelectionComboBox_SelectionChangeCommitted);
//
@ -105,7 +108,7 @@
// determEmucheckBox1
//
this.determEmucheckBox1.AutoSize = true;
this.determEmucheckBox1.Location = new System.Drawing.Point(15, 302);
this.determEmucheckBox1.Location = new System.Drawing.Point(12, 373);
this.determEmucheckBox1.Name = "determEmucheckBox1";
this.determEmucheckBox1.Size = new System.Drawing.Size(135, 17);
this.determEmucheckBox1.TabIndex = 21;
@ -115,7 +118,7 @@
// lblAutoLoadText
//
this.lblAutoLoadText.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblAutoLoadText.Location = new System.Drawing.Point(175, 319);
this.lblAutoLoadText.Location = new System.Drawing.Point(172, 390);
this.lblAutoLoadText.Name = "lblAutoLoadText";
this.lblAutoLoadText.Size = new System.Drawing.Size(196, 30);
this.lblAutoLoadText.TabIndex = 27;
@ -126,20 +129,53 @@
// autoLoadcheckBox1
//
this.autoLoadcheckBox1.AutoSize = true;
this.autoLoadcheckBox1.Location = new System.Drawing.Point(15, 325);
this.autoLoadcheckBox1.Location = new System.Drawing.Point(12, 396);
this.autoLoadcheckBox1.Name = "autoLoadcheckBox1";
this.autoLoadcheckBox1.Size = new System.Drawing.Size(128, 17);
this.autoLoadcheckBox1.TabIndex = 26;
this.autoLoadcheckBox1.Text = "Auto Tape Start/Stop";
this.autoLoadcheckBox1.UseVisualStyleBackColor = true;
//
// lblBorderInfo
//
this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblBorderInfo.Location = new System.Drawing.Point(175, 331);
this.lblBorderInfo.Name = "lblBorderInfo";
this.lblBorderInfo.Size = new System.Drawing.Size(196, 21);
this.lblBorderInfo.TabIndex = 30;
this.lblBorderInfo.Text = "null";
this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(12, 315);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(118, 13);
this.label2.TabIndex = 29;
this.label2.Text = "Rendered Border Type:";
//
// borderTypecomboBox1
//
this.borderTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.borderTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.borderTypecomboBox1.FormattingEnabled = true;
this.borderTypecomboBox1.Location = new System.Drawing.Point(12, 331);
this.borderTypecomboBox1.Name = "borderTypecomboBox1";
this.borderTypecomboBox1.Size = new System.Drawing.Size(159, 21);
this.borderTypecomboBox1.TabIndex = 28;
//
// AmstradCPCCoreEmulationSettings
//
this.AcceptButton = this.OkBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.CancelBtn;
this.ClientSize = new System.Drawing.Size(385, 410);
this.ClientSize = new System.Drawing.Size(387, 467);
this.Controls.Add(this.lblBorderInfo);
this.Controls.Add(this.label2);
this.Controls.Add(this.borderTypecomboBox1);
this.Controls.Add(this.lblAutoLoadText);
this.Controls.Add(this.autoLoadcheckBox1);
this.Controls.Add(this.determEmucheckBox1);
@ -170,5 +206,8 @@
private System.Windows.Forms.CheckBox determEmucheckBox1;
private System.Windows.Forms.Label lblAutoLoadText;
private System.Windows.Forms.CheckBox autoLoadcheckBox1;
private System.Windows.Forms.Label lblBorderInfo;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox borderTypecomboBox1;
}
}

View File

@ -31,6 +31,15 @@ namespace BizHawk.Client.EmuHawk
MachineSelectionComboBox.SelectedItem = _syncSettings.MachineType.ToString();
UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString()));
// border selecton
var borderTypes = Enum.GetNames(typeof(AmstradCPC.BorderType));
foreach (var val in borderTypes)
{
borderTypecomboBox1.Items.Add(val);
}
borderTypecomboBox1.SelectedItem = _syncSettings.BorderType.ToString();
UpdateBorderNotes((AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), borderTypecomboBox1.SelectedItem.ToString()));
// deterministic emulation
determEmucheckBox1.Checked = _syncSettings.DeterministicEmulation;
@ -42,12 +51,14 @@ namespace BizHawk.Client.EmuHawk
{
bool changed =
_syncSettings.MachineType.ToString() != MachineSelectionComboBox.SelectedItem.ToString()
|| _syncSettings.BorderType.ToString() != borderTypecomboBox1.SelectedItem.ToString()
|| _syncSettings.DeterministicEmulation != determEmucheckBox1.Checked
|| _syncSettings.AutoStartStopTape != autoLoadcheckBox1.Checked;
if (changed)
{
_syncSettings.MachineType = (MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString());
_syncSettings.BorderType = (AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), borderTypecomboBox1.SelectedItem.ToString());
_syncSettings.DeterministicEmulation = determEmucheckBox1.Checked;
_syncSettings.AutoStartStopTape = autoLoadcheckBox1.Checked;
@ -80,5 +91,28 @@ namespace BizHawk.Client.EmuHawk
{
lblMachineNotes.Text = CPCMachineMetaData.GetMetaString(type);
}
private void borderTypecomboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cb = sender as ComboBox;
UpdateBorderNotes((AmstradCPC.BorderType)Enum.Parse(typeof(AmstradCPC.BorderType), cb.SelectedItem.ToString()));
}
private void UpdateBorderNotes(AmstradCPC.BorderType type)
{
switch (type)
{
case AmstradCPC.BorderType.Uniform:
lblBorderInfo.Text = "Attempts to equalise the border areas";
break;
case AmstradCPC.BorderType.Uncropped:
lblBorderInfo.Text = "Pretty much the signal the gate array is generating (looks pants)";
break;
case AmstradCPC.BorderType.Widescreen:
lblBorderInfo.Text = "Top and bottom border removed so that the result is *almost* 16:9";
break;
}
}
}
}

View File

@ -140,6 +140,7 @@
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IKeyboard.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPortIODevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Abstraction\IPSG.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Display\CRCTChip.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Display\CRCT_6845.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Datacorder\DatacorderDevice.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\Disk\CHRN.cs" />
@ -171,6 +172,7 @@
<Compile Include="Computers\AmstradCPC\Hardware\PPI\PPI_8255.cs" />
<Compile Include="Computers\AmstradCPC\Media\Disk\CPCExtendedFloppyDisk.cs" />
<Compile Include="Computers\AmstradCPC\Media\Disk\CPCFloppyDisk.cs" />
<Compile Include="Computers\AmstradCPC\Media\Disk\DiskHandler.cs" />
<Compile Include="Computers\AmstradCPC\Media\Disk\DiskType.cs" />
<Compile Include="Computers\AmstradCPC\Media\Disk\FloppyDisk.cs" />
<Compile Include="Computers\AmstradCPC\Media\MediaConverter.cs" />

View File

@ -101,6 +101,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
[DefaultValue(true)]
public bool AutoStartStopTape { get; set; }
[DisplayName("Border type")]
[Description("Select how to show the border area")]
[DefaultValue(BorderType.Uniform)]
public BorderType BorderType { get; set; }
public AmstradCPCSyncSettings Clone()
{
return (AmstradCPCSyncSettings)MemberwiseClone();
@ -299,5 +304,26 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
}
/// <summary>
/// The size of the Spectrum border
/// </summary>
public enum BorderType
{
/// <summary>
/// Attempts to equalise the border areas
/// </summary>
Uniform,
/// <summary>
/// Pretty much the signal the gate array is generating (looks shit)
/// </summary>
Uncropped,
/// <summary>
/// Top and bottom border removed so that the result is *almost* 16:9
/// </summary>
Widescreen,
}
}
}

View File

@ -46,11 +46,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
case MachineType.CPC464:
ControllerDefinition = AmstradCPCControllerDefinition;
Init(MachineType.CPC464, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape);
Init(MachineType.CPC464, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape,
((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).BorderType);
break;
case MachineType.CPC6128:
ControllerDefinition = AmstradCPCControllerDefinition;
Init(MachineType.CPC6128, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape);
Init(MachineType.CPC6128, _files, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).AutoStartStopTape, ((AmstradCPCSyncSettings)syncSettings as AmstradCPCSyncSettings).BorderType);
break;
default:
throw new InvalidOperationException("Machine not yet emulated");
@ -72,7 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(_cpu);
ser.Register<IVideoProvider>(_machine.CRT);
ser.Register<IVideoProvider>(_machine.GateArray);
// initialize sound mixer and attach the various ISoundProvider devices
SoundMixer = new SoundProviderMixer((int)(32767 / 10), "Tape Audio", (ISoundProvider)_machine.TapeBuzzer);
@ -158,7 +159,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
private MachineType _machineType;
private void Init(MachineType machineType, List<byte[]> files, bool autoTape)
private void Init(MachineType machineType, List<byte[]> files, bool autoTape, BorderType bType)
{
_machineType = machineType;
@ -166,7 +167,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
switch (machineType)
{
case MachineType.CPC464:
_machine = new CPC464(this, _cpu, files, autoTape);
_machine = new CPC464(this, _cpu, files, autoTape, bType);
List<RomData> roms64 = new List<RomData>();
roms64.Add(RomData.InitROM(MachineType.CPC464, GetFirmware(0x4000, "OS464ROM"), RomData.ROMChipType.Lower));
roms64.Add(RomData.InitROM(MachineType.CPC464, GetFirmware(0x4000, "BASIC1-0ROM"), RomData.ROMChipType.Upper, 0));
@ -174,7 +175,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
break;
case MachineType.CPC6128:
_machine = new CPC6128(this, _cpu, files, autoTape);
_machine = new CPC6128(this, _cpu, files, autoTape, bType);
List<RomData> roms128 = new List<RomData>();
roms128.Add(RomData.InitROM(MachineType.CPC6128, GetFirmware(0x4000, "OS6128ROM"), RomData.ROMChipType.Lower));
roms128.Add(RomData.InitROM(MachineType.CPC6128, GetFirmware(0x4000, "BASIC1-1ROM"), RomData.ROMChipType.Upper, 0));

View File

@ -0,0 +1,832 @@
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using System.Collections;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// CRT CONTROLLER (CRTC)
/// http://archive.pcjs.org/pubs/pc/datasheets/MC6845-CRT.pdf
/// http://www.cpcwiki.eu/imgs/c/c0/Hd6845.hitachi.pdf
/// http://www.cpcwiki.eu/imgs/1/13/Um6845.umc.pdf
/// http://www.cpcwiki.eu/imgs/b/b5/Um6845r.umc.pdf
/// http://www.cpcwiki.eu/index.php/CRTC
/// </summary>
public class CRCTChip
{
#region Devices
private CPCBase _machine { get; set; }
private CRCTType ChipType;
#endregion
#region Construction
public CRCTChip(CRCTType chipType, CPCBase machine)
{
_machine = machine;
ChipType = chipType;
//Reset();
}
#endregion
#region Output Lines
// State output lines
/// <summary>
/// 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.
/// </summary>
public bool VSYNC { get { return _VSYNC; } }
/// <summary>
/// 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.
/// </summary>
public bool HSYNC { get { return _HSYNC; } }
/// <summary>
/// This TTL compatible output is an active high signal which indicates the CRTC is providing addressing in the active Display Area.
/// </summary>
public bool DisplayEnable { get { return DisplayEnable; } }
/// <summary>
/// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal.
/// </summary>
public bool Cursor { get { return _Cursor; } }
private bool _VSYNC;
private bool _HSYNC;
private bool _DisplayEnable;
private bool _Cursor;
// 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 { get { return LinearAddress.Bit(0); } }
public bool MA1 { get { return LinearAddress.Bit(1); } }
public bool MA2 { get { return LinearAddress.Bit(2); } }
public bool MA3 { get { return LinearAddress.Bit(3); } }
public bool MA4 { get { return LinearAddress.Bit(4); } }
public bool MA5 { get { return LinearAddress.Bit(5); } }
public bool MA6 { get { return LinearAddress.Bit(6); } }
public bool MA7 { get { return LinearAddress.Bit(7); } }
public bool MA8 { get { return LinearAddress.Bit(8); } }
public bool MA9 { get { return LinearAddress.Bit(9); } }
public bool MA10 { get { return LinearAddress.Bit(10); } } // cpcwiki would suggest that this isnt connected in the CPC range
public bool MA11 { get { return LinearAddress.Bit(11); } } // cpcwiki would suggest that this isnt connected in the CPC range
public bool MA12 { get { return LinearAddress.Bit(12); } } // cpcwiki would suggest that this is connected in the CPC range but not used
public bool MA13 { get { return 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 { get { return ScanLineCTR.Bit(0); } }
public bool RA1 { get { return ScanLineCTR.Bit(1); } }
public bool RA2 { get { return ScanLineCTR.Bit(2); } }
public bool RA3 { get { return ScanLineCTR.Bit(3); } } // cpcwiki would suggest that this isnt connected in the CPC range
public bool RA4 { get { return ScanLineCTR.Bit(4); } } // cpcwiki would suggest that this isnt connected in the CPC range
/// <summary>
/// Built from R12, R13 and CLK
/// This is a logical emulator output and is how the CPC gatearray would translate the lines
/*
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
*/
/// </summary>
public ushort AddressLine
{
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];
}
}
#endregion
#region Input Lines
/// <summary>
/// This TTL compatible output indicates Cursor Display to external Video Processing Logic.Active high signal.
/// </summary>
public bool CLK { get { return _CLK; } }
/// <summary>
/// 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.
/// </summary>
public bool RESET { get { return _RESET; } }
/// <summary>
/// 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.
/// </summary>
public bool LPSTB { get { return _LPSTB; } }
private bool _CLK;
private bool _RESET;
private bool _LPSTB;
#endregion
#region Internal Registers
/// <summary>
/// The currently selected register
/// </summary>
private byte AddressRegister;
/// <summary>
/// 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.
/// </summary>
private byte[] Register = new byte[18];
// Horizontal timing register constants
/// <summary>
/// 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.
/// </summary>
private const int H_TOTAL = 0;
/// <summary>
/// This 8 bit write-only register determines the number of displayed characters per horizontal line.
/// </summary>
private const int H_DISPLAYED = 1;
/// <summary>
/// This 8 bit write-only register determines the horizontal sync postiion on the horizontal line.
/// </summary>
private const int H_SYNC_POS = 2;
/// <summary>
/// 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
/// </summary>
private const int SYNC_WIDTHS = 3;
// Vertical timing register constants
/// <summary>
/// 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.
/// </summary>
private const int V_TOTAL = 4;
private const int V_TOTAL_ADJUST = 5;
/// <summary>
/// This 7 bit write-only register determines the number of displayed character rows on the CRT screen, and is programmed in character row times.
/// </summary>
private const int V_DISPLAYED = 6;
/// <summary>
/// This 7 bit write-only register determines the vertical sync position with respect to the reference.It is programmed in character row times.
/// </summary>
private const int V_SYNC_POS = 7;
/// <summary>
/// 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.
/// </summary>
private const int INTERLACE_MODE = 8;
/// <summary>
/// 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.
/// </summary>
private const int MAX_SL_ADDRESS = 9;
// Other register constants
/// <summary>
/// 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.
/// </summary>
private const int CURSOR_START = 10;
/// <summary>
/// This 5 bit write-only register sets the cursor end scan line
/// </summary>
private const int CURSOR_END = 11;
/// <summary>
/// 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.
/// </summary>
private const int START_ADDR_H = 12;
private const int START_ADDR_L = 13;
/// <summary>
/// This 14 bit read/write register stores the cursor location.This register consists of an 8 bit lower and 6 bit higher register.
/// </summary>
private const int CURSOR_H = 14;
private const int CURSOR_L = 15;
/// <summary>
/// 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.
/// </summary>
private const int LIGHT_PEN_H = 16;
private const int LIGHT_PEN_L = 17;
#endregion
#region Internal Fields & Properties
/// <summary>
/// Calculated when set based on R3
/// </summary>
private int HSYNCWidth;
/// <summary>
/// Calculated when set based on R3
/// </summary>
private int VSYNCWidth;
/// <summary>
/// Character pos address (0 index).
/// Feeds the MA lines
/// </summary>
private int LinearAddress;
/// <summary>
/// The currently selected Interlace Mode (based on R8)
/// </summary>
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;
}
}
/// <summary>
/// The current cursor display mode (based on R14 & R15)
/// </summary>
private CursorControl CurrentCursorMode
{
get
{
if (!Register[CURSOR_START].Bit(6) && !Register[CURSOR_START].Bit(5))
{
return CursorControl.NonBlink;
}
else if (!Register[CURSOR_START].Bit(6) && Register[CURSOR_START].Bit(5))
{
return CursorControl.CursorNonDisplay;
}
else if (Register[CURSOR_START].Bit(6) && !Register[CURSOR_START].Bit(5))
{
return CursorControl.Blink1_16;
}
else
{
return CursorControl.Blink1_32;
}
}
}
// Counter microchips
private int HorizontalCTR { get { return _HorizontalCTR; }
set
{
if (value > 255)
_HorizontalCTR = value - 255;
}
}
private int HorizontalSyncWidthCTR
{
get { return _HorizontalSyncWidthCTR; }
set
{
if (value > 15)
_HorizontalSyncWidthCTR = value - 15;
}
}
private int CharacterRowCTR
{
get { return CharacterRowCTR; }
set
{
if (value > 127)
_CharacterRowCTR = value - 127;
}
}
private int ScanLineCTR
{
get { return ScanLineCTR; }
set
{
if (value > 31)
_ScanLineCTR = value - 31;
}
}
private int _HorizontalCTR;
private int _HorizontalSyncWidthCTR;
private int _CharacterRowCTR;
private int _ScanLineCTR;
#endregion
#region Databus Interface
/*
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 -
*/
/// <summary>
/// CPU (or other device) reads from the 8-bit databus
/// </summary>
/// <param name="port"></param>
/// <param name="result"></param>
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;
}
/// <summary>
/// CPU (or other device) writes to the 8-bit databus
/// </summary>
/// <param name="port"></param>
/// <param name="result"></param>
public 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;
}
#endregion
#region Internal IO Methods
/// <summary>
/// Selects a specific register
/// </summary>
/// <param name="value"></param>
private void SelectRegister(int value)
{
var v = (byte)((byte)value & 0x1F);
if (v > 0 && v < 18)
{
AddressRegister = v;
}
}
/// <summary>
/// Writes to the currently latched address register
/// </summary>
/// <param name="value"></param>
private void WriteRegister(int value)
{
byte val = (byte)value;
// lightpen regs are readonly on all models
if (AddressRegister == 16 || AddressRegister == 17)
return;
// all other models can be written to
switch (AddressRegister)
{
case H_TOTAL:
case H_DISPLAYED:
case H_SYNC_POS:
case START_ADDR_L:
Register[AddressRegister] = val;
break;
case SYNC_WIDTHS:
Register[AddressRegister] = val;
UpdateWidths();
break;
case V_TOTAL_ADJUST:
case CURSOR_END:
case MAX_SL_ADDRESS:
Register[AddressRegister] = (byte)(val & 0x1F);
break;
case START_ADDR_H:
case CURSOR_H:
Register[AddressRegister] = (byte)(val & 0x3F);
break;
case V_TOTAL:
case V_DISPLAYED:
case V_SYNC_POS:
case CURSOR_START:
Register[AddressRegister] = (byte)(val & 0x7F);
break;
case INTERLACE_MODE:
Register[AddressRegister] = (byte)(val & 0x3);
break;
}
}
/// <summary>
/// Reads from the currently selected register
/// </summary>
/// <param name="data"></param>
private bool ReadRegister(ref int data)
{
bool addressed = false;
switch (AddressRegister)
{
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 ((int)ChipType == 0 || (int)ChipType == 1)
{
addressed = true;
data = 0;
}
break;
case 12:
case 13:
addressed = true;
if ((int)ChipType == 0)
data = Register[AddressRegister];
else if ((int)ChipType == 1)
data = 0;
break;
case 14:
case 15:
case 16:
case 17:
addressed = true;
data = Register[AddressRegister];
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 (AddressRegister >= 18 && AddressRegister <= 30)
{
switch ((int)ChipType)
{
case 0:
case 2:
case 1:
addressed = true;
data = 0;
break;
}
}
else if (AddressRegister == 31)
{
if ((int)ChipType == 1)
{
addressed = true;
data = 0x0ff;
}
else if ((int)ChipType == 0 || (int)ChipType == 2)
{
addressed = true;
data = 0;
}
}
break;
}
return addressed;
}
/// <summary>
/// Updates the V and H SYNC widths
/// </summary>
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 = (Register[SYNC_WIDTHS] >> 0) & 0x0F;
VSYNCWidth = (Register[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 = (Register[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 = (Register[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 = (Register[SYNC_WIDTHS] >> 0) & 0x0F;
VSYNCWidth = (Register[SYNC_WIDTHS] >> 4) & 0x0F;
if (HSYNCWidth == 0)
HSYNCWidth = 16;
if (VSYNCWidth == 0)
VSYNCWidth = 16;
break;
}
}
/// <summary>
/// Reads from the status register
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
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;
}
#endregion
#region Public Functions
/// <summary>
/// Performs a CRCT clock cycle.
/// On CPC this is called at 1Mhz == 1 Character cycle (2 bytes)
/// </summary>
public void ClockCycle()
{
// H clock
HorizontalCTR++;
if (HorizontalCTR == Register[H_TOTAL])
{
// end of current scanline
HorizontalCTR = 0;
// CRCT starts its scalines at the display area
_DisplayEnable = true;
ScanLineCTR++;
if (ScanLineCTR > Register[MAX_SL_ADDRESS])
{
// end of vertical character
ScanLineCTR = 0;
CharacterRowCTR++;
if (CharacterRowCTR == Register[V_TOTAL])
{
// check for vertical adjust
if (Register[V_TOTAL_ADJUST] > 0)
{
}
else
{
// end of CRCT frame
CharacterRowCTR = 0;
}
}
}
}
else if (HorizontalCTR == Register[H_DISPLAYED] + 1)
{
// end of display area
_DisplayEnable = false;
}
}
#endregion
#region Internal Functions
#endregion
#region Serialization
public void SyncState(Serializer ser)
{
ser.BeginSection("CRCT");
ser.SyncEnum("ChipType", ref ChipType);
ser.Sync("_VSYNC", ref _VSYNC);
ser.Sync("_HSYNC", ref _HSYNC);
ser.Sync("_DisplayEnable", ref _DisplayEnable);
ser.Sync("_Cursor", ref _Cursor);
ser.Sync("_CLK", ref _CLK);
ser.Sync("_RESET", ref _RESET);
ser.Sync("_LPSTB", ref _LPSTB);
ser.Sync("AddressRegister", ref AddressRegister);
ser.Sync("Register", ref Register, false);
ser.Sync("HSYNCWidth", ref HSYNCWidth);
ser.Sync("VSYNCWidth", ref VSYNCWidth);
ser.Sync("_HorizontalCTR", ref _HorizontalCTR);
ser.Sync("_HorizontalSyncWidthCTR", ref _HorizontalSyncWidthCTR);
ser.Sync("_CharacterRowCTR", ref _CharacterRowCTR);
ser.Sync("_ScanLineCTR", ref _ScanLineCTR);
ser.EndSection();
/*
* */
}
#endregion
#region Enums
/// <summary>
/// The types of CRCT chip found in the CPC range
/// </summary>
public enum CRCTType
{
HD6845S = 0,
UM6845 = 0,
UM6845R = 1,
MC6845 = 2,
AMS40489 = 3,
AMS40226 = 4
}
/// <summary>
/// The available interlace modes in the CRCT
/// </summary>
private enum InterlaceMode
{
NormalSyncMode,
InterlaceSyncMode,
InterlaceSyncAndVideoMode
}
/// <summary>
/// Cursor display modes
/// </summary>
private enum CursorControl
{
NonBlink,
CursorNonDisplay,
Blink1_16,
Blink1_32
}
#endregion
}
}

View File

@ -19,16 +19,37 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#endregion
#region CallBacks
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;
}
#endregion
#region Construction
public CRCT_6845(CRCTType chipType, CPCBase machine)
{
_machine = machine;
ChipType = chipType;
Reset();
}
private const int WRITE = 0;
private const int READ = 1;
#endregion
#region Public Lines
@ -55,15 +76,25 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public short MA;
/// <summary>
/// Vertical Character Count
/// </summary>
public int VCC;
/// <summary>
/// Vertical Scanline Count (within the current vertical character)
/// </summary>
public int VLC;
#endregion
#region Public Lookups
/*
* These are not accessible directlyon real hardware
* It just makes screen generation easier to have these accessbile from the gate array
*/
/// <summary>
/// The total frame width (in characters)
/// </summary>
@ -86,6 +117,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
/// <summary>
/// The total frame height (in scanlines)
/// </summary>
public int FrameHeightInChars
{
get
{
return ((int)Regs[VER_TOTAL] + 1);
}
}
/// <summary>
/// The width of the display area (in characters)
/// </summary>
@ -108,6 +150,17 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
/// <summary>
/// The width of the display area (in scanlines)
/// </summary>
public int DisplayHeightInChars
{
get
{
return (int)Regs[VER_DISPLAYED];
}
}
/// <summary>
/// The character at which to start HSYNC
/// </summary>
@ -153,7 +206,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
/// <summary>
/// The number of scanlines in one character
/// The number of scanlines in one character (MAXRASTER)
/// </summary>
public int ScanlinesPerCharacter
{
@ -183,6 +236,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
public int DStartHigh
{ get { return Regs[DISP_START_ADDR_H]; } }
public int DStartLow
{ get { return Regs[DISP_START_ADDR_L]; } }
/// <summary>
/// Returns the video buffer size as specified within R12
/// </summary>
@ -394,12 +453,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// 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)
/// </summary>
private byte[] RegDefaults = new byte[] { 63, 40, 46, 0x8e, 38, 0, 25, 30, 0, 7, 0, 0, 0x20, 0x00, 0, 0, 0, 0 };
private byte[] RegDefaults = new byte[] { 63, 40, 46, 112, 38, 0, 25, 30, 0, 7, 0, 0, 48, 0, 192, 7, 0, 0 };
/// <summary>
/// Register masks
/// 0 = WRITE
/// 1 = READ
/// </summary>
private byte[] CPCMask = new byte[] { 255, 255, 255, 255, 127, 31, 127, 126, 3, 31, 31, 31, 63, 255, 63, 255, 63, 255 };
@ -408,16 +470,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
private int HCC;
/// <summary>
/// Vertical Character Count
/// </summary>
public int VCC;
/// <summary>
/// Vertical Scanline Count
/// </summary>
public int VLC;
/// <summary>
/// Internal cycle counter
/// </summary>
@ -452,11 +504,99 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#region Public Methods
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;
}
}
}
/// <summary>
/// Runs a CRCT clock cycle
/// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states
/// </summary>
public void ClockCycle()
public void ClockCycle2()
{
if (HSYNC)
{
@ -475,7 +615,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (HSYNC && HSYNCCounter == 1)
{
}
// move one horizontal character
@ -493,7 +633,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
else
{
DISPTMG = true;
var line = VCC;
var row = VLC;
var addrX = (LatchedRAMOffset * 2) + ((VCC * LatchedScreenWidthBytes) & 0x7ff) + ByteCounter;
@ -503,8 +643,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
//var addr = VideoPageBase + (line * (0x50)) + (row * 0x800) + (ByteCounter);
CurrentByteAddress = (ushort)(addrX + addrY);
ByteCounter += 2;
}
@ -515,7 +653,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// end of the current scanline
HCC = 0;
if (ChipType == CRCTType.UMC_UM6845R && VLC <= Regs[MAX_RASTER_ADDR])
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).
@ -548,7 +687,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// still doing extra scanlines
}
else
{
// finished doing extra scanlines
EndOfScreen = false;
@ -585,8 +723,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
VSYNC = true;
VSYNCCounter = 0;
VSYNC_Callbacks();
}
}
}
}
else
{
@ -598,6 +737,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
HSYNC = true;
HSYNCCounter = 0;
HSYNC_Callbacks();
lineCounter++;
LatchedRAMStartAddress = VideoPageBase;
LatchedRAMOffset = VideoRAMOffset;
@ -608,6 +749,95 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
}
/// <summary>
/// Runs a CRCT clock cycle
/// This should be called at 1Mhz / 1us / every 4 uncontended CPU t-states
/// </summary>
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;
/// <summary>
/// Resets the chip
/// </summary>
@ -624,6 +854,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// updates widths
UpdateWidths();
HSYNC = false;
VSYNC = false;
HSYNCCounter = 0;
VSYNCCounter = 0;
HCC = 0;
VCC = 0;
VLC = 0;
}
#endregion
@ -690,6 +930,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
}
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)
@ -809,26 +1061,26 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
switch (ChipType)
{
case CRCTType.Hitachi_HD6845S:
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.UMC_UM6845R:
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.Motorola_MC6845:
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.Amstrad_AMS40489:
case CRCTType.Amstrad_40226:
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;
@ -960,11 +1212,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public enum CRCTType
{
Hitachi_HD6845S = 0,
UMC_UM6845R = 1,
Motorola_MC6845 = 2,
Amstrad_AMS40489 = 3,
Amstrad_40226 = 4
HD6845S = 0,
UM6845 = 0,
UM6845R = 1,
MC6845 = 2,
AMS40489 = 3,
AMS40226 = 4
}
#endregion

View File

@ -28,12 +28,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
_machine = machine;
CurrentLine = new ScanLine(this);
CRCT.AttachHSYNCCallback(OnHSYNC);
CRCT.AttachVSYNCCallback(OnVSYNC);
}
#endregion
#region Palettes
/// <summary>
/// The standard CPC Pallete (ordered by firmware #)
/// http://www.cpcwiki.eu/index.php/CPC_Palette
@ -108,6 +111,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
Colors.ARGB(0x80, 0x80, 0x00), // Yellow
Colors.ARGB(0x80, 0x80, 0xFF), // Pastel Blue
};
#endregion
@ -154,6 +158,22 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ScreenBuffer = new int[BufferWidth * BufferHeight];
}
/// <summary>
/// Fired when the CRCT flags HSYNC
/// </summary>
public void OnHSYNC()
{
}
/// <summary>
/// Fired when the CRCT flags VSYNC
/// </summary>
public void OnVSYNC()
{
}
#endregion
#region IVideoProvider
@ -370,11 +390,37 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
// 4 bits per pixel - 2 bytes - 4 pixels (8 CRT pixels)
// RECT
case 0:
Characters[charIndex].Pixels = new int[8];
Characters[charIndex].Pixels = new int[16];
int m0Count = 0;
int m0B0P0i = vid1 & 170;
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));
@ -393,6 +439,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
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)
@ -500,6 +547,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
switch (ScreenMode)
{
case 0:
hScale = 1;
vScale = 2;
break;
case 1:
case 3:
hScale = 2;

View File

@ -16,15 +16,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public CPC464(AmstradCPC cpc, Z80A cpu, List<byte[]> files, bool autoTape)
public CPC464(AmstradCPC cpc, Z80A cpu, List<byte[]> files, bool autoTape, AmstradCPC.BorderType borderType)
{
CPC = cpc;
CPU = cpu;
FrameLength = 79872;
CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this);
CRT = new CRTDevice(this);
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);

View File

@ -16,15 +16,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
/// <param name="spectrum"></param>
/// <param name="cpu"></param>
public CPC6128(AmstradCPC cpc, Z80A cpu, List<byte[]> files, bool autoTape)
public CPC6128(AmstradCPC cpc, Z80A cpu, List<byte[]> files, bool autoTape, AmstradCPC.BorderType borderType)
{
CPC = cpc;
CPU = cpu;
FrameLength = 79872;
CRCT = new CRCT_6845(CRCT_6845.CRCTType.Motorola_MC6845, this);
CRT = new CRTDevice(this);
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);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -95,7 +96,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
diskMediaIndex = result;
// fire osd message
//Spectrum.OSD_DiskInserted();
CPC.OSD_DiskInserted();
LoadDiskMedia();
}
@ -132,6 +133,63 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
diskImages.Add(m);
CPC._diskInfo.Add(CPC._gameInfo[cnt]);
break;
case CPCMediaType.DiskDoubleSided:
// this is a bit tricky. we will attempt to parse the double sided disk image byte array,
// then output two separate image byte arrays
List<byte[]> working = new List<byte[]>();
foreach (DiskType type in Enum.GetValues(typeof(DiskType)))
{
bool found = false;
switch (type)
{
case DiskType.CPCExtended:
found = CPCExtendedFloppyDisk.SplitDoubleSided(m, working);
break;
case DiskType.CPC:
found = CPCFloppyDisk.SplitDoubleSided(m, working);
break;
}
if (found)
{
// add side 1
diskImages.Add(working[0]);
// add side 2
diskImages.Add(working[1]);
Common.GameInfo one = new Common.GameInfo();
Common.GameInfo two = new Common.GameInfo();
var gi = CPC._gameInfo[cnt];
for (int i = 0; i < 2; i++)
{
Common.GameInfo work = new Common.GameInfo();
if (i == 0)
{
work = one;
}
else if (i == 1)
{
work = two;
}
work.FirmwareHash = gi.FirmwareHash;
work.Hash = gi.Hash;
work.Name = gi.Name + " (Parsed Side " + (i + 1) + ")";
work.Region = gi.Region;
work.NotInDatabase = gi.NotInDatabase;
work.Status = gi.Status;
work.System = gi.System;
CPC._diskInfo.Add(work);
}
}
else
{
}
}
break;
}
cnt++;
@ -179,7 +237,12 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (hdr.ToUpper().Contains("EXTENDED CPC DSK") || hdr.ToUpper().Contains("MV - CPC"))
{
// amstrad .dsk disk file
return CPCMediaType.Disk;
// check for number of sides
var sides = data[0x31];
if (sides == 1)
return CPCMediaType.Disk;
else
return CPCMediaType.DiskDoubleSided;
}
// tape checking
@ -198,7 +261,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
None,
Tape,
Disk
Disk,
DiskDoubleSided
}
}

View File

@ -65,7 +65,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Renders pixels to the screen
/// </summary>
public CRTDevice CRT { get; set; }
//public CRTDevice CRT { get; set; }
/// <summary>
/// The PPI contoller chip
@ -140,6 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public virtual void ExecuteFrame(bool render, bool renderSound)
{
GateArray.FrameEnd = false;
CRCT.lineCounter = 0;
InputRead = false;
_render = render;
@ -157,8 +158,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
PollInput();
CRT.SetupVideo();
CRT.ScanlineCounter = 0;
//CRT.SetupVideo();
//CRT.ScanlineCounter = 0;
while (!GateArray.FrameEnd)
{
@ -168,9 +169,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
TapeDevice.TapeCycle();
}
//OverFlow = (int)CurrentFrameCycle - GateArray.FrameLength;
// we have reached the end of a frame
LastFrameStartCPUTick = CPU.TotalExecutedCycles; // - OverFlow;
@ -346,7 +344,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Sync("RAM64KBank", ref RAM64KBank);
CRCT.SyncState(ser);
CRT.SyncState(ser);
//CRT.SyncState(ser);
GateArray.SyncState(ser);
KeyboardDevice.SyncState(ser);
TapeBuzzer.SyncState(ser);

View File

@ -1,5 +1,7 @@
using System.Text;
using BizHawk.Common;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
@ -144,6 +146,88 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return true;
}
/// <summary>
/// Takes a double-sided disk byte array and converts into 2 single-sided arrays
/// </summary>
/// <param name="data"></param>
/// <param name="results"></param>
/// <returns></returns>
public static bool SplitDoubleSided(byte[] data, List<byte[]> results)
{
// look for standard magic string
string ident = Encoding.ASCII.GetString(data, 0, 16);
if (!ident.ToUpper().Contains("EXTENDED CPC DSK"))
{
// incorrect format
return false;
}
byte[] S0 = new byte[data.Length];
byte[] S1 = new byte[data.Length];
// disk info block
Array.Copy(data, 0, S0, 0, 0x100);
Array.Copy(data, 0, S1, 0, 0x100);
// change side number
S0[0x31] = 1;
S1[0x31] = 1;
// extended format has different track sizes
int[] trkSizes = new int[data[0x30] * data[0x31]];
int pos = 0x34;
for (int i = 0; i < data[0x30] * data[0x31]; i++)
{
trkSizes[i] = data[pos] * 256;
// clear destination trk sizes (will be added later)
S0[pos] = 0;
S1[pos] = 0;
pos++;
}
// start at track info blocks
int mPos = 0x100;
int s0Pos = 0x100;
int s0tCount = 0;
int s1tCount = 0;
int s1Pos = 0x100;
int tCount = 0;
while (tCount < data[0x30] * data[0x31])
{
// which side is this?
var side = data[mPos + 0x11];
if (side == 0)
{
// side 1
Array.Copy(data, mPos, S0, s0Pos, trkSizes[tCount]);
s0Pos += trkSizes[tCount];
// trk size table
S0[0x34 + s0tCount++] = (byte)(trkSizes[tCount] / 256);
}
else if (side == 1)
{
// side 2
Array.Copy(data, mPos, S1, s1Pos, trkSizes[tCount]);
s1Pos += trkSizes[tCount];
// trk size table
S1[0x34 + s1tCount++] = (byte)(trkSizes[tCount] / 256);
}
mPos += trkSizes[tCount++];
}
byte[] s0final = new byte[s0Pos];
byte[] s1final = new byte[s1Pos];
Array.Copy(S0, 0, s0final, 0, s0Pos);
Array.Copy(S1, 0, s1final, 0, s1Pos);
results.Add(s0final);
results.Add(s1final);
return true;
}
/// <summary>
/// State serlialization
/// </summary>

View File

@ -1,5 +1,7 @@
using System.Text;
using BizHawk.Common;
using System.Collections.Generic;
using System;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
@ -149,6 +151,70 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
return true;
}
/// <summary>
/// Takes a double-sided disk byte array and converts into 2 single-sided arrays
/// </summary>
/// <param name="data"></param>
/// <param name="results"></param>
/// <returns></returns>
public static bool SplitDoubleSided(byte[] data, List<byte[]> results)
{
// look for standard magic string
string ident = Encoding.ASCII.GetString(data, 0, 16);
if (!ident.ToUpper().Contains("MV - CPC"))
{
// incorrect format
return false;
}
byte[] S0 = new byte[data.Length];
byte[] S1 = new byte[data.Length];
// disk info block
Array.Copy(data, 0, S0, 0, 0x100);
Array.Copy(data, 0, S1, 0, 0x100);
// change side number
S0[0x31] = 1;
S1[0x31] = 1;
int trkSize = MediaConverter.GetWordValue(data, 0x32);
// start at track info blocks
int mPos = 0x100;
int s0Pos = 0x100;
int s1Pos = 0x100;
while (mPos < trkSize * data[0x30] * data[0x31])
{
// which side is this?
var side = data[mPos + 0x11];
if (side == 0)
{
// side 1
Array.Copy(data, mPos, S0, s0Pos, trkSize);
s0Pos += trkSize;
}
else if (side == 1)
{
// side 2
Array.Copy(data, mPos, S1, s1Pos, trkSize);
s1Pos += trkSize;
}
mPos += trkSize;
}
byte[] s0final = new byte[s0Pos];
byte[] s1final = new byte[s1Pos];
Array.Copy(S0, 0, s0final, 0, s0Pos);
Array.Copy(S1, 0, s1final, 0, s1Pos);
results.Add(s0final);
results.Add(s1final);
return true;
}
/// <summary>
/// State serlialization
/// </summary>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
/// <summary>
/// This is called first when importing disk images
/// Disk images can be single or double-sided, so we need to handle that
/// </summary>
public class DiskHandler
{
}
}