Started implementing new ULA implemetation (far more performant)
This commit is contained in:
parent
7532b4be8c
commit
2b988954ee
|
@ -271,7 +271,9 @@
|
|||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeBlockSetPlayer.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeDataBlockPlayer.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Hardware\TapeFilePlayer.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\ULABase.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum16K\ZX16.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.ULA.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\SoundProviderMixer.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
|
||||
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />
|
||||
|
|
|
@ -36,6 +36,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
/// </summary>
|
||||
public RomData RomData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The emulated ULA device
|
||||
/// </summary>
|
||||
public ULABase ULADevice { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The spectrum buzzer/beeper
|
||||
/// </summary>
|
||||
|
@ -119,18 +124,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
var curr = CPU.TotalExecutedCycles;
|
||||
|
||||
while (CurrentFrameCycle <= UlaFrameCycleCount)
|
||||
while (CurrentFrameCycle <= ULADevice.FrameLength) // UlaFrameCycleCount)
|
||||
{
|
||||
// check for interrupt
|
||||
CheckForInterrupt(CurrentFrameCycle);
|
||||
ULADevice.CheckForInterrupt(CurrentFrameCycle);
|
||||
|
||||
// run a single CPU instruction
|
||||
CPU.ExecuteOne();
|
||||
|
||||
// run a rendering cycle according to the current CPU cycle count
|
||||
/*
|
||||
var lastCycle = CurrentFrameCycle;
|
||||
RenderScreen(LastRenderedULACycle + 1, lastCycle);
|
||||
LastRenderedULACycle = lastCycle;
|
||||
*/
|
||||
|
||||
// update AY
|
||||
if (AYDevice != null)
|
||||
|
@ -141,6 +148,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow;
|
||||
LastRenderedULACycle = OverFlow;
|
||||
|
||||
ULADevice.UpdateScreenBuffer(ULADevice.FrameLength);
|
||||
|
||||
BuzzerDevice.EndFrame();
|
||||
|
||||
TapeDevice.CPUFrameCompleted();
|
||||
|
@ -149,7 +158,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
// setup for next frame
|
||||
OverFlow = CurrentFrameCycle % UlaFrameCycleCount;
|
||||
ResetInterrupt();
|
||||
ULADevice.ResetInterrupt();
|
||||
FrameCompleted = true;
|
||||
|
||||
if (FrameCount % FlashToggleFrames == 0)
|
||||
|
@ -157,7 +166,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
_flashPhase = !_flashPhase;
|
||||
}
|
||||
|
||||
RenderScreen(0, OverFlow);
|
||||
//RenderScreen(0, OverFlow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -0,0 +1,571 @@
|
|||
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
/// <summary>
|
||||
/// Another ULA implementation (maybe it will be more performant & accurate)
|
||||
/// </summary>
|
||||
public abstract class ULABase : IVideoProvider
|
||||
{
|
||||
#region ULA Configuration
|
||||
|
||||
#region General
|
||||
|
||||
/// <summary>
|
||||
/// Length of the frame in T-States
|
||||
/// </summary>
|
||||
public int FrameLength;
|
||||
|
||||
/// <summary>
|
||||
/// Emulated clock speed
|
||||
/// </summary>
|
||||
public int ClockSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// Whether machine is late or early timing model
|
||||
/// </summary>
|
||||
public bool LateTiming; //currently not implemented
|
||||
|
||||
/// <summary>
|
||||
/// The current cycle within the current frame
|
||||
/// </summary>
|
||||
public int CurrentTStateInFrame;
|
||||
|
||||
|
||||
protected SpectrumBase _machine;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Palettes
|
||||
|
||||
/// <summary>
|
||||
/// The standard ULA palette
|
||||
/// </summary>
|
||||
private static readonly int[] ULAPalette =
|
||||
{
|
||||
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||||
Colors.ARGB(0x00, 0x00, 0xD7), // Blue
|
||||
Colors.ARGB(0xD7, 0x00, 0x00), // Red
|
||||
Colors.ARGB(0xD7, 0x00, 0xD7), // Magenta
|
||||
Colors.ARGB(0x00, 0xD7, 0x00), // Green
|
||||
Colors.ARGB(0x00, 0xD7, 0xD7), // Cyan
|
||||
Colors.ARGB(0xD7, 0xD7, 0x00), // Yellow
|
||||
Colors.ARGB(0xD7, 0xD7, 0xD7), // White
|
||||
Colors.ARGB(0x00, 0x00, 0x00), // Bright Black
|
||||
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||||
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||||
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||||
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||||
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||||
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||||
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interrupts
|
||||
|
||||
/// <summary>
|
||||
/// The number of T-States that the INT pin is simulated to be held low
|
||||
/// </summary>
|
||||
public int InterruptPeriod;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Contention
|
||||
|
||||
/// <summary>
|
||||
/// T-State at which to start applying contention
|
||||
/// </summary>
|
||||
protected int contentionStartPeriod;
|
||||
/// <summary>
|
||||
/// T-State at which to end applying contention
|
||||
/// </summary>
|
||||
protected int contentionEndPeriod;
|
||||
/// <summary>
|
||||
/// T-State memory contention delay mapping
|
||||
/// </summary>
|
||||
public byte[] contentionTable;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Screen Rendering
|
||||
|
||||
/// <summary>
|
||||
/// Video output buffer
|
||||
/// </summary>
|
||||
public int[] ScreenBuffer;
|
||||
/// <summary>
|
||||
/// Display memory
|
||||
/// </summary>
|
||||
protected byte[] screen;
|
||||
/// <summary>
|
||||
/// Attribute memory lookup (mapped 1:1 to screen for convenience)
|
||||
/// </summary>
|
||||
protected short[] attr;
|
||||
/// <summary>
|
||||
/// T-State display mapping
|
||||
/// </summary>
|
||||
protected short[] tstateToDisp;
|
||||
/// <summary>
|
||||
/// Table that stores T-State to screen/attribute address values
|
||||
/// </summary>
|
||||
protected short[] floatingBusTable;
|
||||
/// <summary>
|
||||
/// Cycle at which the last render update took place
|
||||
/// </summary>
|
||||
protected int lastTState;
|
||||
/// <summary>
|
||||
/// T-States elapsed since last render update
|
||||
/// </summary>
|
||||
protected int elapsedTStates;
|
||||
/// <summary>
|
||||
/// T-State of top left raster pixel
|
||||
/// </summary>
|
||||
protected int actualULAStart;
|
||||
/// <summary>
|
||||
/// Offset into display memory based on current T-State
|
||||
/// </summary>
|
||||
protected int screenByteCtr;
|
||||
/// <summary>
|
||||
/// Offset into current pixel of rasterizer
|
||||
/// </summary>
|
||||
protected int ULAByteCtr;
|
||||
/// <summary>
|
||||
/// The current border colour
|
||||
/// </summary>
|
||||
public int borderColour;
|
||||
/// <summary>
|
||||
/// Signs whether the colour flash is ON or OFF
|
||||
/// </summary>
|
||||
protected bool flashOn = false;
|
||||
|
||||
/// <summary>
|
||||
/// Last 8-bit bitmap read from display memory
|
||||
/// (Floating bus implementation)
|
||||
/// </summary>
|
||||
protected int lastPixelValue;
|
||||
/// <summary>
|
||||
/// Last 8-bit attr val read from attribute memory
|
||||
/// (Floating bus implementation)
|
||||
/// </summary>
|
||||
protected int lastAttrValue;
|
||||
/// <summary>
|
||||
/// Last 8-bit bitmap read from display memory+1
|
||||
/// (Floating bus implementation)
|
||||
/// </summary>
|
||||
protected int lastPixelValuePlusOne;
|
||||
/// <summary>
|
||||
/// Last 8-bit attr val read from attribute memory+1
|
||||
/// (Floating bus implementation)
|
||||
/// </summary>
|
||||
protected int lastAttrValuePlusOne;
|
||||
|
||||
/// <summary>
|
||||
/// Used to create the non-border display area
|
||||
/// </summary>
|
||||
protected int TtateAtLeft;
|
||||
protected int TstateWidth;
|
||||
protected int TstateAtTop;
|
||||
protected int TstateHeight;
|
||||
protected int TstateAtRight;
|
||||
protected int TstateAtBottom;
|
||||
|
||||
/// <summary>
|
||||
/// Total T-States in one scanline
|
||||
/// </summary>
|
||||
protected int TstatesPerScanline;
|
||||
/// <summary>
|
||||
/// Total pixels in one scanline
|
||||
/// </summary>
|
||||
protected int ScanLineWidth;
|
||||
/// <summary>
|
||||
/// Total chars in one PRINT row
|
||||
/// </summary>
|
||||
protected int CharRows;
|
||||
/// <summary>
|
||||
/// Total chars in one PRINT column
|
||||
/// </summary>
|
||||
protected int CharCols;
|
||||
/// <summary>
|
||||
/// Total pixels in one display row
|
||||
/// </summary>
|
||||
protected int ScreenWidth;
|
||||
/// <summary>
|
||||
/// Total pixels in one display column
|
||||
/// </summary>
|
||||
protected int ScreenHeight;
|
||||
/// <summary>
|
||||
/// Total pixels in top border
|
||||
/// </summary>
|
||||
protected int BorderTopHeight;
|
||||
/// <summary>
|
||||
/// Total pixels in bottom border
|
||||
/// </summary>
|
||||
protected int BorderBottomHeight;
|
||||
/// <summary>
|
||||
/// Total pixels in left border width
|
||||
/// </summary>
|
||||
protected int BorderLeftWidth;
|
||||
/// <summary>
|
||||
/// Total pixels in right border width
|
||||
/// </summary>
|
||||
protected int BorderRightWidth;
|
||||
/// <summary>
|
||||
/// Memory address of display start
|
||||
/// </summary>
|
||||
protected int DisplayStart;
|
||||
/// <summary>
|
||||
/// Total number of bytes of display memory
|
||||
/// </summary>
|
||||
protected int DisplayLength;
|
||||
/// <summary>
|
||||
/// Memory address of attribute start
|
||||
/// </summary>
|
||||
protected int AttributeStart;
|
||||
/// <summary>
|
||||
/// Total number of bytes of attribute memory
|
||||
/// </summary>
|
||||
protected int AttributeLength;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interrupt
|
||||
|
||||
/// <summary>
|
||||
/// The longest instruction cycle count
|
||||
/// </summary>
|
||||
protected const int LONGEST_OP_CYCLES = 23;
|
||||
|
||||
/// <summary>
|
||||
/// Signs that an interrupt has been raised in this frame.
|
||||
/// </summary>
|
||||
protected bool InterruptRaised;
|
||||
|
||||
/// <summary>
|
||||
/// Signs that the interrupt signal has been revoked
|
||||
/// </summary>
|
||||
protected bool InterruptRevoked;
|
||||
|
||||
/// <summary>
|
||||
/// Resets the interrupt - this should happen every frame in order to raise
|
||||
/// the VBLANK interrupt in the proceding frame
|
||||
/// </summary>
|
||||
public virtual void ResetInterrupt()
|
||||
{
|
||||
InterruptRaised = false;
|
||||
InterruptRevoked = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates an interrupt in the current phase if needed
|
||||
/// </summary>
|
||||
/// <param name="currentCycle"></param>
|
||||
public virtual void CheckForInterrupt(int currentCycle)
|
||||
{
|
||||
if (InterruptRevoked)
|
||||
{
|
||||
// interrupt has already been handled
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentCycle < InterruptPeriod)
|
||||
{
|
||||
// interrupt does not need to be raised yet
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentCycle > InterruptPeriod + LONGEST_OP_CYCLES)
|
||||
{
|
||||
// interrupt should have already been raised and the cpu may or
|
||||
// may not have caught it. The time has passed so revoke the signal
|
||||
InterruptRevoked = true;
|
||||
//CPU.IFF1 = true;
|
||||
_machine.CPU.FlagI = false;
|
||||
//CPU.NonMaskableInterruptPending = true;
|
||||
|
||||
}
|
||||
|
||||
if (InterruptRaised)
|
||||
{
|
||||
// INT is raised but not yet revoked
|
||||
// CPU has NOT handled it yet
|
||||
return;
|
||||
}
|
||||
|
||||
// if CPU is masking the interrupt do not raise it
|
||||
//if (!CPU.IFF1)
|
||||
//return;
|
||||
|
||||
// Raise the interrupt
|
||||
InterruptRaised = true;
|
||||
//CPU.IFF1 = false;
|
||||
//CPU.IFF2 = false;
|
||||
_machine.CPU.FlagI = true;
|
||||
//FrameCount++;
|
||||
ULAUpdateStart();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Construction & Initialisation
|
||||
|
||||
public ULABase(SpectrumBase machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Resets the ULA chip
|
||||
/// </summary>
|
||||
public abstract void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// Builds the contention table for the emulated model
|
||||
/// </summary>
|
||||
public abstract void BuildContentionTable();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given memory address should be contended
|
||||
/// </summary>
|
||||
/// <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
|
||||
/// </summary>
|
||||
public void ULAUpdateStart()
|
||||
{
|
||||
ULAByteCtr = 0;
|
||||
screenByteCtr = DisplayStart;
|
||||
lastTState = actualULAStart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the T-State to attribute map used with the floating bus
|
||||
/// </summary>
|
||||
public void BuildAttributeMap()
|
||||
{
|
||||
int start = DisplayStart;
|
||||
|
||||
for (int f = 0; f < DisplayLength; f++, start++)
|
||||
{
|
||||
int addrH = start >> 8; //div by 256
|
||||
int addrL = start % 256;
|
||||
|
||||
int pixelY = (addrH & 0x07);
|
||||
pixelY |= (addrL & (0xE0)) >> 2;
|
||||
pixelY |= (addrH & (0x18)) << 3;
|
||||
|
||||
int attrIndex_Y = AttributeStart + ((pixelY >> 3) << 5);// pixel/8 * 32
|
||||
|
||||
addrL = start % 256;
|
||||
int pixelX = addrL & (0x1F);
|
||||
|
||||
attr[f] = (short)(attrIndex_Y + pixelX);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateScreenBuffer(int _tstates)
|
||||
{
|
||||
if (_tstates < actualULAStart)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (_tstates >= FrameLength)
|
||||
{
|
||||
_tstates = FrameLength - 1;
|
||||
}
|
||||
|
||||
//the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna
|
||||
elapsedTStates = (_tstates + 1 - lastTState);
|
||||
|
||||
//It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state.
|
||||
|
||||
int numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0);
|
||||
|
||||
int pixelData;
|
||||
int pixel2Data = 0xff;
|
||||
int attrData;
|
||||
int attr2Data;
|
||||
int bright;
|
||||
int ink;
|
||||
int paper;
|
||||
int flash;
|
||||
|
||||
for (int i = 0; i < numBytes; i++)
|
||||
{
|
||||
if (tstateToDisp[lastTState] > 1)
|
||||
{
|
||||
screenByteCtr = tstateToDisp[lastTState] - 16384; //adjust for actual screen offset
|
||||
|
||||
pixelData = _machine.FetchScreenMemory((ushort)screenByteCtr); //screen[screenByteCtr];
|
||||
attrData = _machine.FetchScreenMemory((ushort)(attr[screenByteCtr] - 16384)); //screen[attr[screenByteCtr] - 16384];
|
||||
|
||||
lastPixelValue = pixelData;
|
||||
lastAttrValue = attrData;
|
||||
|
||||
bright = (attrData & 0x40) >> 3;
|
||||
flash = (attrData & 0x80) >> 7;
|
||||
ink = (attrData & 0x07);
|
||||
paper = ((attrData >> 3) & 0x7);
|
||||
int paletteInk = ULAPalette[ink + bright];
|
||||
int palettePaper = ULAPalette[paper + bright];
|
||||
|
||||
if (flashOn && (flash != 0)) //swap paper and ink when flash is on
|
||||
{
|
||||
int temp = paletteInk;
|
||||
paletteInk = palettePaper;
|
||||
palettePaper = temp;
|
||||
}
|
||||
|
||||
for (int a = 0; a < 8; ++a)
|
||||
{
|
||||
if ((pixelData & 0x80) != 0)
|
||||
{
|
||||
ScreenBuffer[ULAByteCtr++] = paletteInk;
|
||||
lastAttrValue = ink;
|
||||
//pixelIsPaper = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScreenBuffer[ULAByteCtr++] = palettePaper;
|
||||
lastAttrValue = paper;
|
||||
}
|
||||
pixelData <<= 1;
|
||||
}
|
||||
}
|
||||
else if (tstateToDisp[lastTState] == 1)
|
||||
{
|
||||
int bor = ULAPalette[borderColour];
|
||||
|
||||
for (int g = 0; g < 8; g++)
|
||||
ScreenBuffer[ULAByteCtr++] = bor;
|
||||
}
|
||||
lastTState += 4;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IVideoProvider
|
||||
|
||||
private int _virtualWidth;
|
||||
private int _virtualHeight;
|
||||
private int _bufferWidth;
|
||||
private int _bufferHeight;
|
||||
|
||||
public int BackgroundColor
|
||||
{
|
||||
get { return ULAPalette[borderColour]; }
|
||||
}
|
||||
|
||||
public int VirtualWidth
|
||||
{
|
||||
get { return _virtualWidth; }
|
||||
set { _virtualWidth = value; }
|
||||
}
|
||||
|
||||
public int VirtualHeight
|
||||
{
|
||||
get { return _virtualHeight; }
|
||||
set { _virtualHeight = value; }
|
||||
}
|
||||
|
||||
public int BufferWidth
|
||||
{
|
||||
get { return _bufferWidth; }
|
||||
set { _bufferWidth = value; }
|
||||
}
|
||||
|
||||
public int BufferHeight
|
||||
{
|
||||
get { return _bufferHeight; }
|
||||
set { _bufferHeight = value; }
|
||||
}
|
||||
|
||||
public int VsyncNumerator
|
||||
{
|
||||
get { return ClockSpeed; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public int VsyncDenominator
|
||||
{
|
||||
get { return FrameLength; }
|
||||
}
|
||||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
return ScreenBuffer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Attribution
|
||||
|
||||
/*
|
||||
* Based on code from ArjunNair's Zero emulator (MIT Licensed)
|
||||
* https://github.com/ArjunNair/Zero-Emulator
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2009 Arjun Nair
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -63,6 +63,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
var bank = Memory[divisor];
|
||||
var index = addr % 0x4000;
|
||||
bank[index] = value;
|
||||
|
||||
// update ULA screen buffer if necessary
|
||||
if ((addr & 49152) == 16384)
|
||||
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -96,13 +100,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
// Do nothing - we cannot write to ROM
|
||||
return;
|
||||
}
|
||||
/*
|
||||
else if (addr < 0xC000)
|
||||
{
|
||||
// possible contended RAM
|
||||
var delay = GetContentionValue(CurrentFrameCycle);
|
||||
CPU.TotalExecutedCycles += delay;
|
||||
}
|
||||
*/
|
||||
|
||||
// apply contention if necessry
|
||||
if (ULADevice.IsContended(addr))
|
||||
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
|
||||
|
||||
WriteBus(addr, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -132,13 +132,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
// Technically the ULA should respond to every even I/O address
|
||||
bool lowBitReset = (port & 0x01) == 0;
|
||||
|
||||
ContendPort(port);
|
||||
ULADevice.Contend(port);
|
||||
|
||||
// Only even addresses address the ULA
|
||||
if (lowBitReset)
|
||||
{
|
||||
// store the last OUT byte
|
||||
LastULAOutByte = value;
|
||||
CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle];
|
||||
|
||||
/*
|
||||
Bit 7 6 5 4 3 2 1 0
|
||||
|
@ -148,13 +149,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
*/
|
||||
|
||||
// Border - LSB 3 bits hold the border colour
|
||||
BorderColour = value & BORDER_BIT;
|
||||
if (ULADevice.borderColour != (value & BORDER_BIT))
|
||||
ULADevice.UpdateScreenBuffer(CurrentFrameCycle);
|
||||
|
||||
ULADevice.borderColour = value & BORDER_BIT;
|
||||
|
||||
// Buzzer
|
||||
BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0);
|
||||
|
||||
// Tape
|
||||
TapeDevice.ProcessMicBit((value & MIC_BIT) != 0);
|
||||
|
||||
CPU.TotalExecutedCycles += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
|
||||
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||
{
|
||||
class ULA48 : ULABase
|
||||
{
|
||||
#region Construction
|
||||
|
||||
public ULA48(SpectrumBase machine)
|
||||
: base(machine)
|
||||
{
|
||||
InterruptPeriod = 32;
|
||||
FrameLength = 69888;
|
||||
ClockSpeed = 3500000;
|
||||
|
||||
contentionTable = new byte[70930];
|
||||
floatingBusTable = new short[70930];
|
||||
for (int f = 0; f < 70930; f++)
|
||||
floatingBusTable[f] = -1;
|
||||
|
||||
CharRows = 24;
|
||||
CharCols = 32;
|
||||
ScreenWidth = 256;
|
||||
ScreenHeight = 192;
|
||||
BorderTopHeight = 48;
|
||||
BorderBottomHeight = 56;
|
||||
BorderLeftWidth = 48;
|
||||
BorderRightWidth = 48;
|
||||
DisplayStart = 16384;
|
||||
DisplayLength = 6144;
|
||||
AttributeStart = 22528;
|
||||
AttributeLength = 768;
|
||||
borderColour = 7;
|
||||
ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth;
|
||||
|
||||
TstatesPerScanline = 224;
|
||||
TstateAtTop = BorderTopHeight * TstatesPerScanline;
|
||||
TstateAtBottom = BorderBottomHeight * TstatesPerScanline;
|
||||
tstateToDisp = new short[FrameLength];
|
||||
|
||||
ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border
|
||||
+ ScanLineWidth * ScreenHeight //border + main + border of 192 lines
|
||||
+ ScanLineWidth * BorderBottomHeight]; //56 lines of border
|
||||
|
||||
attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped
|
||||
|
||||
BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth;
|
||||
BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight;
|
||||
VirtualHeight = BufferHeight;
|
||||
VirtualWidth = BufferWidth;
|
||||
|
||||
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Misc Operations
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
contentionStartPeriod = 14335; // + LateTiming;
|
||||
contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline);
|
||||
screen = _machine.Memory[1];
|
||||
screenByteCtr = DisplayStart;
|
||||
ULAByteCtr = 0;
|
||||
actualULAStart = 14340 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming;
|
||||
lastTState = actualULAStart;
|
||||
BuildAttributeMap();
|
||||
BuildContentionTable();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Contention Methods
|
||||
|
||||
public override bool IsContended(int addr)
|
||||
{
|
||||
if ((addr & 49152) == 16384)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void BuildContentionTable()
|
||||
{
|
||||
int t = contentionStartPeriod;
|
||||
while (t < contentionEndPeriod)
|
||||
{
|
||||
//for 128 t-states
|
||||
for (int i = 0; i < 128; i += 8)
|
||||
{
|
||||
contentionTable[t++] = 6;
|
||||
contentionTable[t++] = 5;
|
||||
contentionTable[t++] = 4;
|
||||
contentionTable[t++] = 3;
|
||||
contentionTable[t++] = 2;
|
||||
contentionTable[t++] = 1;
|
||||
contentionTable[t++] = 0;
|
||||
contentionTable[t++] = 0;
|
||||
}
|
||||
t += (TstatesPerScanline - 128); //24 tstates of right border + left border + 48 tstates of retrace
|
||||
}
|
||||
|
||||
//build top half of tstateToDisp table
|
||||
//vertical retrace period
|
||||
for (t = 0; t < actualULAStart; t++)
|
||||
tstateToDisp[t] = 0;
|
||||
|
||||
//next 48 are actual border
|
||||
while (t < actualULAStart + (TstateAtTop))
|
||||
{
|
||||
//border(24t) + screen (128t) + border(24t) = 176 tstates
|
||||
for (int g = 0; g < 176; g++)
|
||||
tstateToDisp[t++] = 1;
|
||||
|
||||
//horizontal retrace
|
||||
for (int g = 176; g < TstatesPerScanline; g++)
|
||||
tstateToDisp[t++] = 0;
|
||||
}
|
||||
|
||||
//build middle half of display
|
||||
int _x = 0;
|
||||
int _y = 0;
|
||||
int scrval = 2;
|
||||
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline))
|
||||
{
|
||||
//left border
|
||||
for (int g = 0; g < 24; g++)
|
||||
tstateToDisp[t++] = 1;
|
||||
|
||||
//screen
|
||||
for (int g = 24; g < 24 + 128; g++)
|
||||
{
|
||||
//Map screenaddr to tstate
|
||||
if (g % 4 == 0)
|
||||
{
|
||||
scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2));
|
||||
_x += 8;
|
||||
}
|
||||
tstateToDisp[t++] = (short)scrval;
|
||||
}
|
||||
_y++;
|
||||
|
||||
//right border
|
||||
for (int g = 24 + 128; g < 24 + 128 + 24; g++)
|
||||
tstateToDisp[t++] = 1;
|
||||
|
||||
//horizontal retrace
|
||||
for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 48; g++)
|
||||
tstateToDisp[t++] = 0;
|
||||
}
|
||||
|
||||
int h = contentionStartPeriod + 3;
|
||||
while (h < contentionEndPeriod + 3)
|
||||
{
|
||||
for (int j = 0; j < 128; j += 8)
|
||||
{
|
||||
floatingBusTable[h] = tstateToDisp[h + 2]; //screen address
|
||||
floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; //attr address
|
||||
floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; //screen address + 1
|
||||
floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; //attr address + 1
|
||||
h += 8;
|
||||
}
|
||||
h += TstatesPerScanline - 128;
|
||||
}
|
||||
|
||||
//build bottom border
|
||||
while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom))
|
||||
{
|
||||
//border(24t) + screen (128t) + border(24t) = 176 tstates
|
||||
for (int g = 0; g < 176; g++)
|
||||
tstateToDisp[t++] = 1;
|
||||
|
||||
//horizontal retrace
|
||||
for (int g = 176; g < TstatesPerScanline; g++)
|
||||
tstateToDisp[t++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -22,7 +22,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
CPU = cpu;
|
||||
|
||||
ReInitMemory();
|
||||
|
||||
|
||||
ULADevice = new ULA48(this);
|
||||
|
||||
InitScreenConfig(borderType);
|
||||
InitScreen();
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
|||
|
||||
ser.Register<ITraceable>(_tracer);
|
||||
ser.Register<IDisassemblable>(_cpu);
|
||||
ser.Register<IVideoProvider>(_machine);
|
||||
ser.Register<IVideoProvider>(_machine.ULADevice);
|
||||
|
||||
SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice);
|
||||
if (_machine.AYDevice != null)
|
||||
|
|
|
@ -23,9 +23,12 @@ At this moment this is still *very* experimental and needs a lot more work.
|
|||
|
||||
### Not working
|
||||
* IDebuggable
|
||||
* ZX Spectrum Plus3 emulation
|
||||
* Default keyboard keymappings (you have to configure yourself in the core controller settings)
|
||||
* Manual tape device control (at the moment the tape device detects when the spectrum goes into 'loadbytes' mode and auto-plays the tape. This is not ideal and manual control should be implemented so the user can start/stop manually, return to zero etc..)
|
||||
* Only standard spectrum tape blocks currently work. Any fancy SpeedLock encoded (and similar) blocks do not
|
||||
|
||||
### Known bugs
|
||||
* Audible 'popping' from the emulated buzzer after a load state operation
|
||||
|
||||
-Asnivor
|
||||
|
|
Loading…
Reference in New Issue