ZXHawk: new ULA implementation

This commit is contained in:
Matt Burgess 2018-05-31 17:54:57 +01:00
parent 20f10b9311
commit 9a15cbf4d4
26 changed files with 1728 additions and 220 deletions

View File

@ -261,6 +261,8 @@
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IFDDHost.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\AY38912.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\SoundOuput\Beeper.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\CPUMonitor.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ULA.cs" />
<Compile Include="Computers\SinclairSpectrum\Media\Disk\FloppyDisk.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IJoystick.cs" />
<Compile Include="Computers\SinclairSpectrum\Hardware\Abstraction\IPortIODevice.cs" />
@ -1434,6 +1436,7 @@
<ItemGroup>
<Compile Include="Computers\SinclairSpectrum\Hardware\Input\StandardKeyboard.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Port.cs" />
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Screen.cs" />
<None Include="Computers\SinclairSpectrum\readme.md" />
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Media.cs" />
<None Include="Consoles\Atari\docs\stella.pdf" />

View File

@ -0,0 +1,187 @@
using BizHawk.Emulation.Cores.Components.Z80A;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
public class CPUMonitor
{
private SpectrumBase _machine;
private Z80A _cpu;
public MachineType machineType = MachineType.ZXSpectrum48;
public CPUMonitor(SpectrumBase machine)
{
_machine = machine;
_cpu = _machine.CPU;
}
public ushort[] cur_instr => _cpu.cur_instr;
public int instr_pntr => _cpu.instr_pntr;
public ushort RegPC => _cpu.RegPC;
public long TotalExecutedCycles => _cpu.TotalExecutedCycles;
/// <summary>
/// Called when the first byte of an instruction is fetched
/// </summary>
/// <param name="firstByte"></param>
public void OnExecFetch(ushort firstByte)
{
// fetch instruction without incrementing pc
//_cpu.FetchInstruction(_cpu.FetchMemory(firstByte));
}
/// <summary>
/// A CPU monitor cycle
/// </summary>
public void Cycle()
{
if (portContending)
{
RunPortContention();
}
}
#region Port Contention
public int portContCounter = 0;
public bool portContending = false;
public ushort lastPortAddr;
/// <summary>
/// Perfors the actual port contention (if necessary)
/// </summary>
private void RunPortContention()
{
//return;
bool lowBitSet = false;
bool highByte407f = false;
int offset = 0; // _machine.ULADevice.contentionOffset; // -5;// 57;// - 10;
var c = _machine.CurrentFrameCycle;
var t = _machine.ULADevice.FrameLength;
int f = (int)c + offset;
if (f >= t)
f = f - t;
else if (f < 0)
f = t + f;
if ((lastPortAddr & 0x0001) != 0)
lowBitSet = true;
portContCounter--;
switch (machineType)
{
case MachineType.ZXSpectrum16:
case MachineType.ZXSpectrum48:
if ((lastPortAddr & 0xc000) == 0x4000)
highByte407f = true;
if (highByte407f)
{
// high byte 40-7f
if (lowBitSet)
{
// high byte 40-7f
// low bit set
// C:1, C:1, C:1, C:1
switch (portContCounter)
{
case 3: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 1: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 0: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
default:
portContCounter = 0;
portContending = false;
break;
}
}
else
{
// high byte 40-7f
// low bit reset
// C:1, C:3
switch (portContCounter)
{
case 3: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 1: break;
case 0: break;
default:
portContCounter = 0;
portContending = false;
break;
}
}
}
else
{
// high byte not 40-7f
if (lowBitSet)
{
// high byte not 40-7f
// low bit set
// N:4
switch (portContCounter)
{
case 3: break;
case 2: break;
case 1: break;
case 0: break;
default:
portContCounter = 0;
portContending = false;
break;
}
}
else
{
// high byte not 40-7f
// low bit reset
// N:1, C:3
switch (portContCounter)
{
case 3: break;
case 2: _cpu.TotalExecutedCycles += _machine.ULADevice.GetContentionValue(f); break;
case 1: break;
case 0: break;
default:
portContCounter = 0;
portContending = false;
break;
}
}
}
break;
case MachineType.ZXSpectrum128:
case MachineType.ZXSpectrum128Plus2:
break;
case MachineType.ZXSpectrum128Plus2a:
case MachineType.ZXSpectrum128Plus3:
break;
}
}
/// <summary>
/// Starts the port contention process
/// </summary>
/// <param name="type"></param>
public void ContendPort(ushort port)
{
portContending = true;
portContCounter = 4;
lastPortAddr = port;
}
#endregion
}
}

View File

@ -89,8 +89,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
#region Memory Related Methods
/// <summary>
@ -153,9 +151,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public virtual byte FetchScreenMemory(ushort addr)
{
var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000));
//var value = ReadBus(addr);
return value;
}
/// <summary>
/// Contends memory if necessary
/// </summary>
public abstract void ContendMemory(ushort addr);
/// <summary>
/// Checks whether supplied address is in a potentially contended bank
/// </summary>
/// <param name="addr"></param>
public abstract bool IsContended(ushort addr);
#endregion
#region Helper Methods

View File

@ -40,88 +40,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Increments the CPU totalCycles counter by the tStates value specified
/// </summary>
/// <param name="tStates"></param>
public virtual void PortContention(int tStates)
{
CPU.TotalExecutedCycles += tStates;
}
//public virtual void PortContention(int tStates)
//{
// CPU.TotalExecutedCycles += tStates;
//}
/// <summary>
/// Simulates IO port contention based on the supplied address
/// This method is for 48k and 128k/+2 machines only and should be overridden for other models
/// </summary>
/// <param name="addr"></param>
public virtual void ContendPortAddress(ushort addr)
{
return;
/*
It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port. As is the case with memory access,
this can be lengthened by the ULA. There are two effects which occur here:
If the port address being accessed has its low bit reset, the ULA is required to supply the result, which leads to a delay if it is
currently busy handling the screen.
The address of the port being accessed is placed on the data bus. If this is in the range 0x4000 to 0x7fff, the ULA treats this as an
attempted access to contended memory and therefore introduces a delay. If the port being accessed is between 0xc000 and 0xffff,
this effect does not apply, even on a 128K machine if a contended memory bank is paged into the range 0xc000 to 0xffff.
These two effects combine to lead to the following contention patterns:
High byte | |
in 40 - 7F? | Low bit | Contention pattern
------------+---------+-------------------
No | Reset | N:1, C:3
No | Set | N:4
Yes | Reset | C:1, C:3
Yes | Set | C:1, C:1, C:1, C:1
The 'Contention pattern' column should be interpreted from left to right. An "N:n" entry means that no delay is applied at this cycle, and the Z80 continues uninterrupted for 'n' T states. A "C:n" entry means that the ULA halts the Z80; the delay is exactly the same as would occur for a contended memory access at this cycle (eg 6 T states at cycle 14335, 5 at 14336, etc on the 48K machine). After this delay, the Z80 then continues for 'n' cycles.
*/
// is the low bit reset (i.e. is this addressing the ULA)?
bool lowBit = (addr & 0x0001) != 0;
if ((addr & 0xc000) == 0x4000 || (addr & 0xc000) == 0xC000)
{
// high byte is in 40 - 7F
if (lowBit)
{
// lowbit is set
// C:1, C:1, C:1, C:1
for (int i = 0; i < 4; i++)
{
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles++;
}
}
else
{
// low bit is reset
// C:1, C:3
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles++;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles += 3;
}
}
else
{
// high byte is NOT in 40 - 7F
if (lowBit)
{
// lowbit is set
// C:1, C:1, C:1, C:1
CPU.TotalExecutedCycles += 4;
}
else
{
// lowbit is reset
// N:1, C:3
CPU.TotalExecutedCycles++;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles += 3;
}
}
}
public abstract void ContendPort(ushort addr);
}
}

View File

@ -32,7 +32,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// The emulated ULA device
/// </summary>
public ULABase ULADevice { get; set; }
//public ULABase ULADevice { get; set; }
public ULA ULADevice { get; set; }
/// <summary>
/// Monitors CPU cycles
/// </summary>
public CPUMonitor CPUMon { get; set; }
/// <summary>
/// The spectrum buzzer/beeper
@ -152,9 +158,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (_renderSound)
{
//BuzzerDevice.StartFrame();
//TapeBuzzer.StartFrame();
if (AYDevice != null)
AYDevice.StartFrame();
}
@ -169,23 +172,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// run a single CPU instruction
CPU.ExecuteOne();
CPUMon.Cycle();
// cycle the tape device
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
TapeDevice.TapeCycle();
}
OverFlow = (int)CurrentFrameCycle - ULADevice.FrameLength;
// we have reached the end of a frame
LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow;
// paint the buffer if needed
if (ULADevice.needsPaint && _render)
ULADevice.UpdateScreenBuffer(ULADevice.FrameLength);
// paint the buffer at end of frame
if (_render)
ULADevice.RenderScreen(ULADevice.FrameLength);
if (_renderSound)
{
//BuzzerDevice.EndFrame();
//TapeBuzzer.EndFrame();
}
ULADevice.LastTState = 0;
if (AYDevice != null)
AYDevice.EndFrame();
@ -194,7 +197,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// setup for next frame
ULADevice.ResetInterrupt();
if (UPDDiskDevice == null || !UPDDiskDevice.FDD_IsDiskLoaded)
TapeDevice.EndFrame();

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
/// <summary>
/// ULA (Uncommitted Logic Array) implementation
/// </summary>
@ -78,6 +79,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public byte[] contentionTable;
/// <summary>
/// Contention offset (used for testing)
/// </summary>
public int contentionOffset = 0; // -5;
#endregion
#region Screen Rendering
@ -86,6 +92,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Video output buffer
/// </summary>
public int[] ScreenBuffer;
/// <summary>
/// Screen rendering T-State info
/// </summary>
public RenderCycle[] RenderTable;
/// <summary>
/// Display memory
/// </summary>
@ -246,14 +258,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#region Interrupt
/// <summary>
/// The number of T-States that the INT pin is simulated to be held low
/// The t-state within the frame that an interrupt is raised
/// </summary>
public int InterruptPeriod;
public int InterruptStart;
/// <summary>
/// The longest instruction cycle count
/// The number of T-States that the INT pin is simulated to be held low
/// </summary>
protected int LongestOperationCycles = 23;
public int InterruptDuration = 32;
/// <summary>
/// Signs that an interrupt has been raised in this frame.
@ -287,13 +299,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
return;
}
if (currentCycle < LongestOperationCycles)// InterruptPeriod)
if (currentCycle < InterruptStart)
{
// interrupt does not need to be raised yet
return;
}
if (currentCycle >= InterruptPeriod + LongestOperationCycles)
if (currentCycle > InterruptStart + InterruptDuration)
{
// interrupt should have already been raised and the cpu may or
// may not have caught it. The time has passed so revoke the signal
@ -350,31 +362,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <param name="addr"></param>
/// <returns></returns>
public abstract bool IsContended(int addr);
/// <summary>
/// Contends the machine for a given address
/// </summary>
/// <param name="addr"></param>
public virtual void Contend(ushort addr)
{
if (IsContended(addr) && !(_machine is ZX128Plus3))
{
_machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame];
}
}
public virtual void Contend(int addr, int time, int count)
{
if (IsContended(addr) && !(_machine is ZX128Plus3))
{
for (int f = 0; f < count; f++)
{
_machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame] + time;
}
}
else
_machine.CPU.TotalExecutedCycles += count * time;
}
/// <summary>
/// Resets render state once interrupt is generated
@ -401,6 +389,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
/// <summary>
/// Builds the T-State to attribute map used with the floating bus
/// </summary>
@ -426,6 +415,32 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
}
/// <summary>
/// Returns the floating bus value
/// </summary>
public virtual void ReadFloatingBus(ref int result)
{
// Floating bus is read on the previous cycle
long _tStates = _machine.CurrentFrameCycle - 1;
// if we are on the top or bottom border return 0xff
if ((_tStates < contentionStartPeriod) || (_tStates > contentionEndPeriod))
{
result = 0xff;
}
else
{
if (floatingBusTable[_tStates] < 0)
{
result = 0xff;
}
else
{
result = _machine.ReadBus((ushort)floatingBusTable[_tStates]);
}
}
}
/// <summary>
/// Updates the screen buffer based on the number of T-States supplied
/// </summary>
@ -444,7 +459,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
//the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna
elapsedTStates = (_tstates + 1 - lastTState) - 1;
elapsedTStates = (_tstates + 1 - lastTState);
//It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state.
@ -755,6 +770,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
#endregion
}
/// <summary>
/// T-State display mapping
/// </summary>
public class RenderCycle
{
public RenderType Type { get; set; }
public short DisplayAddress { get; set; }
public short AttributeAddress { get; set; }
public int ContentionValue { get; set; }
public short FloatingBusAddress { get; set; }
}
public enum RenderType
{
None,
Border,
Display
}
*/
}

View File

@ -175,10 +175,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
default:
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
@ -189,9 +185,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
ContendMemory(addr);
var data = ReadBus(addr);
return data;
}
@ -204,13 +199,56 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <param name="value"></param>
public override void WriteMemory(ushort addr, byte value)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
// update ULA screen buffer if necessary BEFORE T1 write
if (((addr & 49152) == 16384 || ((addr & 0xc000) == 0xc000) && (RAMPaged == 5 || RAMPaged == 7)) && _render)
ULADevice.RenderScreen((int)CurrentFrameCycle);
ContendMemory(addr);
WriteBus(addr, value);
}
/// <summary>
/// Contends memory if necessary
/// </summary>
public override void ContendMemory(ushort addr)
{
if (IsContended(addr))
{
var delay = ULADevice.GetContentionValue((int)CurrentFrameCycle);
CPU.TotalExecutedCycles += delay;
}
}
/// <summary>
/// Checks whether supplied address is in a potentially contended bank
/// </summary>
/// <param name="addr"></param>
public override bool IsContended(ushort addr)
{
var a = addr & 0xc000;
if (a == 0x4000)
{
// low port contention
return true;
}
if (a == 0xc000)
{
// high port contention - check for contended bank paged in
switch (RAMPaged)
{
case 1:
case 3:
case 5:
case 7:
return true;
}
}
return false;
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)

View File

@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
ContendPort(port);
int result = 0xFF;
@ -56,6 +56,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
ULADevice.ReadFloatingBus((int)_tStates, ref result);
/*
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
@ -72,6 +75,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
*/
}
return (byte)result;
@ -85,7 +89,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void WritePort(ushort port, byte value)
{
// process IO contention
ContendPortAddress(port);
ContendPort(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
@ -142,7 +146,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// store the last OUT byte
LastULAOutByte = value;
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
CPU.TotalExecutedCycles += ULADevice.GetContentionValue();
/*
Bit 7 6 5 4 3 2 1 0
@ -152,10 +156,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
if (ULADevice.BorderColor != (value & BORDER_BIT))
ULADevice.RenderScreen((int)CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
ULADevice.BorderColor = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue((value & EAR_BIT) != 0);
@ -165,5 +169,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
}
/// <summary>
/// Contend port if necessary
/// </summary>
/// <param name="addr"></param>
public override void ContendPort(ushort addr)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,6 +1,7 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
class ULA128 : ULABase
{
#region Construction
@ -8,8 +9,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public ULA128(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
InterruptStart = 0;
//LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
@ -189,4 +190,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
*/
}

View File

@ -21,12 +21,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Spectrum = spectrum;
CPU = cpu;
CPUMon = new CPUMonitor(this);
CPUMon.machineType = MachineType.ZXSpectrum128;
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULA128(this);
//ULADevice = new ULA128(this);
ULADevice = new Screen48(this); // still todo
BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);

View File

@ -344,7 +344,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
ULADevice.RenderScreen((int)CurrentFrameCycle);
}
/// <summary>
@ -355,9 +355,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
ContendMemory(addr);
var data = ReadBus(addr);
return data;
}
@ -370,13 +369,74 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <param name="value"></param>
public override void WriteMemory(ushort addr, byte value)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
// update ULA screen buffer if necessary BEFORE T1 write
if (!SpecialPagingMode)
{
if (((addr & 49152) == 16384 || ((addr & 0xc000) == 0xc000) && (RAMPaged == 5 || RAMPaged == 7)) && _render)
ULADevice.RenderScreen((int)CurrentFrameCycle);
}
else
{
switch (PagingConfiguration)
{
case 2:
case 3:
if ((addr & 49152) == 16384)
ULADevice.RenderScreen((int)CurrentFrameCycle);
break;
case 1:
if ((addr & 49152) == 16384 || addr >= 0xc000)
ULADevice.RenderScreen((int)CurrentFrameCycle);
break;
}
}
ContendMemory(addr);
WriteBus(addr, value);
}
/// <summary>
/// Contends memory if necessary
/// </summary>
public override void ContendMemory(ushort addr)
{
if (IsContended(addr))
{
var delay = ULADevice.GetContentionValue((int)CurrentFrameCycle);
CPU.TotalExecutedCycles += delay;
}
}
/// <summary>
/// Checks whether supplied address is in a potentially contended bank
/// </summary>
/// <param name="addr"></param>
public override bool IsContended(ushort addr)
{
var a = addr & 0xc000;
if (a == 0x4000)
{
// low port contention
return true;
}
if (a == 0xc000)
{
// high port contention - check for contended bank paged in
switch (RAMPaged)
{
case 4:
case 5:
case 6:
case 7:
return true;
}
}
return false;
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)

View File

@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
ContendPort(port);
int result = 0xFF;
@ -56,6 +56,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
ULADevice.ReadFloatingBus((int)_tStates, ref result);
/*
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
@ -72,6 +74,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
*/
}
/*
// Check whether the low bit is reset
@ -158,7 +161,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void WritePort(ushort port, byte value)
{
// process IO contention
ContendPortAddress(port);
ContendPort(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
@ -241,10 +244,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
if (ULADevice.BorderColor != (value & BORDER_BIT))
ULADevice.RenderScreen((int)CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
ULADevice.BorderColor = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue((value & EAR_BIT) != 0);
@ -281,13 +284,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
/// <summary>
/// Override port contention
/// +3/2a does not have the same ULA IO contention
/// Contend port if necessary
/// </summary>
/// <param name="addr"></param>
public override void ContendPortAddress(ushort addr)
public override void ContendPort(ushort addr)
{
//CPU.TotalExecutedCycles += 4;
throw new NotImplementedException();
}
}
}

View File

@ -1,6 +1,7 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
class ULAPlus2a : ULABase
{
#region Construction
@ -8,8 +9,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public ULAPlus2a(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
InterruptStart = 0;
//LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
@ -193,4 +194,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
*/
}

View File

@ -21,12 +21,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Spectrum = spectrum;
CPU = cpu;
CPUMon = new CPUMonitor(this);
CPUMon.machineType = MachineType.ZXSpectrum128Plus2a;
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULAPlus2a(this);
//ULADevice = new ULAPlus2a(this);
ULADevice = new Screen48(this); // still todo
BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);

View File

@ -344,7 +344,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
ULADevice.RenderScreen((int)CurrentFrameCycle);
}
/// <summary>
@ -355,9 +355,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
ContendMemory(addr);
var data = ReadBus(addr);
return data;
}
@ -370,13 +369,74 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <param name="value"></param>
public override void WriteMemory(ushort addr, byte value)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
// update ULA screen buffer if necessary BEFORE T1 write
if (!SpecialPagingMode)
{
if (((addr & 49152) == 16384 || ((addr & 0xc000) == 0xc000) && (RAMPaged == 5 || RAMPaged == 7)) && _render)
ULADevice.RenderScreen((int)CurrentFrameCycle);
}
else
{
switch (PagingConfiguration)
{
case 2:
case 3:
if ((addr & 49152) == 16384)
ULADevice.RenderScreen((int)CurrentFrameCycle);
break;
case 1:
if ((addr & 49152) == 16384 || addr >= 0xc000)
ULADevice.RenderScreen((int)CurrentFrameCycle);
break;
}
}
ContendMemory(addr);
WriteBus(addr, value);
}
/// <summary>
/// Contends memory if necessary
/// </summary>
public override void ContendMemory(ushort addr)
{
if (IsContended(addr))
{
var delay = ULADevice.GetContentionValue((int)CurrentFrameCycle);
CPU.TotalExecutedCycles += delay;
}
}
/// <summary>
/// Checks whether supplied address is in a potentially contended bank
/// </summary>
/// <param name="addr"></param>
public override bool IsContended(ushort addr)
{
var a = addr & 0xc000;
if (a == 0x4000)
{
// low port contention
return true;
}
if (a == 0xc000)
{
// high port contention - check for contended bank paged in
switch (RAMPaged)
{
case 4:
case 5:
case 6:
case 7:
return true;
}
}
return false;
}
/// <summary>
/// ULA reads the memory at the specified address
/// (No memory contention)

View File

@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
bool deviceAddressed = true;
// process IO contention
ContendPortAddress(port);
ContendPort(port);
int result = 0xFF;
@ -60,6 +60,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
ULADevice.ReadFloatingBus((int)_tStates, ref result);
/*
// if we are on the top or bottom border return 0xff
if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod))
{
@ -76,6 +80,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
*/
}
return (byte)result;
@ -89,7 +94,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void WritePort(ushort port, byte value)
{
// process IO contention
ContendPortAddress(port);
ContendPort(port);
// get a BitArray of the port
BitArray portBits = new BitArray(BitConverter.GetBytes(port));
@ -174,10 +179,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
if (ULADevice.BorderColor != (value & BORDER_BIT))
ULADevice.RenderScreen((int)CurrentFrameCycle);
ULADevice.borderColour = value & BORDER_BIT;
ULADevice.BorderColor = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue((value & EAR_BIT) != 0);
@ -214,13 +219,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
/// <summary>
/// Override port contention
/// +3/2a does not have the same ULA IO contention
/// Contend port if necessary
/// </summary>
/// <param name="addr"></param>
public override void ContendPortAddress(ushort addr)
public override void ContendPort(ushort addr)
{
//CPU.TotalExecutedCycles += 4;
throw new NotImplementedException();
}
}
}

View File

@ -1,6 +1,7 @@

namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
class ULAPlus3 : ULABase
{
#region Construction
@ -8,8 +9,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public ULAPlus3(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 36;
LongestOperationCycles = 64 + 2;
InterruptStart = 0;
//LongestOperationCycles = 64 + 2;
FrameLength = 70908;
ClockSpeed = 3546900;
@ -193,4 +194,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
*/
}

View File

@ -20,13 +20,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
Spectrum = spectrum;
CPU = cpu;
CPUMon.machineType = MachineType.ZXSpectrum128Plus3;
CPUMon = new CPUMonitor(this);
ROMPaged = 0;
SHADOWPaged = false;
RAMPaged = 0;
PagingDisabled = false;
ULADevice = new ULAPlus3(this);
// ULADevice = new ULAPlus3(this);
ULADevice = new Screen48(this); // still todo
BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);

View File

@ -24,7 +24,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
#endregion
#region Memory
/* 48K Spectrum has NO memory paging
@ -88,11 +87,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
case 1:
RAM0[index] = value;
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
}
/// <summary>
@ -103,8 +98,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
if (IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.GetContentionValue((int)CurrentFrameCycle);
var data = ReadBus(addr);
return data;
@ -119,8 +114,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void WriteMemory(ushort addr, byte value)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
if (IsContended(addr))
{
ULADevice.RenderScreen((int)CurrentFrameCycle);
CPU.TotalExecutedCycles += ULADevice.GetContentionValue((int)CurrentFrameCycle);
}
WriteBus(addr, value);
}

View File

@ -86,10 +86,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
RAM2[index] = value;
break;
}
// update ULA screen buffer if necessary
if ((addr & 49152) == 16384 && _render)
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
/// <summary>
@ -100,9 +96,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <returns></returns>
public override byte ReadMemory(ushort addr)
{
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
ContendMemory(addr);
var data = ReadBus(addr);
return data;
}
@ -115,13 +109,41 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <param name="value"></param>
public override void WriteMemory(ushort addr, byte value)
{
// apply contention if necessary
if (ULADevice.IsContended(addr))
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
// update ULA screen buffer if necessary BEFORE T1 write
if ((addr & 49152) == 16384 && _render)
ULADevice.RenderScreen((int)CurrentFrameCycle);
ContendMemory(addr);
WriteBus(addr, value);
}
/// <summary>
/// Contends memory if necessary
/// </summary>
public override void ContendMemory(ushort addr)
{
if (IsContended(addr))
{
var delay = ULADevice.GetContentionValue((int)CurrentFrameCycle);
if (delay > 0)
{
}
CPU.TotalExecutedCycles += delay;
}
}
/// <summary>
/// Checks whether supplied address is in a potentially contended bank
/// </summary>
/// <param name="addr"></param>
public override bool IsContended(ushort addr)
{
if ((addr & 49152) == 16384)
return true;
return false;
}
/// <summary>
/// Sets up the ROM
/// </summary>

View File

@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override byte ReadPort(ushort port)
{
// process IO contention
ContendPortAddress(port);
ContendPort(port);
int result = 0xFF;
@ -56,6 +56,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
// If this is an unused port the floating memory bus should be returned
ULADevice.ReadFloatingBus((int)CurrentFrameCycle, ref result);
/*
// Floating bus is read on the previous cycle
long _tStates = CurrentFrameCycle - 1;
@ -75,6 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]);
}
}
*/
}
return (byte)result;
@ -88,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void WritePort(ushort port, byte value)
{
// process IO contention
ContendPortAddress(port);
ContendPort(port);
// Check whether the low bit is reset
// Technically the ULA should respond to every even I/O address
@ -106,13 +110,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
*/
// Border - LSB 3 bits hold the border colour
if (ULADevice.borderColour != (value & BORDER_BIT))
{
// border value has changed - update the screen buffer
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
}
ULADevice.borderColour = value & BORDER_BIT;
ULADevice.RenderScreen((int)CurrentFrameCycle);
ULADevice.BorderColor = value & BORDER_BIT;
// Buzzer
BuzzerDevice.ProcessPulseValue((value & EAR_BIT) != 0);
@ -124,6 +123,40 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
//TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
}
/// <summary>
/// Simulates IO port contention based on the supplied address
/// This method is for 48k and 128k/+2 machines only and should be overridden for other models
/// </summary>
/// <param name="addr"></param>
public override void ContendPort(ushort addr)
{
/*
It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port. As is the case with memory access,
this can be lengthened by the ULA. There are two effects which occur here:
If the port address being accessed has its low bit reset, the ULA is required to supply the result, which leads to a delay if it is
currently busy handling the screen.
The address of the port being accessed is placed on the data bus. If this is in the range 0x4000 to 0x7fff, the ULA treats this as an
attempted access to contended memory and therefore introduces a delay. If the port being accessed is between 0xc000 and 0xffff,
this effect does not apply, even on a 128K machine if a contended memory bank is paged into the range 0xc000 to 0xffff.
These two effects combine to lead to the following contention patterns:
High byte | |
in 40 - 7F? | Low bit | Contention pattern
------------+---------+-------------------
No | Reset | N:1, C:3
No | Set | N:4
Yes | Reset | C:1, C:3
Yes | Set | C:1, C:1, C:1, C:1
The 'Contention pattern' column should be interpreted from left to right. An "N:n" entry means that no delay is applied at this cycle, and the Z80 continues uninterrupted for 'n' T states. A "C:n" entry means that the ULA halts the Z80; the delay is exactly the same as would occur for a contended memory access at this cycle (eg 6 T states at cycle 14335, 5 at 14336, etc on the 48K machine). After this delay, the Z80 then continues for 'n' cycles.
*/
CPUMon.ContendPort(addr);
return;
}
}
}

View File

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
class Screen48 : ULA
{
#region Construction
public Screen48(SpectrumBase machine)
: base(machine)
{
// timing
ClockSpeed = 3500000;
FrameCycleLength = 69888;
InterruptStartTime = 32;
InterruptLength = 32;
ScanlineTime = 224;
BorderLeftTime = 24;
BorderRightTime = 24;
FirstPaperLine = 64;
FirstPaperTState = 64;
Border4T = true;
Border4TStage = 0;
// screen layout
ScreenWidth = 256;
ScreenHeight = 192;
BorderTopHeight = 48;
BorderBottomHeight = 56;
BorderLeftWidth = 48;
BorderRightWidth = 48;
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
RenderingTable = new RenderTable(this,
MachineType.ZXSpectrum48);
SetupScreenSize();
}
#endregion
}
}

View File

@ -1,6 +1,9 @@

using System.Linq;
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
/*
class ULA48 : ULABase
{
#region Construction
@ -8,8 +11,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public ULA48(SpectrumBase machine)
: base(machine)
{
InterruptPeriod = 32;
LongestOperationCycles = 64;
InterruptStart = 0;// 5; // 0; // 3; // 0;// 32;
//LongestOperationCycles = 32;
FrameLength = 69888;
ClockSpeed = 3500000;
@ -55,7 +58,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
public override void Reset()
{
contentionStartPeriod = 14335; // + LateTiming;
contentionOffset = 0;
InterruptStart = 0;
contentionStartPeriod = 14335;// + contentionOffset; // + LateTiming;
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
screen = _machine.RAM0;
screenByteCtr = DisplayStart;
@ -160,6 +166,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
h += TstatesPerScanline - 128;
}
// offset floating bus table
var offset = 0;
if (offset != 0)
{
var fbt = floatingBusTable.ToArray();
for (int i = 0; i < FrameLength; i++)
{
var off = i + offset;
if (off < 0)
{
off = FrameLength - 1 + off;
}
else if (off >= FrameLength)
{
off = off - FrameLength;
}
fbt[off] = floatingBusTable[i];
}
floatingBusTable = fbt.ToArray();
}
//build bottom border
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
{
@ -177,4 +207,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
}
*/
}

View File

@ -21,7 +21,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
Spectrum = spectrum;
CPU = cpu;
ULADevice = new ULA48(this);
CPUMon = new CPUMonitor(this);
//ULADevice = new ULA48(this);
ULADevice = new Screen48(this);
BuzzerDevice = new Beeper(this);
BuzzerDevice.Init(44100, ULADevice.FrameLength);

View File

@ -91,6 +91,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
_cpu.ReadHardware = _machine.ReadPort;
_cpu.WriteHardware = _machine.WritePort;
_cpu.FetchDB = _machine.PushBus;
_cpu.OnExecFetch = _machine.CPUMon.OnExecFetch;
ser.Register<ITraceable>(_tracer);
ser.Register<IDisassemblable>(_cpu);