Ammendments to Z80 to make it system agnostic.

This commit is contained in:
2026-04-24 14:27:08 +01:00
parent 0e8462c8a5
commit 2842af182f
3 changed files with 61 additions and 118 deletions

View File

@@ -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<ushort, long, int>? 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()
{

View File

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

View File

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