CPCHawk: Port IO changes, Interrupt generation, i8255 PPI chip, PSG IO & Keyboard/Joysticks

This commit is contained in:
Asnivor 2018-07-18 08:12:44 +01:00
parent 4192f764b1
commit c0fcac5ab1
19 changed files with 784 additions and 937 deletions

View File

@ -154,9 +154,7 @@
<Compile Include="Computers\AmstradCPC\Hardware\Input\StandardKeyboard.cs" /> <Compile Include="Computers\AmstradCPC\Hardware\Input\StandardKeyboard.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\AY38912.cs" /> <Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\AY38912.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\Beeper.cs" /> <Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\Beeper.cs" />
<Compile Include="Computers\AmstradCPC\Hardware\SoundOutput\PSG.cs" />
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.cs" /> <Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.cs" />
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.GateArray.cs" />
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Memory.cs" /> <Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Memory.cs" />
<Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Port.cs" /> <Compile Include="Computers\AmstradCPC\Machine\CPC464\CPC464.Port.cs" />
<Compile Include="Computers\AmstradCPC\Machine\CPCBase.cs" /> <Compile Include="Computers\AmstradCPC\Machine\CPCBase.cs" />

View File

@ -43,19 +43,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
definition.BoolButtons.Add(s); definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "J2"; definition.CategoryLabels[s] = "J2";
} }
/*
List<string> joys3 = new List<string>
{
// P3 Joystick
"P3 Up", "P3 Down", "P3 Left", "P3 Right", "P3 Button",
};
foreach (var s in joys3)
{
definition.BoolButtons.Add(s);
definition.CategoryLabels[s] = "J3 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType3.ToString() + ")";
}
*/
// keyboard // keyboard
List<string> keys = new List<string> List<string> keys = new List<string>

View File

@ -15,6 +15,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <param name="tStatesPerFrame"></param> /// <param name="tStatesPerFrame"></param>
void Init(int sampleRate, int tStatesPerFrame); void Init(int sampleRate, int tStatesPerFrame);
void SetFunction(int data);
/// <summary> /// <summary>
/// Activates a register /// Activates a register
/// </summary> /// </summary>

View File

@ -41,6 +41,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#region State Information #region State Information
/// <summary>
/// Signs whether the tape motor is running
/// </summary>
public bool TapeMotor;
/// <summary> /// <summary>
/// Internal counter used to trigger tape buzzer output /// Internal counter used to trigger tape buzzer output
/// </summary> /// </summary>
@ -970,6 +975,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Sync("_monitorTimeOut", ref _monitorTimeOut); ser.Sync("_monitorTimeOut", ref _monitorTimeOut);
ser.Sync("_monitorLastPC", ref _monitorLastPC); ser.Sync("_monitorLastPC", ref _monitorLastPC);
ser.Sync("_monitorLastRegs", ref _monitorLastRegs, false); ser.Sync("_monitorLastRegs", ref _monitorLastRegs, false);
ser.Sync("TapeMotor", ref TapeMotor);
ser.EndSection(); ser.EndSection();
} }

View File

@ -652,7 +652,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{ {
case 0: case 0:
CRCT.ClockCycle(); CRCT.ClockCycle();
//psg clockcycle //PSG.ClockCycle();
WaitLine = false; WaitLine = false;
break; break;
case 1: case 1:
@ -675,9 +675,19 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
break; break;
} }
if (!HSYNC && CRCT.HSYNC)
{
HSYNC = true;
}
// run the interrupt generator routine // run the interrupt generator routine
InterruptGenerator(); InterruptGenerator();
if (!CRCT.HSYNC)
{
HSYNC = false;
}
// conditional CPU cycle // conditional CPU cycle
DoConditionalCPUCycle(); DoConditionalCPUCycle();
@ -719,11 +729,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port)); BitArray portBits = new BitArray(BitConverter.GetBytes(port));
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result)); 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;
// The gate array responds to port 0x7F
bool accessed = !portBits[15];
if (!accessed) if (!accessed)
return false; return accessed;
// Bit 9 and 8 of the data byte define the function to access // Bit 9 and 8 of the data byte define the function to access
if (!dataBits[6] && !dataBits[7]) if (!dataBits[6] && !dataBits[7])

View File

@ -800,6 +800,13 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#region PortIODevice #region PortIODevice
/*
#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> /// <summary>
/// Device responds to an IN instruction /// Device responds to an IN instruction
/// </summary> /// </summary>
@ -808,24 +815,30 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public bool ReadPort(ushort port, ref int result) public bool ReadPort(ushort port, ref int result)
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port)); 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" // The 6845 is selected when bit 14 of the I/O port address is set to "0"
bool accessed = !portBits[14]; if (portUpper.Bit(6))
if (!accessed) return accessed;
return false;
// Bit 9 and 8 of the I/O port address define the function to access // Bit 9 and 8 of the I/O port address define the function to access
if (portBits[8] == false && portBits[9] == true) if (portUpper.Bit(1) && !portUpper.Bit(0))
{ {
// read status register // read status register
accessed = ReadStatus(ref result); accessed = ReadStatus(ref result);
} }
else if (portBits[8] == true && portBits[9] == true) else if ((portUpper & 3) == 3)
{ {
// read data register // read data register
accessed = ReadRegister(ref result); accessed = ReadRegister(ref result);
} }
else
{
result = 0;
}
return accessed; return accessed;
} }
@ -838,26 +851,31 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public bool WritePort(ushort port, int result) public bool WritePort(ushort port, int result)
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port)); 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" // The 6845 is selected when bit 14 of the I/O port address is set to "0"
bool accessed = !portBits[14]; if (portUpper.Bit(6))
if (!accessed) return accessed;
return false;
// Bit 9 and 8 of the I/O port address define the function to access var func = portUpper & 3;
if (portBits[8] == false && portBits[9] == false)
switch (func)
{ {
// Select 6845 register // reg select
RegisterSelect(result); case 0:
} RegisterSelect(result);
else if (portBits[8] == true && portBits[9] == false) break;
{
// Write 6845 register data // data write
WriteRegister(result); case 1:
WriteRegister(result);
break;
} }
return true; return accessed;
} }
#endregion #endregion
@ -884,53 +902,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
ser.Sync("VSYNCWidth", ref VSYNCWidth); ser.Sync("VSYNCWidth", ref VSYNCWidth);
ser.Sync("VSYNCCounter", ref VSYNCCounter); ser.Sync("VSYNCCounter", ref VSYNCCounter);
ser.EndSection(); ser.EndSection();
/*
* /// <summary>
/// Horizontal Character Count
/// </summary>
private int ;
/// <summary>
/// Vertical Character Count
/// </summary>
private int ;
/// <summary>
/// Vertical Scanline Count
/// </summary>
private int ;
/// <summary>
/// Internal cycle counter
/// </summary>
private int ;
/// <summary>
/// Signs that we have finished the last character row
/// </summary>
private bool ;
/// <summary>
/// HSYNC pulse width (in characters)
/// </summary>
private int ;
/// <summary>
/// Internal HSYNC counter
/// </summary>
private int ;
/// <summary>
/// VSYNC pulse width (in characters)
/// </summary>
private int ;
/// <summary>
/// Internal VSYNC counter
/// </summary>
private int ;
* */
} }
#endregion #endregion

View File

@ -284,6 +284,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <param name="phase"></param> /// <param name="phase"></param>
public void AddScanlineCharacter(int index, RenderPhase phase, byte vid1, byte vid2, int[] pens) public void AddScanlineCharacter(int index, RenderPhase phase, byte vid1, byte vid2, int[] pens)
{ {
if (index >= 64)
{
return;
}
switch (phase) switch (phase)
{ {
case RenderPhase.BORDER: case RenderPhase.BORDER:

View File

@ -17,7 +17,18 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public int CurrentLine public int CurrentLine
{ {
get { return _currentLine; } get { return _currentLine; }
set { _currentLine = value; } set
{
// bits 0-3 contain the line
var line = value & 0x0f;
if (line > 0)
{
}
_currentLine = line;
}
} }
private bool[] _keyStatus; private bool[] _keyStatus;
@ -44,6 +55,8 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public StandardKeyboard(CPCBase machine) public StandardKeyboard(CPCBase machine)
{ {
_machine = machine; _machine = machine;
//_machine.AYDevice.PortA_IN_CallBack = INCallback;
//_machine.AYDevice.PortA_OUT_CallBack = OUTCallback;
// scancode rows, ascending (Bit0 - Bit7) // scancode rows, ascending (Bit0 - Bit7)
KeyboardMatrix = new string[] KeyboardMatrix = new string[]
@ -92,13 +105,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public byte ReadCurrentLine() public byte ReadCurrentLine()
{ {
var lin = _currentLine - 0x40; var lin = _currentLine; // - 0x40;
var pos = lin * 8; var pos = lin * 8;
var l = KeyStatus.Skip(pos).Take(8).Reverse().ToArray(); var l = KeyStatus.Skip(pos).Take(8).ToArray();
BitArray bi = new BitArray(l); BitArray bi = new BitArray(l);
byte[] bytes = new byte[1]; byte[] bytes = new byte[1];
bi.CopyTo(bytes, 0); bi.CopyTo(bytes, 0);
return bytes[0]; byte inv = (byte)(~bytes[0]);
return inv;
} }
/// <summary> /// <summary>

View File

@ -23,44 +23,6 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#endregion #endregion
#region State
private State PortA = new State("PortA");
private State PortB = new State("PortB");
private State PortCU = new State("PortCU");
private State PortCL = new State("PortCL");
private class State
{
public string Ident;
public int Data;
public bool Input;
public int OpMode;
public void Reset()
{
OpMode = 0;
Input = true;
Data = 255;
}
public State(string ident)
{
Ident = ident;
}
public void SyncState(Serializer ser)
{
ser.BeginSection("PPI_" + Ident);
ser.Sync("Data", ref Data);
ser.Sync("Input", ref Input);
ser.Sync("OpMode", ref OpMode);
ser.EndSection();
}
}
#endregion
#region Construction #region Construction
public PPI_8255(CPCBase machine) public PPI_8255(CPCBase machine)
@ -71,83 +33,202 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
#endregion #endregion
#region Reset #region Implementation
public void Reset()
{
PortA.Reset();
PortB.Reset();
PortCL.Reset();
PortCU.Reset();
}
#endregion
#region PORT A
/*
I/O Mode 0,
For writing data to PSG all bits must be set to output,
for reading data from PSG all bits must be set to input (thereafter, output direction should be restored, for compatibility with the BIOS).
Bit Description Usage
7-0 PSG.DATA PSG Databus (Sound/Keyboard/Joystick)
*/
/// <summary> /// <summary>
/// Reads from Port A /// BDIR Line connected to PSG
/// </summary> /// </summary>
/// <returns></returns> public bool BDIR
private int INPortA()
{ {
if (PortA.Input) get { return Regs[PORT_C].Bit(7); }
{
// read from AY
return PSG.PortRead();
}
else
{
// return stored port data
return PortA.Data;
}
} }
/// <summary>
/// BC1 Line connected to PSG
/// </summary>
public bool BC1
{
get { return Regs[PORT_C].Bit(6); }
}
/* Port Constants */
private const int PORT_A = 0;
private const int PORT_B = 1;
private const int PORT_C = 2;
private const int PORT_CONTROL = 3;
/// <summary>
/// The i8255 internal data registers
/// </summary>
private byte[] Regs = new byte[4];
/// <summary>
/// Returns the currently latched port direction for Port A
/// </summary>
private PortDirection DirPortA
{
get { return Regs[PORT_CONTROL].Bit(4) ? PortDirection.Input : PortDirection.Output; }
}
/// <summary>
/// Returns the currently latched port direction for Port B
/// </summary>
private PortDirection DirPortB
{
get { return Regs[PORT_CONTROL].Bit(1) ? PortDirection.Input : PortDirection.Output; }
}
/// <summary>
/// Returns the currently latched port direction for Port C (lower half)
/// </summary>
private PortDirection DirPortCL
{
get { return Regs[PORT_CONTROL].Bit(0) ? PortDirection.Input : PortDirection.Output; }
}
/// <summary>
/// Returns the currently latched port direction for Port C (upper half)
/// </summary>
private PortDirection DirPortCU
{
get { return Regs[PORT_CONTROL].Bit(3) ? PortDirection.Input : PortDirection.Output; }
}
#region OUT Methods
/// <summary> /// <summary>
/// Writes to Port A /// Writes to Port A
/// </summary> /// </summary>
private void OUTPortA(int data) private void OUTPortA(int data)
{ {
PortA.Data = data; // latch the data
Regs[PORT_A] = (byte)data;
if (!PortA.Input) if (DirPortA == PortDirection.Output)
{ {
// write to AY // PSG write
PSG.PortWrite(data); PSG.PortWrite(data);
} }
} }
/// <summary>
/// Writes to Port B
/// </summary>
private void OUTPortB(int data)
{
// PortB is read only
// just latch the data
Regs[PORT_B] = (byte)data;
}
/// <summary>
/// Writes to Port C
/// </summary>
private void OUTPortC(int data)
{
// latch the data
Regs[PORT_C] = (byte)data;
if (DirPortCL == PortDirection.Output)
{
// lower Port C bits OUT
// keyboard line update
Keyboard.CurrentLine = Regs[PORT_C] & 0x0f;
}
if (DirPortCU == PortDirection.Output)
{
// upper Port C bits OUT
// write to PSG using latched data
PSG.SetFunction(data);
PSG.PortWrite(Regs[PORT_A]);
// cassete write data
//not implemeted
// cas motor control
Tape.TapeMotor = Regs[PORT_C].Bit(4);
}
}
/// <summary>
/// Writes to the control register
/// </summary>
/// <param name="data"></param>
private void OUTControl(int data)
{
if (data.Bit(7))
{
// update configuration
Regs[PORT_CONTROL] = (byte)data;
// Writing to PIO Control Register (with Bit7 set), automatically resets PIO Ports A,B,C to 00h each
Regs[PORT_A] = 0;
Regs[PORT_B] = 0;
Regs[PORT_C] = 0;
}
else
{
// register is used to set/reset a single bit in Port C
bool isSet = data.Bit(0);
// get the bit in PortC that we wish to change
var bit = (data >> 1) & 7;
// modify this bit
if (isSet)
{
Regs[PORT_C] = (byte)(Regs[PORT_C] | (bit * bit));
}
else
{
Regs[PORT_C] = (byte)(Regs[PORT_C] & ~(bit * bit));
}
// any other ouput business
if (DirPortCL == PortDirection.Output)
{
// update keyboard line
Keyboard.CurrentLine = Regs[PORT_C] & 0x0f;
}
if (DirPortCU == PortDirection.Output)
{
// write to PSG using latched data
PSG.SetFunction(data);
PSG.PortWrite(Regs[PORT_A]);
// cassete write data
//not implemeted
// cas motor control
Tape.TapeMotor = Regs[PORT_C].Bit(4);
}
}
}
#endregion #endregion
#region Port B #region IN Methods
/*
I/O Mode 0,
Input
Bit Description Usage in CPC Usage in KC Compact /// <summary>
7 CAS.IN Cassette data input Same as on CPC /// Reads from Port A
6 PRN.BUSY Parallel/Printer port ready signal, "1" = not ready, "0" = Ready Same as on CPC /// </summary>
5 /EXP Expansion Port /EXP pin Same as on CPC /// <returns></returns>
4 LK4 Screen Refresh Rate ("1"=50Hz, "0"=60Hz) Set to "1"=50Hz (but ignored by the KC BIOS, which always uses 50Hz even if LK4 is changed) private int INPortA()
3 LK3 3bit Distributor ID. Usually set to 4=Awa, 5=Schneider, or 7=Amstrad, see LK-selectable Brand Names for details. Purpose unknown (set to "1") {
2 LK2 Purpose unknown (set to "0") if (DirPortA == PortDirection.Input)
1 LK1 Expansion Port /TEST pin {
0 CRTC VSYNC Vertical Sync ("1"=VSYNC active, "0"=VSYNC inactive) Same as on CPC // read from PSG
return PSG.PortRead();
LK1-4 are links on the mainboard ("0" bits are wired to GND). On CPC464,CPC664,CPC6128 and GX4000 they are labeled LK1-LK4, on the CPC464+ and CPC6128+ they are labeled LK101-LK103 }
(and LK104, presumably?). else
Bit5 (/EXP) can be used by a expansion device to report its presence. "1" = device connected, "0" = device not connected. {
This is not always used by all expansion devices. is it used by any expansions? [in the DDI-1 disc interface, /EXP connects to the ROM bank selection, bank 0 or bank 7] // Port A is set to output
If port B is programmed as an output, you can make a fake vsync visible to the Gate-Array by writing 1 to bit 0. You can then turn it off by writing 0 to bit 0. // return latched value
It is fake in the sense that it is not generated by the CRTC as it normally is. This fake vsync doesn't work on all CPCs. It is not known if it is dependent on CRTC or 8255 or both. return Regs[PORT_A];
*/ }
}
/// <summary> /// <summary>
/// Reads from Port B /// Reads from Port B
@ -155,8 +236,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
private int INPortB() private int INPortB()
{ {
if (PortB.Input) if (DirPortB == PortDirection.Input)
{ {
// build the PortB output
// start with every bit reset // start with every bit reset
BitArray rBits = new BitArray(8); BitArray rBits = new BitArray(8);
@ -189,194 +271,81 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
} }
else else
{ {
return PortB.Data; // return the latched value
return Regs[PORT_B];
} }
} }
/// <summary>
/// Writes to Port B
/// </summary>
private void OUTPortB(int data)
{
// just store the value
PortB.Data = data;
}
#endregion
#region Port C
/*
upper: I/O Mode 0, lower: I/O mode 0,
upper: output, lower: output
Bit Description Usage
7 PSG BDIR PSG function selection
6 PSG BC1
5 Cassette Write data Cassette Out (sometimes also used as Printer Bit7, see 8bit Printer Ports)
4 Cassette Motor Control set bit to "1" for motor on, or "0" for motor off
0-3 Keyboard line Select keyboard line to be scanned (0-15)
PSG function selection:
Bit 7 Bit 6 Function
0 0 Inactive
0 1 Read from selected PSG register
1 0 Write to selected PSG register
1 1 Select PSG register
*/
/// <summary> /// <summary>
/// Reads from Port C /// Reads from Port C
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private int INPortC() private int INPortC()
{ {
var val = PortCU.Data; // get the PortC value
int val = Regs[PORT_C];
if (PortCU.Input) if (DirPortCU == PortDirection.Input)
val |= 0xf0; {
// upper port C bits
// remove upper half
val &= 0x0f;
if (PortCL.Input) // isolate control bits
var v = Regs[PORT_C] & 0xc0;
if (v == 0xc0)
{
// set reg is present. change to write reg
v = 0x80;
}
// cas wr is always set
val |= v | 0x20;
if (Tape.TapeMotor)
{
val |= 0x10;
}
}
if (DirPortCL == PortDirection.Input)
{
// lower port C bits
val |= 0x0f; val |= 0x0f;
}
return val; return val;
} }
/// <summary>
/// Writes to Port C
/// </summary>
private void OUTPortC(int data)
{
PortCL.Data = data;
PortCU.Data = data;
if (!PortCU.Input)
{
// ay register set and write
PSG.SelectedRegister = data;
PSG.PortWrite(data);
// cassette motor control
byte val = (byte)data;
var motor = val.Bit(4);
}
if (!PortCL.Input)
{
// which keyboard row to scan
Keyboard.CurrentLine = PortCL.Data & 0x0f;
}
}
#endregion #endregion
#region PPI Control #endregion
/*
This register has two different functions depending on bit7 of the data written to this register.
PPI Control with Bit7=1
If Bit 7 is "1" then the other bits will initialize Port A-B as Input or Output: #region Reset
Bit 0 IO-Cl Direction for Port C, lower bits (always 0=Output in CPC) public void Reset()
Bit 1 IO-B Direction for Port B (always 1=Input in CPC)
Bit 2 MS0 Mode for Port B and Port Cl (always zero in CPC)
Bit 3 IO-Ch Direction for Port C, upper bits (always 0=Output in CPC)
Bit 4 IO-A Direction for Port A (0=Output, 1=Input)
Bit 5,6 MS0,MS1 Mode for Port A and Port Ch (always zero in CPC)
Bit 7 SF Must be "1" to setup the above bits
CAUTION: Writing to PIO Control Register (with Bit7 set), automatically resets PIO Ports A,B,C to 00h each!
In the CPC only Bit 4 is of interest, all other bits are always having the same value. In order to write to the PSG sound registers, a value of 82h must
be written to this register. In order to read from the keyboard (through PSG register 0Eh), a value of 92h must be written to this register.
PPI Control with Bit7=0
Otherwise, if Bit 7 is "0" then the register is used to set or clear a single bit in Port C:
Bit 0 B New value for the specified bit (0=Clear, 1=Set)
Bit 1-3 N0,N1,N2 Specifies the number of a bit (0-7) in Port C
Bit 4-6 - Not Used
Bit 7 SF Must be "0" in this case
*/
/// <summary>
/// Deals with bytes written to the control
/// </summary>
/// <param name="data"></param>
private void ControlHandler(int data)
{ {
byte val = (byte)data; for (int i = 0; i < 3; i++)
// Bit7 = 1
if (val.Bit(7))
{ {
// Bit0 - Direction for Port C, lower bits Regs[i] = 0xff;
PortCL.Input = val.Bit(0);
// Bit1 - Direction for Port B
PortB.Input = val.Bit(1);
// Bit2 - Mode for Port B and Port Cl (CPC always 0)
PortB.OpMode = 0;
PortCL.OpMode = 0;
// Bit3 - Direction for Port C, upper bits
PortCU.Input = val.Bit(3);
// Bit4 - Direction for Port A
PortA.Input = val.Bit(4);
// Bits 5,6 - Mode for Port A and Port Ch (CPC always 0)
PortA.OpMode = 0;
PortCU.OpMode = 0;
// reset ports
PortA.Data = 0x00;
PortB.Data = 0x00;
PortCL.Data = 0x00;
PortCU.Data = 0x00;
} }
// Bit7 = 0
else
{
// Bit0 - New value for the specified bit (0=Clear, 1=Set)
var newBit = val.Bit(0);
// Bits 1-3 - Specifies the number of a bit (0-7) in Port C Regs[3] = 0xff;
var bit = (data >> 1) & 7;
if (newBit)
{
// set the bit
PortCL.Data |= ~(1 << bit);
PortCU.Data |= ~(1 << bit);
}
else
{
// reset the bit
PortCL.Data &= ~(1 << bit);
PortCU.Data &= ~(1 << bit);
}
if (!PortCL.Input)
{
// keyboard set row
}
if (!PortCU.Input)
{
// ay register set and write
PSG.SelectedRegister = val;
PSG.PortWrite(data);
}
}
} }
#endregion #endregion
#region IPortIODevice #region IPortIODevice
/*
#F4XX %xxxx0x00 xxxxxxxx 8255 PIO Port A (PSG Data) Read Write
#F5XX %xxxx0x01 xxxxxxxx 8255 PIO Port B (Vsync,PrnBusy,Tape,etc.) Read -
#F6XX %xxxx0x10 xxxxxxxx 8255 PIO Port C (KeybRow,Tape,PSG Control) - Write
#F7XX %xxxx0x11 xxxxxxxx 8255 PIO Control-Register - Write
*/
/// <summary> /// <summary>
/// Device responds to an IN instruction /// Device responds to an IN instruction
/// </summary> /// </summary>
@ -385,39 +354,40 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public bool ReadPort(ushort port, ref int result) public bool ReadPort(ushort port, ref int result)
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port)); byte portUpper = (byte)(port >> 8);
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result)); byte portLower = (byte)(port & 0xff);
// The 8255 responds to bit 11 reset // The 8255 responds to bit 11 reset with A10 and A12-A15 set
bool accessed = !portBits[11]; //if (portUpper.Bit(3))
if (!accessed) //return false;
return false;
if (!portBits[8] && !portBits[9]) var PPIFunc = (port & 0x0300) >> 8; // portUpper & 3;
switch (PPIFunc)
{ {
// Port A Data // Port A Read
// PSG (Sound/Keyboard/Joystick) case 0:
result = INPortA();
} // PSG (Sound/Keyboard/Joystick)
result = INPortA();
if (portBits[8] && !portBits[9]) break;
{
// Port B Data
// Vsync/Jumpers/PrinterBusy/CasIn/Exp
result = INPortB();
}
if (!portBits[8] && portBits[9]) // Port B Read
{ case 1:
// Port C Data
// KeybRow/CasOut/PSG
result = INPortC();
}
if (portBits[8] && portBits[9]) // Vsync/Jumpers/PrinterBusy/CasIn/Exp
{ result = INPortB();
// Control
return false; break;
// Port C Read (docs define this as write-only but we do need to do some processing)
case 2:
// KeybRow/CasOut/PSG
result = INPortC();
break;
} }
return true; return true;
@ -431,39 +401,48 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public bool WritePort(ushort port, int result) public bool WritePort(ushort port, int result)
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port)); byte portUpper = (byte)(port >> 8);
BitArray dataBits = new BitArray(BitConverter.GetBytes((byte)result)); byte portLower = (byte)(port & 0xff);
// The 8255 responds to bit 11 reset // The 8255 responds to bit 11 reset with A10 and A12-A15 set
bool accessed = !portBits[11]; if (portUpper.Bit(3))
if (!accessed)
return false; return false;
if (!portBits[8] && !portBits[9]) var PPIFunc = portUpper & 3;
{
// Port A Data
// PSG (Sound/Keyboard/Joystick)
OUTPortA(result);
}
if (portBits[8] && !portBits[9]) switch (PPIFunc)
{ {
// Port B Data // Port A Write
// Vsync/Jumpers/PrinterBusy/CasIn/Exp case 0:
OUTPortB(result);
}
if (!portBits[8] && portBits[9]) // PSG (Sound/Keyboard/Joystick)
{ OUTPortA(result);
// Port C Data
// KeybRow/CasOut/PSG
OUTPortC(result);
}
if (portBits[8] && portBits[9]) break;
{
// Control // Port B Write
ControlHandler(result); case 1:
// Vsync/Jumpers/PrinterBusy/CasIn/Exp
OUTPortB(result);
break;
// Port C Write
case 2:
// KeybRow/CasOut/PSG
OUTPortC(result);
break;
// Control Register Write
case 3:
// Control
OUTControl((byte)result);
break;
} }
return true; return true;
@ -476,13 +455,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
public void SyncState(Serializer ser) public void SyncState(Serializer ser)
{ {
ser.BeginSection("PPI"); ser.BeginSection("PPI");
PortA.SyncState(ser); ser.Sync("Regs", ref Regs, false);
PortB.SyncState(ser);
PortCU.SyncState(ser);
PortCL.SyncState(ser);
ser.EndSection(); ser.EndSection();
} }
#endregion #endregion
}
public enum PortDirection
{
Input,
Output
} }
} }

View File

@ -201,20 +201,45 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
*/ */
} }
/// <summary>
/// 0: Inactive
/// 1: Read Register
/// 2: Write Register
/// 3: Select Register
/// </summary>
public int ActiveFunction;
public void SetFunction(int val)
{
int b = ((val & 0xc0) >> 6);
ActiveFunction = b;
}
/// <summary> /// <summary>
/// Reads the value from the currently selected register /// Reads the value from the currently selected register
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public int PortRead() public int PortRead()
{ {
if (_activeRegister == 14) if (ActiveFunction == 1)
{ {
// exteral keyboard register if (_activeRegister == 14)
return _keyboard.ReadCurrentLine(); {
} if (PortAInput)
{
// exteral keyboard register
return _keyboard.ReadCurrentLine();
}
else
{
return _keyboard.ReadCurrentLine() & _registers[_activeRegister];
}
}
if (_activeRegister < 16) if (_activeRegister < 16)
return _registers[_activeRegister]; return _registers[_activeRegister];
}
return 0; return 0;
} }
@ -225,102 +250,127 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <param name="value"></param> /// <param name="value"></param>
public void PortWrite(int value) public void PortWrite(int value)
{ {
if (_activeRegister == 14) switch (ActiveFunction)
{ {
// external keyboard register default:
return;
}
if (_activeRegister >= 0x10)
return;
byte val = (byte)value;
if (((1 << _activeRegister) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13))) != 0)
val &= 0x0F;
if (((1 << _activeRegister) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10))) != 0)
val &= 0x1F;
if (_activeRegister != 13 && _registers[_activeRegister] == val)
return;
_registers[_activeRegister] = val;
switch (_activeRegister)
{
// Channel A (Combined Pitch)
// (not written to directly)
case 0:
case 1:
_dividerA = _registers[AY_A_FINE] | (_registers[AY_A_COARSE] << 8);
break; break;
// Channel B (Combined Pitch) // select reg
// (not written to directly)
case 2:
case 3: case 3:
_dividerB = _registers[AY_B_FINE] | (_registers[AY_B_COARSE] << 8);
break;
// Channel C (Combined Pitch)
// (not written to directly)
case 4:
case 5:
_dividerC = _registers[AY_C_FINE] | (_registers[AY_C_COARSE] << 8);
break;
// Noise Pitch
case 6:
_dividerN = val * 2;
break;
// Mixer
case 7:
_bit0 = 0 - ((val >> 0) & 1);
_bit1 = 0 - ((val >> 1) & 1);
_bit2 = 0 - ((val >> 2) & 1);
_bit3 = 0 - ((val >> 3) & 1);
_bit4 = 0 - ((val >> 4) & 1);
_bit5 = 0 - ((val >> 5) & 1);
break;
// Channel Volumes
case 8:
_eMaskA = (val & 0x10) != 0 ? -1 : 0;
_vA = ((val & 0x0F) * 2 + 1) & ~_eMaskA;
break;
case 9:
_eMaskB = (val & 0x10) != 0 ? -1 : 0;
_vB = ((val & 0x0F) * 2 + 1) & ~_eMaskB;
break;
case 10:
_eMaskC = (val & 0x10) != 0 ? -1 : 0;
_vC = ((val & 0x0F) * 2 + 1) & ~_eMaskC;
break;
// Envelope (Combined Duration)
// (not written to directly)
case 11:
case 12:
_dividerE = _registers[AY_E_FINE] | (_registers[AY_E_COARSE] << 8);
break;
// Envelope Shape
case 13:
// reset the envelope counter
_countE = 0;
if ((_registers[AY_E_SHAPE] & 4) != 0) int b = (value & 0x0f);
{ SelectedRegister = b;
// attack
_eState = 0;
_eDirection = 1;
}
else
{
// decay
_eState = 31;
_eDirection = -1;
}
break; break;
case 14:
// IO Port - not implemented // write reg
case 2:
if (_activeRegister == 14)
{
// external keyboard register
//return;
}
if (_activeRegister >= 0x10)
return;
byte val = (byte)value;
if (((1 << _activeRegister) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13))) != 0)
val &= 0x0F;
if (((1 << _activeRegister) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10))) != 0)
val &= 0x1F;
if (_activeRegister != 13 && _registers[_activeRegister] == val)
return;
_registers[_activeRegister] = val;
switch (_activeRegister)
{
// Channel A (Combined Pitch)
// (not written to directly)
case 0:
case 1:
_dividerA = _registers[AY_A_FINE] | (_registers[AY_A_COARSE] << 8);
break;
// Channel B (Combined Pitch)
// (not written to directly)
case 2:
case 3:
_dividerB = _registers[AY_B_FINE] | (_registers[AY_B_COARSE] << 8);
break;
// Channel C (Combined Pitch)
// (not written to directly)
case 4:
case 5:
_dividerC = _registers[AY_C_FINE] | (_registers[AY_C_COARSE] << 8);
break;
// Noise Pitch
case 6:
_dividerN = val * 2;
break;
// Mixer
case 7:
_bit0 = 0 - ((val >> 0) & 1);
_bit1 = 0 - ((val >> 1) & 1);
_bit2 = 0 - ((val >> 2) & 1);
_bit3 = 0 - ((val >> 3) & 1);
_bit4 = 0 - ((val >> 4) & 1);
_bit5 = 0 - ((val >> 5) & 1);
PortAInput = ((value & 0x40) == 0);
PortBInput = ((value & 0x80) == 0);
break;
// Channel Volumes
case 8:
_eMaskA = (val & 0x10) != 0 ? -1 : 0;
_vA = ((val & 0x0F) * 2 + 1) & ~_eMaskA;
break;
case 9:
_eMaskB = (val & 0x10) != 0 ? -1 : 0;
_vB = ((val & 0x0F) * 2 + 1) & ~_eMaskB;
break;
case 10:
_eMaskC = (val & 0x10) != 0 ? -1 : 0;
_vC = ((val & 0x0F) * 2 + 1) & ~_eMaskC;
break;
// Envelope (Combined Duration)
// (not written to directly)
case 11:
case 12:
_dividerE = _registers[AY_E_FINE] | (_registers[AY_E_COARSE] << 8);
break;
// Envelope Shape
case 13:
// reset the envelope counter
_countE = 0;
if ((_registers[AY_E_SHAPE] & 4) != 0)
{
// attack
_eState = 0;
_eDirection = 1;
}
else
{
// decay
_eState = 31;
_eDirection = -1;
}
break;
case 14:
// IO Port - not implemented
break;
}
break; break;
} }
} }
/// <summary> /// <summary>
@ -414,6 +464,10 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary> /// </summary>
private byte _activeRegister; private byte _activeRegister;
private bool PortAInput = true;
private bool PortBInput = true;
/// <summary> /// <summary>
/// The frequency of the AY chip /// The frequency of the AY chip
/// </summary> /// </summary>
@ -764,12 +818,16 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{ {
ser.BeginSection("PSG-AY"); ser.BeginSection("PSG-AY");
ser.Sync("ActiveFunction", ref ActiveFunction);
ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame);
ser.Sync("_sampleRate", ref _sampleRate); ser.Sync("_sampleRate", ref _sampleRate);
ser.Sync("_samplesPerFrame", ref _samplesPerFrame); ser.Sync("_samplesPerFrame", ref _samplesPerFrame);
ser.Sync("_tStatesPerSample", ref _tStatesPerSample); ser.Sync("_tStatesPerSample", ref _tStatesPerSample);
ser.Sync("_audioBufferIndex", ref _audioBufferIndex); ser.Sync("_audioBufferIndex", ref _audioBufferIndex);
ser.Sync("_audioBuffer", ref _audioBuffer, false); ser.Sync("_audioBuffer", ref _audioBuffer, false);
ser.Sync("PortAInput", ref PortAInput);
ser.Sync("PortBInput", ref PortBInput);
ser.Sync("_registers", ref _registers, false); ser.Sync("_registers", ref _registers, false);
ser.Sync("_activeRegister", ref _activeRegister); ser.Sync("_activeRegister", ref _activeRegister);

View File

@ -1,407 +0,0 @@
using System;
using BizHawk.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
public sealed class PSG : ISoundProvider
{
private readonly BlipBuffer _blip = new BlipBuffer(4096);
private short[] _sampleBuffer = new short[0];
public PSG()
{
_blip.SetRates(4000000 / 4.0, 44100);
}
public ushort[] Register = new ushort[16];
public int total_clock; // TODO: what is this used for?
public void Reset()
{
clock_A = clock_B = clock_C = 0x1000;
noise_clock = 0x20;
for (int i = 0; i < 16; i++)
{
Register[i] = 0x0000;
}
sync_psg_state();
DiscardSamples();
}
public void DiscardSamples()
{
_blip.Clear();
_sampleClock = 0;
}
public void GetSamplesAsync(short[] samples)
{
throw new NotSupportedException("Async is not available");
}
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
public void SetSyncMode(SyncSoundMode mode)
{
if (mode != SyncSoundMode.Sync)
{
throw new InvalidOperationException("Only Sync mode is supported.");
}
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
_blip.EndFrame((uint)_sampleClock);
_sampleClock = 0;
nsamp = _blip.SamplesAvailable();
int targetLength = nsamp * 2;
if (_sampleBuffer.Length != targetLength)
{
_sampleBuffer = new short[targetLength];
}
_blip.ReadSamplesLeft(_sampleBuffer, nsamp);
for (int i = 0; i < _sampleBuffer.Length; i += 2)
{
_sampleBuffer[i + 1] = _sampleBuffer[i];
}
samples = _sampleBuffer;
}
public void GetSamples(short[] samples)
{
throw new Exception();
}
private static readonly int[] VolumeTable =
{
0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA,
0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA
};
private int _sampleClock;
private int _latchedSample;
private int TotalExecutedCycles;
private int PendingCycles;
private int psg_clock;
private int sq_per_A, sq_per_B, sq_per_C;
private int clock_A, clock_B, clock_C;
private int vol_A, vol_B, vol_C;
private bool A_on, B_on, C_on;
private bool A_up, B_up, C_up;
private bool A_noise, B_noise, C_noise;
private int env_per;
private int env_clock;
private int env_shape;
private int env_E;
private int E_up_down;
private int env_vol_A, env_vol_B, env_vol_C;
private int noise_clock;
private int noise_per;
private int noise = 0x1;
public Func<ushort, bool, ushort> ReadMemory;
public Func<ushort, ushort, bool, bool> WriteMemory;
public void SyncState(Serializer ser)
{
ser.BeginSection("PSG");
ser.Sync("Register", ref Register, false);
ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles);
ser.Sync("Pending_Cycles", ref PendingCycles);
ser.Sync("psg_clock", ref psg_clock);
ser.Sync("clock_A", ref clock_A);
ser.Sync("clock_B", ref clock_B);
ser.Sync("clock_C", ref clock_C);
ser.Sync("noise_clock", ref noise_clock);
ser.Sync("env_clock", ref env_clock);
ser.Sync("A_up", ref A_up);
ser.Sync("B_up", ref B_up);
ser.Sync("C_up", ref C_up);
ser.Sync("noise", ref noise);
ser.Sync("env_E", ref env_E);
ser.Sync("E_up_down", ref E_up_down);
sync_psg_state();
ser.EndSection();
}
public ushort? ReadPSG(ushort addr, bool peek)
{
if (addr >= 0x01F0 && addr <= 0x01FF)
{
return (ushort)(Register[addr - 0x01F0]);
}
return null;
}
private void sync_psg_state()
{
sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8));
if (sq_per_A == 0)
{
sq_per_A = 0x1000;
}
sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8));
if (sq_per_B == 0)
{
sq_per_B = 0x1000;
}
sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8));
if (sq_per_C == 0)
{
sq_per_C = 0x1000;
}
env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8));
if (env_per == 0)
{
env_per = 0x10000;
}
env_per *= 2;
A_on = Register[8].Bit(0);
B_on = Register[8].Bit(1);
C_on = Register[8].Bit(2);
A_noise = Register[8].Bit(3);
B_noise = Register[8].Bit(4);
C_noise = Register[8].Bit(5);
noise_per = Register[9] & 0x1F;
if (noise_per == 0)
{
noise_per = 0x20;
}
var shape_select = Register[10] & 0xF;
if (shape_select < 4)
env_shape = 0;
else if (shape_select < 8)
env_shape = 1;
else
env_shape = 2 + (shape_select - 8);
vol_A = Register[11] & 0xF;
env_vol_A = (Register[11] >> 4) & 0x3;
vol_B = Register[12] & 0xF;
env_vol_B = (Register[12] >> 4) & 0x3;
vol_C = Register[13] & 0xF;
env_vol_C = (Register[13] >> 4) & 0x3;
}
public bool WritePSG(ushort addr, ushort value, bool poke)
{
if (addr >= 0x01F0 && addr <= 0x01FF)
{
var reg = addr - 0x01F0;
value &= 0xFF;
if (reg == 4 || reg == 5 || reg == 6 || reg == 10)
value &= 0xF;
if (reg == 9)
value &= 0x1F;
if (reg == 11 || reg == 12 || reg == 13)
value &= 0x3F;
Register[addr - 0x01F0] = value;
sync_psg_state();
if (reg == 10)
{
env_clock = env_per;
if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5)
{
env_E = 15;
E_up_down = -1;
}
else
{
env_E = 0;
E_up_down = 1;
}
}
return true;
}
return false;
}
public void generate_sound(int cycles_to_do)
{
// there are 4 cpu cycles for every psg cycle
bool sound_out_A;
bool sound_out_B;
bool sound_out_C;
for (int i = 0; i < cycles_to_do; i++)
{
psg_clock++;
if (psg_clock == 4)
{
psg_clock = 0;
total_clock++;
clock_A--;
clock_B--;
clock_C--;
noise_clock--;
env_clock--;
// clock noise
if (noise_clock == 0)
{
noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0);
noise_clock = noise_per;
}
if (env_clock == 0)
{
env_clock = env_per;
env_E += E_up_down;
if (env_E == 16 || env_E == -1)
{
// we just completed a period of the envelope, determine what to do now based on the envelope shape
if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9)
{
E_up_down = 0;
env_E = 0;
}
else if (env_shape == 5 || env_shape == 7)
{
E_up_down = 0;
env_E = 15;
}
else if (env_shape == 4 || env_shape == 8)
{
if (env_E == 16)
{
env_E = 15;
E_up_down = -1;
}
else
{
env_E = 0;
E_up_down = 1;
}
}
else if (env_shape == 2)
{
env_E = 15;
}
else
{
env_E = 0;
}
}
}
if (clock_A == 0)
{
A_up = !A_up;
clock_A = sq_per_A;
}
if (clock_B == 0)
{
B_up = !B_up;
clock_B = sq_per_B;
}
if (clock_C == 0)
{
C_up = !C_up;
clock_C = sq_per_C;
}
sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up);
sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up);
sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up);
// now calculate the volume of each channel and add them together
int v;
if (env_vol_A == 0)
{
v = (short)(sound_out_A ? VolumeTable[vol_A] : 0);
}
else
{
int shift_A = 3 - env_vol_A;
if (shift_A < 0)
shift_A = 0;
v = (short)(sound_out_A ? (VolumeTable[env_E] >> shift_A) : 0);
}
if (env_vol_B == 0)
{
v += (short)(sound_out_B ? VolumeTable[vol_B] : 0);
}
else
{
int shift_B = 3 - env_vol_B;
if (shift_B < 0)
shift_B = 0;
v += (short)(sound_out_B ? (VolumeTable[env_E] >> shift_B) : 0);
}
if (env_vol_C == 0)
{
v += (short)(sound_out_C ? VolumeTable[vol_C] : 0);
}
else
{
int shift_C = 3 - env_vol_C;
if (shift_C < 0)
shift_C = 0;
v += (short)(sound_out_C ? (VolumeTable[env_E] >> shift_C) : 0);
}
if (v != _latchedSample)
{
_blip.AddDelta((uint)_sampleClock, v - _latchedSample);
_latchedSample = v;
}
_sampleClock++;
}
}
}
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
public class GateArray_CPC464 : GateArrayBase
{
public GateArray_CPC464(CPCBase machine)
: base(machine)
{
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -19,17 +20,87 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <returns></returns> /// <returns></returns>
public override byte ReadPort(ushort port) public override byte ReadPort(ushort port)
{ {
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
byte portUpper = (byte)(port >> 8);
byte portLower = (byte)(port & 0xff);
int result = 0xff; int result = 0xff;
if (DecodeINPort(port) == PortDevice.GateArray)
{
GateArray.ReadPort(port, ref result);
}
else if (DecodeINPort(port) == PortDevice.RAMManagement)
{
}
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)
{
}
return (byte)result;
if ((port & 0x8000) == 0)
{
if ((port & 0x4000) != 0)
{
GateArray.ReadPort(port, ref result);
}
else
{
// ram
}
}
else if ((port & 0x4000) == 0)
{
CRCT.ReadPort(port, ref result);
}
else if ((port & 0x0300) == 0)
{
// rom
}
else if ((port & 0x1000) == 0)
{
// printer
}
else if ((port & 0x0800) == 0)
{
PPI.ReadPort(port, ref result);
}
else if ((port & 0x0400) == 0)
{
// exp
}
return (byte)result;
if (CRCT.ReadPort(port, ref result)) if (CRCT.ReadPort(port, ref result))
{ {
return (byte)result; return (byte)result;
} }
else if (GateArray.ReadPort(port, ref result)) else if (PPI.ReadPort(port, ref result))
{ {
return (byte)result; return (byte)result;
} }
else if (PPI.ReadPort(port, ref result)) else if (GateArray.ReadPort(port, ref result))
{ {
return (byte)result; return (byte)result;
} }
@ -39,16 +110,63 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary> /// <summary>
/// Writes a byte of data to a specified port address /// Writes a byte of data to a specified port address
/// Because of the port decoding, multiple devices can be written to
/// </summary> /// </summary>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="value"></param> /// <param name="value"></param>
public override void WritePort(ushort port, byte value) public override void WritePort(ushort port, byte value)
{ {
if (CRCT.WritePort(port, (int)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)
{
if (d == PortDevice.GateArray)
{
GateArray.WritePort(port, value);
}
else if (d == PortDevice.RAMManagement)
{
}
else if (d == PortDevice.CRCT)
{
CRCT.WritePort(port, value);
}
else if (d == PortDevice.ROMSelect)
{
}
else if (d == PortDevice.Printer)
{
}
else if (d == PortDevice.PPI)
{
PPI.WritePort(port, value);
}
else if (d == PortDevice.Expansion)
{
}
}
return;
if (GateArray.WritePort(port, value))
{ } { }
else if (GateArray.WritePort(port, (int)value))
if (CRCT.WritePort(port, value))
{ } { }
else if (PPI.WritePort(port, (int)value))
// rom select
// printer port
if (PPI.WritePort(port, value))
{ } { }
} }
} }

View File

@ -28,14 +28,15 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007); GateArray = new AmstradGateArray(this, AmstradGateArray.GateArrayType.Amstrad40007);
PPI = new PPI_8255(this); PPI = new PPI_8255(this);
KeyboardDevice = new StandardKeyboard(this);
TapeBuzzer = new Beeper(this); TapeBuzzer = new Beeper(this);
TapeBuzzer.Init(44100, FrameLength); TapeBuzzer.Init(44100, FrameLength);
//AYDevice = new PSG(this, PSG.ay38910_type_t.AY38910_TYPE_8912, GateArray.PSGClockSpeed, 882 * 50);
AYDevice = new AY38912(this); AYDevice = new AY38912(this);
AYDevice.Init(44100, FrameLength); AYDevice.Init(44100, FrameLength);
KeyboardDevice = new StandardKeyboard(this);
TapeDevice = new DatacorderDevice(); TapeDevice = new DatacorderDevice();
TapeDevice.Init(this); TapeDevice.Init(this);

View File

@ -1,4 +1,8 @@
 
using BizHawk.Common.NumberExtensions;
using System;
using System.Collections.Generic;
namespace BizHawk.Emulation.Cores.Computers.AmstradCPC namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{ {
/// <summary> /// <summary>
@ -20,5 +24,92 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="value"></param> /// <param name="value"></param>
public abstract void WritePort(ushort port, byte value); public abstract void WritePort(ushort port, byte value);
/// <summary>
/// Returns a single port device enum based on the port address
/// (for IN operations)
/// https://web.archive.org/web/20090808085929/http://www.cepece.info/amstrad/docs/iopord.html
/// http://www.cpcwiki.eu/index.php/I/O_Port_Summary
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
protected virtual PortDevice DecodeINPort(ushort port)
{
PortDevice dev = PortDevice.Unknown;
if (!port.Bit(15) && port.Bit(14))
dev = PortDevice.GateArray;
else if (!port.Bit(15))
dev = PortDevice.RAMManagement;
else if (!port.Bit(14))
dev = PortDevice.CRCT;
else if (!port.Bit(13))
dev = PortDevice.ROMSelect;
else if (!port.Bit(12))
dev = PortDevice.Printer;
else if (!port.Bit(11))
dev = PortDevice.PPI;
else if (!port.Bit(10))
dev = PortDevice.Expansion;
return dev;
}
/// <summary>
/// Returns a list of port device enums based on the port address
/// (for OUT operations)
/// https://web.archive.org/web/20090808085929/http://www.cepece.info/amstrad/docs/iopord.html
/// http://www.cpcwiki.eu/index.php/I/O_Port_Summary
/// </summary>
/// <param name="port"></param>
/// <returns></returns>
protected virtual List<PortDevice> DecodeOUTPort(ushort port)
{
List<PortDevice> devs = new List<PortDevice>();
if (!port.Bit(15) && port.Bit(14))
devs.Add(PortDevice.GateArray);
if (!port.Bit(15))
devs.Add(PortDevice.RAMManagement);
if (!port.Bit(14))
devs.Add(PortDevice.CRCT);
if (!port.Bit(13))
devs.Add(PortDevice.ROMSelect);
if (!port.Bit(12))
devs.Add(PortDevice.Printer);
if (!port.Bit(11))
devs.Add(PortDevice.PPI);
if (!port.Bit(10))
devs.Add(PortDevice.Expansion);
return devs;
}
/// <summary>
/// Potential port devices
/// </summary>
public enum PortDevice
{
Unknown,
GateArray,
RAMManagement,
CRCT,
ROMSelect,
Printer,
PPI,
Expansion
}
} }
} }

View File

@ -338,15 +338,11 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
//KeyboardDevice.SyncState(ser); //KeyboardDevice.SyncState(ser);
//BuzzerDevice.SyncState(ser); //BuzzerDevice.SyncState(ser);
TapeBuzzer.SyncState(ser); TapeBuzzer.SyncState(ser);
AYDevice.SyncState(ser);
//.SyncState(ser); //.SyncState(ser);
//CPUMon.SyncState(ser); //CPUMon.SyncState(ser);
if (AYDevice != null)
{
AYDevice.SyncState(ser);
((AY38912)AYDevice as AY38912).PanningConfiguration = CPC.Settings.AYPanConfig;
}
ser.Sync("tapeMediaIndex", ref tapeMediaIndex); ser.Sync("tapeMediaIndex", ref tapeMediaIndex);
if (ser.IsReader) if (ser.IsReader)
TapeMediaIndex = tapeMediaIndex; TapeMediaIndex = tapeMediaIndex;

View File

@ -1,11 +1,33 @@
## CPCHawk ## CPCHawk
This may or may not work out. But I figured I could at least start by building on the existing ZXHawk implementation (the machines do have CPU, tape format, PSG and disk drive in common). This may or may not work out. But I figured I could at least start by building on the existing ZXHawk implementation (the machines do have CPU, tape format, PSG and disk drive/controller in common).
We'll see how that goes... We'll see how that goes...
#### Currently working: #### In Place (but probably requires more work)
* Nothing. Just starting to get a code outline in place * CPC464 model template
* Non-paged memory
* Standard lower and upper ROM
* Port IO decoding
* CRCT (Cathode Ray Tube Controller) chip emulation
* Amstrad Gate Array chip emulation
* Video rendering (mode 1)
* i8255 Programmable Peripheral Interface (PPI) chip emulation
* AY-3-8912 PSG Port IO
* Keyboard/Joystick
* .CDT tape image file support
#### Not Yet
* CPC664, CPC6128, CPC464plus, CPC6128plus, GX4000 models
* RAM banking
* Upper ROM banking
* Video rendering (modes 0, 2 & 3)
* AY-3-8912 PSG sound output
* Datacorder (tape) device
* FDC and FDD devices
* .DSK image parsing and identification (to auto differenciate from ZX Spectrum disk bootloader)
* Expansion IO
-Asnivor -Asnivor

View File

@ -101,6 +101,9 @@
<Name>BizHawk.Common</Name> <Name>BizHawk.Common</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="ClassDiagram1.cd" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
@ -109,4 +112,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<ClassDiagram />