From 2b988954eea486964169acfb0aa1faab5d8123f5 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 12:54:48 +0000 Subject: [PATCH] Started implementing new ULA implemetation (far more performant) --- .../BizHawk.Emulation.Cores.csproj | 2 + .../SinclairSpectrum/Machine/SpectrumBase.cs | 17 +- .../SinclairSpectrum/Machine/ULABase.cs | 571 ++++++++++++++++++ .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 10 + .../Machine/ZXSpectrum48K/ZX48.Port.cs | 10 +- .../Machine/ZXSpectrum48K/ZX48.ULA.cs | 184 ++++++ .../Machine/ZXSpectrum48K/ZX48.cs | 4 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- .../Computers/SinclairSpectrum/readme.md | 3 + 9 files changed, 795 insertions(+), 8 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 638bf87f74..da148d52e1 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -271,7 +271,9 @@ + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 8de871a5e1..220c6c1e38 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -36,6 +36,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public RomData RomData { get; set; } + /// + /// The emulated ULA device + /// + public ULABase ULADevice { get; set; } + /// /// The spectrum buzzer/beeper /// @@ -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); } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs new file mode 100644 index 0000000000..015980b038 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -0,0 +1,571 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Another ULA implementation (maybe it will be more performant & accurate) + /// + public abstract class ULABase : IVideoProvider + { + #region ULA Configuration + + #region General + + /// + /// Length of the frame in T-States + /// + public int FrameLength; + + /// + /// Emulated clock speed + /// + public int ClockSpeed; + + /// + /// Whether machine is late or early timing model + /// + public bool LateTiming; //currently not implemented + + /// + /// The current cycle within the current frame + /// + public int CurrentTStateInFrame; + + + protected SpectrumBase _machine; + + #endregion + + #region Palettes + + /// + /// The standard ULA palette + /// + 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 + + /// + /// The number of T-States that the INT pin is simulated to be held low + /// + public int InterruptPeriod; + + #endregion + + #region Contention + + /// + /// T-State at which to start applying contention + /// + protected int contentionStartPeriod; + /// + /// T-State at which to end applying contention + /// + protected int contentionEndPeriod; + /// + /// T-State memory contention delay mapping + /// + public byte[] contentionTable; + + #endregion + + #region Screen Rendering + + /// + /// Video output buffer + /// + public int[] ScreenBuffer; + /// + /// Display memory + /// + protected byte[] screen; + /// + /// Attribute memory lookup (mapped 1:1 to screen for convenience) + /// + protected short[] attr; + /// + /// T-State display mapping + /// + protected short[] tstateToDisp; + /// + /// Table that stores T-State to screen/attribute address values + /// + protected short[] floatingBusTable; + /// + /// Cycle at which the last render update took place + /// + protected int lastTState; + /// + /// T-States elapsed since last render update + /// + protected int elapsedTStates; + /// + /// T-State of top left raster pixel + /// + protected int actualULAStart; + /// + /// Offset into display memory based on current T-State + /// + protected int screenByteCtr; + /// + /// Offset into current pixel of rasterizer + /// + protected int ULAByteCtr; + /// + /// The current border colour + /// + public int borderColour; + /// + /// Signs whether the colour flash is ON or OFF + /// + protected bool flashOn = false; + + /// + /// Last 8-bit bitmap read from display memory + /// (Floating bus implementation) + /// + protected int lastPixelValue; + /// + /// Last 8-bit attr val read from attribute memory + /// (Floating bus implementation) + /// + protected int lastAttrValue; + /// + /// Last 8-bit bitmap read from display memory+1 + /// (Floating bus implementation) + /// + protected int lastPixelValuePlusOne; + /// + /// Last 8-bit attr val read from attribute memory+1 + /// (Floating bus implementation) + /// + protected int lastAttrValuePlusOne; + + /// + /// Used to create the non-border display area + /// + protected int TtateAtLeft; + protected int TstateWidth; + protected int TstateAtTop; + protected int TstateHeight; + protected int TstateAtRight; + protected int TstateAtBottom; + + /// + /// Total T-States in one scanline + /// + protected int TstatesPerScanline; + /// + /// Total pixels in one scanline + /// + protected int ScanLineWidth; + /// + /// Total chars in one PRINT row + /// + protected int CharRows; + /// + /// Total chars in one PRINT column + /// + protected int CharCols; + /// + /// Total pixels in one display row + /// + protected int ScreenWidth; + /// + /// Total pixels in one display column + /// + protected int ScreenHeight; + /// + /// Total pixels in top border + /// + protected int BorderTopHeight; + /// + /// Total pixels in bottom border + /// + protected int BorderBottomHeight; + /// + /// Total pixels in left border width + /// + protected int BorderLeftWidth; + /// + /// Total pixels in right border width + /// + protected int BorderRightWidth; + /// + /// Memory address of display start + /// + protected int DisplayStart; + /// + /// Total number of bytes of display memory + /// + protected int DisplayLength; + /// + /// Memory address of attribute start + /// + protected int AttributeStart; + /// + /// Total number of bytes of attribute memory + /// + protected int AttributeLength; + + #endregion + + #region Interrupt + + /// + /// The longest instruction cycle count + /// + protected const int LONGEST_OP_CYCLES = 23; + + /// + /// Signs that an interrupt has been raised in this frame. + /// + protected bool InterruptRaised; + + /// + /// Signs that the interrupt signal has been revoked + /// + protected bool InterruptRevoked; + + /// + /// Resets the interrupt - this should happen every frame in order to raise + /// the VBLANK interrupt in the proceding frame + /// + public virtual void ResetInterrupt() + { + InterruptRaised = false; + InterruptRevoked = false; + } + + /// + /// Generates an interrupt in the current phase if needed + /// + /// + 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 + + /// + /// Resets the ULA chip + /// + public abstract void Reset(); + + /// + /// Builds the contention table for the emulated model + /// + public abstract void BuildContentionTable(); + + /// + /// Returns true if the given memory address should be contended + /// + /// + /// + public abstract bool IsContended(int addr); + + /// + /// Contends the machine for a given address + /// + /// + 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; + } + + /// + /// Resets render state once interrupt is generated + /// + public void ULAUpdateStart() + { + ULAByteCtr = 0; + screenByteCtr = DisplayStart; + lastTState = actualULAStart; + } + + /// + /// Builds the T-State to attribute map used with the floating bus + /// + 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 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 3cf3b09390..8ec377b16a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -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); } /// @@ -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); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 2a0080e1c8..aa92a35464 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -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; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs new file mode 100644 index 0000000000..d6ca8b4565 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -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 + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 81e1b07258..42dbbeaf49 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -22,7 +22,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CPU = cpu; ReInitMemory(); - + + ULADevice = new ULA48(this); + InitScreenConfig(borderType); InitScreen(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 75bb2fcca0..ac8fd29229 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Register(_tracer); ser.Register(_cpu); - ser.Register(_machine); + ser.Register(_machine.ULADevice); SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice); if (_machine.AYDevice != null) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index ce9e736487..c0147c1d3a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -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