From 2842af182fc63a0534642bf9e04d5ef13bc92c6d Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Fri, 24 Apr 2026 14:27:08 +0100 Subject: [PATCH] Ammendments to Z80 to make it system agnostic. --- Core/Cpu/Z80.cs | 100 ++------------------------------------ Desktop/Form1.Designer.cs | 13 +---- Desktop/Form1.cs | 66 +++++++++++++++++++++---- 3 files changed, 61 insertions(+), 118 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 6c765bc..ed9c80d 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -19,7 +19,6 @@ namespace Core.Cpu } } - public bool IsZexDocMode { get; set; } = false; //T-State counter public long TotalTStates { get; set; } @@ -55,19 +54,14 @@ namespace Core.Cpu // The Memory Bus private readonly IMemory _memory; private readonly IO_Bus _simpleIoBus; - public TapManager _tapManager; //External Timing interface public Func? WaitStateCallback { get; set; } - //Misc Variables - public bool EnableFastLoad { get; set; } = true; - - public Z80(IMemory memory, IO_Bus ioBus, TapManager tapManager) + public Z80(IMemory memory, IO_Bus ioBus) { _memory = memory; _simpleIoBus = ioBus; - _tapManager = tapManager; Reset(); } @@ -193,46 +187,7 @@ namespace Core.Cpu } public int Step() - { - if (IsZexDocMode && PC == 0x0005) - { - // CP/M System Call Hook - if (BC.Low == 2) // C = 2: Print a single character stored in register E - { - System.Diagnostics.Debug.Write((char)DE.Low); - } - else if (BC.Low == 9) // C = 9: Print a string starting at memory address DE, terminated by '$' - { - ushort addr = DE.Word; - while (true) - { - char c = (char)ReadMemory(addr++); - if (c == '$') break; - System.Diagnostics.Debug.Write(c); - } - } - - // Emulate a 'RET' instruction to return from the CALL 0x0005 - byte retLow = ReadMemory(SP); - SP++; - byte retHigh = ReadMemory(SP); - SP++; - PC = (ushort)((retHigh << 8) | retLow); - - return 10; // Skip normal execution for this cycle - } - if (PC == 0x0556) - { - if (EnableFastLoad) - { - HandleInstantTapeLoad(); - return 100; - } - else - { - _tapManager.Play(); - } - } + { // Fetch the next opcode and increment the Program Counter byte opcode = ReadMemory(PC++); @@ -280,56 +235,9 @@ namespace Core.Cpu // It was PUSHED to the stack exactly 1 instruction before the snapshot was saved. // So, we just pop it off the stack to resume execution! PC = Pop(); - } + } - private void HandleInstantTapeLoad() - { - // 1. Grab the next block from the virtual cassette - byte[] block = _tapManager.GetNextBlock(); - if (block == null) return; // Tape ended unexpectedly - - // 2. Verify the block type. - // The ROM passes the expected flag (0x00 for Header, 0xFF for Data) in the A register. - byte expectedFlag = AF.High; - if (block[0] != expectedFlag) - { - // Tape loading error! Simulate a failure by clearing the Carry flag and returning. - AF.Low &= unchecked((byte)~0x01); - ExecuteRet(); - return; - } - - // 3. Copy the data block straight into the Spectrum's RAM! - // DE holds the number of bytes the ROM *wants*. We copy that much, skipping the Flag byte. - int bytesToCopy = DE.Word; - for (int i = 0; i < bytesToCopy; i++) - { - WriteMemory((ushort)(IX.Word + i), block[i + 1]); - } - - // 4. Update the registers exactly how the ROM would after a successful load - IX.Word = (ushort)(IX.Word + bytesToCopy); - DE.Word = 0; - - // 5. Simulate the Checksum Match (Accumulator becomes 0) - AF.High = 0x00; - - // 6. Set Carry Flag (Bit 0) for Success, and Zero Flag (Bit 6) for Checksum Match - AF.Low |= 0x41; // 0x41 is binary 0100 0001 (Zero and Carry both set) - - // 7. Simulate a standard 'RET' instruction to exit the LD-BYTES routine safely - ExecuteRet(); - } - - // A quick helper to simulate a RET instruction manually - private void ExecuteRet() - { - byte pcLow = ReadMemory(SP); - SP++; - byte pcHigh = ReadMemory(SP); - SP++; - PC = (ushort)((pcHigh << 8) | pcLow); - } + public string GetFlagsString() { diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index cdf4e35..ab37e12 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -43,7 +43,6 @@ stepToolStripMenuItem = new ToolStripMenuItem(); resetToolStripMenuItem1 = new ToolStripMenuItem(); optionsToolStripMenuItem = new ToolStripMenuItem(); - fastLoadingToolStripMenuItem = new ToolStripMenuItem(); HighSpeedToolStripMenuItem = new ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)picScreen).BeginInit(); menuStrip1.SuspendLayout(); @@ -156,20 +155,11 @@ // // optionsToolStripMenuItem // - optionsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { fastLoadingToolStripMenuItem, HighSpeedToolStripMenuItem }); + optionsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { HighSpeedToolStripMenuItem }); optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; optionsToolStripMenuItem.Size = new Size(75, 24); optionsToolStripMenuItem.Text = "Options"; // - // fastLoadingToolStripMenuItem - // - fastLoadingToolStripMenuItem.Checked = true; - fastLoadingToolStripMenuItem.CheckState = CheckState.Checked; - fastLoadingToolStripMenuItem.Name = "fastLoadingToolStripMenuItem"; - fastLoadingToolStripMenuItem.Size = new Size(224, 26); - fastLoadingToolStripMenuItem.Text = "Fast TAP Loading"; - fastLoadingToolStripMenuItem.Click += fastLoadingToolStripMenuItem_Click; - // // HighSpeedToolStripMenuItem // HighSpeedToolStripMenuItem.Name = "HighSpeedToolStripMenuItem"; @@ -212,7 +202,6 @@ private ToolStripMenuItem stepToolStripMenuItem; private ToolStripMenuItem resetToolStripMenuItem1; private ToolStripMenuItem optionsToolStripMenuItem; - private ToolStripMenuItem fastLoadingToolStripMenuItem; private ToolStripMenuItem HighSpeedToolStripMenuItem; } } diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 3f0f4c7..205655f 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -50,7 +50,7 @@ namespace Desktop _memoryBus.CrapRAMData(); byte[] romData = RomLoader.Load("48.rom"); _memoryBus.LoadRom(romData); - _cpu = new Z80(_memoryBus, _simpleIoBus, _tapManager); + _cpu = new Z80(_memoryBus, _simpleIoBus); _cpu.WaitStateCallback = _ula.GetContentionDelay; } @@ -112,6 +112,13 @@ namespace Desktop long tStatesBefore = _cpu.TotalTStates; + // --- HARDWARE INTERCEPTS --- + if (_cpu.PC == 0x0556 && _tapManager.HasBlocks) + { + HandleInstantTapeLoad(); + _cpu.TotalTStates += 100; // Charge some arbitrary time for the fast load + } + // --- Execute Instruction --- _cpu.Step(); @@ -218,6 +225,52 @@ namespace Desktop }); } + private void HandleInstantTapeLoad() + { + byte[] block = _tapManager.GetNextBlock(); // Your original Queue.Dequeue() method + if (block == null) return; + + byte expectedFlag = _cpu.AF.High; + if (block[0] != expectedFlag) + { + // Block mismatch (e.g. found data when looking for a header) + // Clear the carry flag to simulate a tape loading error + _cpu.AF.Low &= unchecked((byte)~0x01); + ForceRet(); + return; + } + + int bytesToCopy = _cpu.DE.Word; + + // Safety check just in case the TAP file is malformed + int actualBytes = Math.Min(bytesToCopy, block.Length - 1); + + // Directly inject the payload into the RAM + for (int i = 0; i < actualBytes; i++) + { + _memoryBus.Write((ushort)(_cpu.IX.Word + i), block[i + 1]); + } + + // --- Update Registers to match a PERFECT ROM Load --- + _cpu.IX.Word = (ushort)(_cpu.IX.Word + actualBytes); + _cpu.DE.Word = (ushort)(bytesToCopy - actualBytes); // Should hit 0 + _cpu.HL.Word = 0x0000; // The ROM zeroes this out after calculating checksums + + // Checksum Zero (A = 0x00), Success Flag / Zero Flag Set (F = 0x41) + _cpu.AF.Word = 0x0041; + + ForceRet(); + } + + private void ForceRet() + { + // Pop the return address off the stack just like a real RET instruction + byte pcLow = _memoryBus.Read(_cpu.SP); + _cpu.SP++; + byte pcHigh = _memoryBus.Read(_cpu.SP); + _cpu.SP++; + _cpu.PC = (ushort)((pcHigh << 8) | pcLow); + } private void UpdateScreenBitmap() @@ -246,21 +299,14 @@ namespace Desktop if (ofd.ShowDialog() == DialogResult.OK) { byte[] tapBytes = File.ReadAllBytes(ofd.FileName); - _cpu._tapManager.LoadTapData(tapBytes); + _tapManager.LoadTapData(tapBytes); tapeLoaded = true; } } _isPaused = false; } - private void fastLoadingToolStripMenuItem_Click(object sender, EventArgs e) - { - // Toggle the checkmark UI - fastLoadingToolStripMenuItem.Checked = !fastLoadingToolStripMenuItem.Checked; - - // Tell the CPU to enable or disable the ROM hijack - _cpu.EnableFastLoad = fastLoadingToolStripMenuItem.Checked; - } + private void openSNAToolStripMenuItem_Click(object sender, EventArgs e) { _isPaused = true;