Started implementing new ULA implemetation (far more performant)

This commit is contained in:
Asnivor 2017-12-11 12:54:48 +00:00
parent 7532b4be8c
commit 2b988954ee
9 changed files with 795 additions and 8 deletions

View File

@ -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" />

View File

@ -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>

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -22,7 +22,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
CPU = cpu;
ReInitMemory();
ULADevice = new ULA48(this);
InitScreenConfig(borderType);
InitScreen();

View File

@ -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)

View File

@ -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