Files
ZXSpectrum48K/Desktop/DebuggerForm.cs

928 lines
41 KiB
C#

using System;
using System.Text;
using System.Windows.Forms;
using Core.Cpu;
using Core.Memory;
namespace Desktop
{
public partial class DebuggerForm : Form
{
private readonly Z80 _cpu;
private readonly MemoryBus _memoryBus;
private bool _isRunning = false;
private ushort? _breakpoint = null;
public DebuggerForm(Z80 cpu, MemoryBus memoryBus)
{
InitializeComponent();
_cpu = cpu;
_memoryBus = memoryBus;
// Set default memory view address
txtMemoryStart.Text = "0000";
UpdateDisplay();
UpdateStackView();
UpdateDisassemblyView();
}
private void btnStep_Click(object sender, EventArgs e)
{
try
{
_cpu.Step();
UpdateDisplay();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "CPU Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
UpdateDisplay();
}
}
private void btnRefreshMemory_Click(object sender, EventArgs e)
{
UpdateDisplay();
}
private async void btnRun_Click(object sender, EventArgs e)
{
// If it is already running, this button acts as a STOP button
if (_isRunning)
{
_isRunning = false;
btnRun.Text = "Run";
return;
}
// --- NEW: Parse the Breakpoint ---
if (!string.IsNullOrWhiteSpace(txtBreakpoint.Text))
{
if (ushort.TryParse(txtBreakpoint.Text, System.Globalization.NumberStyles.HexNumber, null, out ushort parsedBp))
{
_breakpoint = parsedBp;
}
else
{
MessageBox.Show("Invalid hex address in breakpoint!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
}
else
{
_breakpoint = null; // No text means no breakpoint
}
// ---------------------------------
// Start the run state
_isRunning = true;
btnRun.Text = "Stop";
// Fire up a background thread
await Task.Run(() =>
{
try
{
while (_isRunning)
{
// --- NEW: Breakpoint Check ---
// We check BEFORE stepping so it stops exactly on the instruction
if (_breakpoint.HasValue && _cpu.PC == _breakpoint.Value)
{
_isRunning = false;
break; // Cleanly exit the while loop
}
// -----------------------------
_cpu.Step();
}
}
catch (Exception ex)
{
_isRunning = false;
this.Invoke((MethodInvoker)delegate
{
MessageBox.Show(ex.Message, "CPU Break", MessageBoxButtons.OK, MessageBoxIcon.Information);
});
}
});
// Whether it stopped because of a breakpoint, a crash, or the user clicking "Stop",
// we MUST update the UI when the background thread finishes!
btnRun.Text = "Run";
UpdateDisplay();
}
private void btnReset_Click(object sender, EventArgs e)
{
// 1. Safely stop the emulator if it is currently in a Run loop
if (_isRunning)
{
_isRunning = false;
btnRun.Text = "Run";
}
// 2. Power cycle the CPU
_cpu.Reset();
// Note: A true hard reset also wipes the RAM.
// If you add a RAM clear method to your MemoryBus later, call it here!
_memoryBus.ClearRam(); //To Do
// 3. Clear the UI tracking lists
lstDisassembly.Items.Clear();
lstStack.Items.Clear();
// 4. Force a full UI refresh to show the clean slate
UpdateDisplay();
}
private void btnExit_Click(object sender, EventArgs e)
{
Environment.Exit(0);
}
// This is the master function that pulls state from the CPU
private void UpdateDisplay()
{
// 1. Update Registers (Formatting as 4-character Hex strings)
lblAF.Text = $"AF: {_cpu.AF.Word:X4}";
lblBC.Text = $"BC: {_cpu.BC.Word:X4}";
lblDE.Text = $"DE: {_cpu.DE.Word:X4}";
lblHL.Text = $"HL: {_cpu.HL.Word:X4}";
lblPC.Text = $"PC: {_cpu.PC:X4}";
lblSP.Text = $"SP: {_cpu.SP:X4}";
lblIX.Text = $"IX: {_cpu.IX.Word:X4}";
lblIY.Text = $"IY: {_cpu.IY.Word:X4}";
lblIff1.Text = $"IFF1: {_cpu.IFF1}";
lblIff2.Text = $"IFF2: {_cpu.IFF2}";
lblIE.Text = $"Interrupt Mode: {_cpu.InterruptMode}";
// 2. Update Flags & T-States
lblFlags.Text = $"Flags: {_cpu.GetFlagsString()}";
lblTStates.Text = $"T-States: {_cpu.TotalTStates}";
// 3. Update Memory Viewer
UpdateMemoryView();
UpdateStackView();
UpdateDisassemblyView();
}
private void UpdateMemoryView()
{
// Try to parse the hex string the user typed in
if (!ushort.TryParse(txtMemoryStart.Text, System.Globalization.NumberStyles.HexNumber, null, out ushort startAddress))
{
txtMemoryView.Text = "Invalid Hex Address!";
return;
}
StringBuilder sb = new StringBuilder();
// Read 100 bytes (or roughly 6 lines of 16 bytes)
for (int line = 0; line < 7; line++)
{
ushort currentAddr = (ushort)(startAddress + (line * 16));
// Print the address header for this line (e.g., "0000: ")
sb.Append($"{currentAddr:X4}: ");
// Print 16 bytes across
for (int i = 0; i < 16; i++)
{
// Careful not to overflow the 64k address space!
if (currentAddr + i <= 0xFFFF)
{
byte b = _memoryBus.Read((ushort)(currentAddr + i));
sb.Append($"{b:X2} ");
}
}
sb.AppendLine();
}
txtMemoryView.Text = sb.ToString();
}
private void UpdateStackView()
{
lstStack.Items.Clear();
int itemsToShow = 5;
ushort currentSp = _cpu.SP;
for (int i = 0; i < itemsToShow; i++)
{
// Prevent reading past 0xFFFF
if (currentSp >= 0xFFFE)
{
lstStack.Items.Add($"{currentSp:X4}: [End of Mem]");
break;
}
// Read the 16-bit value (Little-Endian: Low byte first, then High byte)
byte low = _memoryBus.Read(currentSp);
byte high = _memoryBus.Read((ushort)(currentSp + 1));
ushort value = (ushort)((high << 8) | low);
lstStack.Items.Add($"{currentSp:X4}: {value:X4}");
// Move to the next 16-bit word on the stack
currentSp += 2;
}
}
private void UpdateDisassemblyView()
{
lstDisassembly.Items.Clear();
ushort currentPc = _cpu.PC;
int instructionsToShow = 8;
for (int i = 0; i < instructionsToShow; i++)
{
byte opcode = _memoryBus.Read(currentPc);
string mnemonic;
int instructionLength = 1; // Default to 1
byte cbOp = 0;
switch (opcode)
{
case 0x00: mnemonic = "NOP"; break;
case 0x01:
ushort bcVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD BC, 0x{bcVal:X4}";
instructionLength = 3;
break;
case 0x02: mnemonic = "LD (BC), A"; break;
// --- 16-Bit Increments ---
case 0x03: mnemonic = "INC BC"; break;
case 0x08:
mnemonic = "EX AF, AF'";
break;
case 0x12: mnemonic = "LD (DE), A"; break;
case 0x13: mnemonic = "INC DE"; break;
case 0x33: mnemonic = "INC SP"; break;
// --- 16-Bit Decrements ---
case 0x0B: mnemonic = "DEC BC"; break;
case 0x3B: mnemonic = "DEC SP"; break;
// --- 8-Bit Increments ---
case 0x04: mnemonic = "INC B"; break;
case 0x0C: mnemonic = "INC C"; break;
case 0x14: mnemonic = "INC D"; break;
case 0x1C: mnemonic = "INC E"; break;
case 0x24: mnemonic = "INC H"; break;
case 0x2C: mnemonic = "INC L"; break;
case 0x34: mnemonic = "INC (HL)"; break;
case 0x3C: mnemonic = "INC A"; break;
// --- 8-Bit Decrements ---
case 0x05: mnemonic = "DEC B"; break;
case 0x0D: mnemonic = "DEC C"; break;
case 0x15: mnemonic = "DEC D"; break;
case 0x1D: mnemonic = "DEC E"; break;
case 0x25: mnemonic = "DEC H"; break;
case 0x2D: mnemonic = "DEC L"; break;
case 0x35: mnemonic = "DEC (HL)"; break;
case 0x3D: mnemonic = "DEC A"; break;
case 0x06:
byte bImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD B, 0x{bImm:X2}";
instructionLength = 2;
break;
// --- ADD HL, rr ---
case 0x09: mnemonic = "ADD HL, BC"; break;
case 0x19: mnemonic = "ADD HL, DE"; break;
case 0x29: mnemonic = "ADD HL, HL"; break;
case 0x39: mnemonic = "ADD HL, SP"; break;
case 0x0E:
byte cImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD C, 0x{cImm:X2}";
instructionLength = 2;
break;
case 0x0F:
mnemonic = "RRCA";
break;
case 0x10:
sbyte djnzOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
ushort djnzDest = (ushort)(currentPc + 2 + djnzOffset);
mnemonic = $"DJNZ 0x{djnzDest:X4}";
instructionLength = 2;
break;
case 0x11:
// LD DE, nn
byte deLow = _memoryBus.Read((ushort)(currentPc + 1));
byte deHigh = _memoryBus.Read((ushort)(currentPc + 2));
mnemonic = $"LD DE, 0x{deHigh:X2}{deLow:X2}";
instructionLength = 3;
break;
case 0x16:
byte dImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD D, 0x{dImm:X2}";
instructionLength = 2;
break;
case 0x18:
sbyte dUnconditional = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
// Calculate the target address based on the PC *after* this 2-byte instruction
ushort targetAddressUnconditional = (ushort)(currentPc + 2 + dUnconditional);
mnemonic = $"JR 0x{targetAddressUnconditional:X4}";
instructionLength = 2;
break;
case 0x1A:
mnemonic = "LD A, (DE)";
break;
case 0x1B:
mnemonic = "DEC DE";
break;
case 0x20:
sbyte jrOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
ushort destination = (ushort)(currentPc + 2 + jrOffset);
mnemonic = $"JR NZ, 0x{destination:X4}";
instructionLength = 2;
break;
case 0x21:
{
ushort hlImm = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD HL, 0x{hlImm:X4}";
instructionLength = 3;
break;
}
case 0x22:
ushort hlAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD (0x{hlAddr:X4}), HL";
instructionLength = 3;
break;
case 0x23:
mnemonic = "INC HL";
break;
case 0x26:
byte hImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD H, 0x{hImm:X2}";
instructionLength = 2;
break;
case 0x28:
sbyte jrZOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
ushort jrZDest = (ushort)(currentPc + 2 + jrZOffset);
mnemonic = $"JR Z, 0x{jrZDest:X4}";
instructionLength = 2;
break;
case 0x2A:
{
ushort addr2A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD HL, (0x{addr2A:X4})";
instructionLength = 3;
break;
}
case 0x2B:
mnemonic = "DEC HL";
break;
case 0x30:
sbyte jrNcOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
ushort dest = (ushort)(currentPc + 2 + jrNcOffset);
mnemonic = $"JR NC, 0x{dest:X4}";
instructionLength = 2;
break;
case 0x32:
{
ushort addr32 = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD (0x{addr32:X4}), A";
instructionLength = 3;
break;
}
case 0x36:
byte memValue = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD (HL), 0x{memValue:X2}";
instructionLength = 2;
break;
case 0x37:
mnemonic = "SCF";
break;
case 0x38:
sbyte dC = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
ushort targetC = (ushort)(currentPc + 2 + dC);
mnemonic = $"JR C, 0x{targetC:X4}";
instructionLength = 2;
break;
case 0x3A:
ushort addr3A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"LD A, (0x{addr3A:X4})";
instructionLength = 3;
break;
case 0x3E:
mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}";
instructionLength = 2;
break;
case 0x3F:
mnemonic = "CCF";
break;
// --- LD B, r ---
case 0x40: mnemonic = "LD B, B"; break;
case 0x41: mnemonic = "LD B, C"; break;
case 0x42: mnemonic = "LD B, D"; break;
case 0x43: mnemonic = "LD B, E"; break;
case 0x44: mnemonic = "LD B, H"; break;
case 0x45: mnemonic = "LD B, L"; break;
case 0x46: mnemonic = "LD B, (HL)"; break;
case 0x47: mnemonic = "LD B, A"; break;
// --- LD C, r ---
case 0x48: mnemonic = "LD C, B"; break;
case 0x49: mnemonic = "LD C, C"; break;
case 0x4A: mnemonic = "LD C, D"; break;
case 0x4B: mnemonic = "LD C, E"; break;
case 0x4C: mnemonic = "LD C, H"; break;
case 0x4D: mnemonic = "LD C, L"; break;
case 0x4E: mnemonic = "LD C, (HL)"; break;
case 0x4F: mnemonic = "LD C, A"; break;
// --- LD D, r ---
case 0x50: mnemonic = "LD D, B"; break;
case 0x51: mnemonic = "LD D, C"; break;
case 0x52: mnemonic = "LD D, D"; break;
case 0x53: mnemonic = "LD D, E"; break;
case 0x54: mnemonic = "LD D, H"; break;
case 0x55: mnemonic = "LD D, L"; break;
case 0x56: mnemonic = "LD D, (HL)"; break;
case 0x57: mnemonic = "LD D, A"; break;
// --- LD E, r ---
case 0x58: mnemonic = "LD E, B"; break;
case 0x59: mnemonic = "LD E, C"; break;
case 0x5A: mnemonic = "LD E, D"; break;
case 0x5B: mnemonic = "LD E, E"; break;
case 0x5C: mnemonic = "LD E, H"; break;
case 0x5D: mnemonic = "LD E, L"; break;
case 0x5E: mnemonic = "LD E, (HL)"; break;
case 0x5F: mnemonic = "LD E, A"; break;
// --- LD H, r ---
case 0x60: mnemonic = "LD H, B"; break;
case 0x61: mnemonic = "LD H, C"; break;
case 0x62: mnemonic = "LD H, D"; break;
case 0x63: mnemonic = "LD H, E"; break;
case 0x64: mnemonic = "LD H, H"; break;
case 0x65: mnemonic = "LD H, L"; break;
case 0x66: mnemonic = "LD H, (HL)"; break;
case 0x67: mnemonic = "LD H, A"; break;
// --- LD L, r ---
case 0x68: mnemonic = "LD L, B"; break;
case 0x69: mnemonic = "LD L, C"; break;
case 0x6A: mnemonic = "LD L, D"; break;
case 0x6B: mnemonic = "LD L, E"; break;
case 0x6C: mnemonic = "LD L, H"; break;
case 0x6D: mnemonic = "LD L, L"; break;
case 0x6E: mnemonic = "LD L, (HL)"; break;
case 0x6F: mnemonic = "LD L, A"; break;
// --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped)
case 0x70: mnemonic = "LD (HL), B"; break;
case 0x71: mnemonic = "LD (HL), C"; break;
case 0x72: mnemonic = "LD (HL), D"; break;
case 0x73: mnemonic = "LD (HL), E"; break;
case 0x74: mnemonic = "LD (HL), H"; break;
case 0x75: mnemonic = "LD (HL), L"; break;
case 0x77: mnemonic = "LD (HL), A"; break;
// --- LD A, r ---
case 0x78: mnemonic = "LD A, B"; break;
case 0x79: mnemonic = "LD A, C"; break;
case 0x7A: mnemonic = "LD A, D"; break;
case 0x7B: mnemonic = "LD A, E"; break;
case 0x7C: mnemonic = "LD A, H"; break;
case 0x7D: mnemonic = "LD A, L"; break;
case 0x7E: mnemonic = "LD A, (HL)"; break;
case 0x7F: mnemonic = "LD A, A"; break;
// --- ADD A, r ---
case 0x80: mnemonic = "ADD A, B"; break;
case 0x81: mnemonic = "ADD A, C"; break;
case 0x82: mnemonic = "ADD A, D"; break;
case 0x83: mnemonic = "ADD A, E"; break;
case 0x84: mnemonic = "ADD A, H"; break;
case 0x85: mnemonic = "ADD A, L"; break;
case 0x86: mnemonic = "ADD A, (HL)"; break;
case 0x87: mnemonic = "ADD A, A"; break;
// --- SUB r ---
case 0x90: mnemonic = "SUB B"; break;
case 0x91: mnemonic = "SUB C"; break;
case 0x92: mnemonic = "SUB D"; break;
case 0x93: mnemonic = "SUB E"; break;
case 0x94: mnemonic = "SUB H"; break;
case 0x95: mnemonic = "SUB L"; break;
case 0x96: mnemonic = "SUB (HL)"; break;
case 0x97: mnemonic = "SUB A"; break;
// --- SBC A, r ---
case 0x98: mnemonic = "SBC A, B"; break;
case 0x99: mnemonic = "SBC A, C"; break;
case 0x9A: mnemonic = "SBC A, D"; break;
case 0x9B: mnemonic = "SBC A, E"; break;
case 0x9C: mnemonic = "SBC A, H"; break;
case 0x9D: mnemonic = "SBC A, L"; break;
case 0x9E: mnemonic = "SBC A, (HL)"; break;
case 0x9F: mnemonic = "SBC A, A"; break;
// --- AND r ---
case 0xA0: mnemonic = "AND B"; break;
case 0xA1: mnemonic = "AND C"; break;
case 0xA2: mnemonic = "AND D"; break;
case 0xA3: mnemonic = "AND E"; break;
case 0xA4: mnemonic = "AND H"; break;
case 0xA5: mnemonic = "AND L"; break;
case 0xA6: mnemonic = "AND (HL)"; break;
case 0xA7: mnemonic = "AND A"; break;
// --- XOR r ---
case 0xA8: mnemonic = "XOR B"; break;
case 0xA9: mnemonic = "XOR C"; break;
case 0xAA: mnemonic = "XOR D"; break;
case 0xAB: mnemonic = "XOR E"; break;
case 0xAC: mnemonic = "XOR H"; break;
case 0xAD: mnemonic = "XOR L"; break;
case 0xAE: mnemonic = "XOR (HL)"; break;
case 0xAF: mnemonic = "XOR A"; break;
// --- OR r ---
case 0xB0: mnemonic = "OR B"; break;
case 0xB1: mnemonic = "OR C"; break;
case 0xB2: mnemonic = "OR D"; break;
case 0xB3: mnemonic = "OR E"; break;
case 0xB4: mnemonic = "OR H"; break;
case 0xB5: mnemonic = "OR L"; break;
case 0xB6: mnemonic = "OR (HL)"; break;
case 0xB7: mnemonic = "OR A"; break;
// --- CP r ---
case 0xB8: mnemonic = "CP B"; break;
case 0xB9: mnemonic = "CP C"; break;
case 0xBA: mnemonic = "CP D"; break;
case 0xBB: mnemonic = "CP E"; break;
case 0xBC: mnemonic = "CP H"; break;
case 0xBD: mnemonic = "CP L"; break;
case 0xBE: mnemonic = "CP (HL)"; break;
case 0xBF: mnemonic = "CP A"; break;
case 0xC1: mnemonic = "POP BC"; break;
case 0xC2:
case 0xCA:
case 0xD2:
case 0xDA:
case 0xE2:
case 0xEA:
case 0xF2:
case 0xFA:
{
// Read the 16-bit target address
ushort jumpAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
string condition = "";
switch (opcode)
{
case 0xC2: condition = "NZ"; break;
case 0xCA: condition = "Z"; break;
case 0xD2: condition = "NC"; break;
case 0xDA: condition = "C"; break;
case 0xE2: condition = "PO"; break;
case 0xEA: condition = "PE"; break;
case 0xF2: condition = "P"; break;
case 0xFA: condition = "M"; break;
}
mnemonic = $"JP {condition}, 0x{jumpAddr:X4}";
instructionLength = 3;
break;
}
// --- Conditional Returns ---
case 0xC0: mnemonic = "RET NZ"; break;
case 0xE0: mnemonic = "RET PO"; break;
case 0xE8: mnemonic = "RET PE"; break;
case 0xF0: mnemonic = "RET P"; break;
case 0xF8: mnemonic = "RET M"; break;
case 0xC3:
// JP nn
byte jpLow = _memoryBus.Read((ushort)(currentPc + 1));
byte jpHigh = _memoryBus.Read((ushort)(currentPc + 2));
mnemonic = $"JP 0x{jpHigh:X2}{jpLow:X2}";
instructionLength = 3;
break;
case 0xC4:
case 0xCC:
case 0xD4:
case 0xDC:
case 0xE4:
case 0xEC:
case 0xF4:
case 0xFC:
{
// Read the 16-bit target address
ushort callAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
string condition = "";
switch (opcode)
{
case 0xC4: condition = "NZ"; break;
case 0xCC: condition = "Z"; break;
case 0xD4: condition = "NC"; break;
case 0xDC: condition = "C"; break;
case 0xE4: condition = "PO"; break;
case 0xEC: condition = "PE"; break;
case 0xF4: condition = "P"; break;
case 0xFC: condition = "M"; break;
}
mnemonic = $"CALL {condition}, 0x{callAddr:X4}";
instructionLength = 3;
break;
}
case 0xc5:
mnemonic = "PUSH BC";
break;
case 0xC6:
{
byte addImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"ADD A, 0x{addImm:X2}";
instructionLength = 2;
break;
}
// --- RST Instructions ---
case 0xC7: mnemonic = "RST 00h"; break;
case 0xCF: mnemonic = "RST 08h"; break;
case 0xD7: mnemonic = "RST 10h"; break;
case 0xDF: mnemonic = "RST 18h"; break;
case 0xE7: mnemonic = "RST 20h"; break;
case 0xEF: mnemonic = "RST 28h"; break;
case 0xF7: mnemonic = "RST 30h"; break;
case 0xFF: mnemonic = "RST 38h"; break;
case 0xC8:
mnemonic = "RET Z";
break;
case 0xC9:
mnemonic = "RET";
break;
case 0xCB:
cbOp = _memoryBus.Read((ushort)(currentPc + 1));
if (cbOp == 0x7E)
{
mnemonic = "BIT 7, (HL)";
}
else if (cbOp == 0x86)
{
mnemonic = "RES 0, (HL)";
}
else if (cbOp == 0xAE)
{
mnemonic = "RES 5, (HL)";
}
else if (cbOp == 0xC6)
{
mnemonic = "SET 0, (HL)";
}
else
{
mnemonic = $"CB UNKNOWN (0x{cbOp:X2})";
}
instructionLength = 2;
break;
case 0xCD:
ushort callDest = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8));
mnemonic = $"CALL 0x{callDest:X4}";
instructionLength = 3;
break;
case 0xD0:
mnemonic = "RET NC";
break;
case 0xD1: mnemonic = "POP DE"; break;
case 0xD3:
byte outPort = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"OUT (0x{outPort:X2}), A";
instructionLength = 2;
break;
case 0xD9:
mnemonic = "EXX";
break;
case 0xd5:
mnemonic = "PUSH DE";
break;
case 0xD6:
byte subImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"SUB 0x{subImm:X2}";
instructionLength = 2;
break;
case 0xD8:
mnemonic = "RET C";
break;
case 0xDE:
byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"SBC A, 0x{sbcValue:X2}";
instructionLength = 2;
break;
case 0xE1: mnemonic = "POP HL"; break;
case 0xE3:
mnemonic = "EX (SP), HL";
break;
case 0xE5:
mnemonic = "PUSH HL";
break;
case 0xE6:
byte andImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"AND 0x{andImm:X2}";
instructionLength = 2;
break;
case 0xE9:
mnemonic = "JP (HL)";
break;
case 0xEB:
mnemonic = "EX DE, HL";
break;
case 0xED:
byte extendedOp = _memoryBus.Read((ushort)(currentPc + 1));
switch (extendedOp)
{
case 0x43:
ushort bcAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD (0x{bcAddr:X4}), BC";
instructionLength = 4;
break;
case 0x47:
mnemonic = "LD I, A";
instructionLength = 2; // 0xED + 0x47
break;
case 0x4B:
ushort addr4B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD BC, (0x{addr4B:X4})";
instructionLength = 4;
break;
case 0x52:
mnemonic = "SBC HL, DE";
instructionLength = 2; // ED 52
break;
case 0x53:
ushort deAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD (0x{deAddr:X4}), DE";
instructionLength = 4;
break;
case 0x56:
mnemonic = "IM 1";
instructionLength = 2;
break;
case 0x5B:
ushort addr5B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD DE, (0x{addr5B:X4})";
instructionLength = 4;
break;
case 0x73:
ushort addr73 = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD (0x{addr73:X4}), SP";
instructionLength = 4;
break;
case 0xB0:
mnemonic = "LDIR";
instructionLength = 2;
break;
case 0xB8:
mnemonic = "LDDR";
instructionLength = 2;
break;
default:
mnemonic = $"EXT UNKNOWN (ED {extendedOp:X2})";
instructionLength = 2; // Most ED instructions are 2 bytes, but some have operands!
break;
}
break;
case 0xF1: mnemonic = "POP AF"; break;
case 0xF3:
mnemonic = "DI";
break;
case 0xf5:
mnemonic = "PUSH AF";
break;
case 0xF6:
byte orImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"OR 0x{orImm:X2}";
instructionLength = 2;
break;
case 0xF9:
mnemonic = "LD SP, HL";
break;
case 0xFB:
mnemonic = "EI";
break;
case 0xFD:
{
byte fdOpcode = _memoryBus.Read((ushort)(currentPc + 1));
if (fdOpcode == 0x21) // LD IY, nn
{
ushort iyVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD IY, 0x{iyVal:X4}";
instructionLength = 4;
}
else if (fdOpcode == 0x35) // DEC (IY+d)
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string sign = d >= 0 ? "+" : ""; // Gives us a clean + or -
mnemonic = $"DEC (IY{sign}{d})";
instructionLength = 3;
}
else if (fdOpcode == 0x36) // LD (IY+d), n
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
byte n = _memoryBus.Read((ushort)(currentPc + 3));
string sign = d >= 0 ? "+" : "";
mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}";
instructionLength = 4;
}
else if (fdOpcode == 0x46) // LD B, (IY+d)
{
sbyte dB = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string signB = dB >= 0 ? "+" : "";
mnemonic = $"LD B, (IY{signB}{dB})";
instructionLength = 3;
}
else if (fdOpcode == 0x6E)
{
sbyte offsetL = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string signL = offsetL >= 0 ? "+" : "";
mnemonic = $"LD L, (IY{signL}{offsetL})";
instructionLength = 3;
}
else if (fdOpcode == 0x86) // ADD A, (IY+d)
{
sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string signAdd = dAdd >= 0 ? "+" : "";
mnemonic = $"ADD A, (IY{signAdd}{dAdd})";
instructionLength = 3;
}
else if (fdOpcode == 0xCB) // FD CB prefix
{
cbOp = _memoryBus.Read((ushort)(currentPc + 1));
int opGroup = cbOp >> 6;
int targetBit = (cbOp >> 3) & 0x07;
int regIdx = cbOp & 0x07;
// Map the 0-7 index directly to the Z80 register names
string[] regNames = { "B", "C", "D", "E", "H", "L", "(HL)", "A" };
string targetReg = regNames[regIdx];
if (opGroup == 1) mnemonic = $"BIT {targetBit}, {targetReg}";
else if (opGroup == 2) mnemonic = $"RES {targetBit}, {targetReg}";
else if (opGroup == 3) mnemonic = $"SET {targetBit}, {targetReg}";
else mnemonic = $"CB SHIFT/ROTATE (0x{cbOp:X2})";
instructionLength = 2;
}
else if (fdOpcode == 0x71) // LD (IY+d), C
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string sign = d >= 0 ? "+" : "";
mnemonic = $"LD (IY{sign}{d}), C";
instructionLength = 3;
}
else if (fdOpcode == 0x75) // LD (IY+d), L
{
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
string sign = d >= 0 ? "+" : "";
mnemonic = $"LD (IY{sign}{d}), L";
instructionLength = 3;
}
else
{
mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})";
instructionLength = 2; // Fallback so we don't freeze the UI
}
break;
}
case 0xFE:
byte cpImm = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"CP 0x{cpImm:X2}";
instructionLength = 2;
break;
default:
mnemonic = $"UNKNOWN (0x{opcode:X2})";
break;
}
lstDisassembly.Items.Add($"{currentPc:X4}: {mnemonic}");
// Advance the fake PC just for drawing the next line in the UI
currentPc += (ushort)instructionLength;
}
}
}
}