Files
ZXSpectrum48K/Desktop/DebuggerForm.cs

290 lines
10 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;
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;
}
// Start the run state
_isRunning = true;
btnRun.Text = "Stop";
// Fire up a background thread so the Windows UI doesn't freeze
await Task.Run(() =>
{
try
{
// Free-run the CPU until the flag is flipped or it crashes!
while (_isRunning)
{
_cpu.Step();
}
}
catch (Exception ex)
{
_isRunning = false;
// We are on a background thread. We MUST use Invoke to tell the
// main UI thread to update the labels and show the message box!
this.Invoke((MethodInvoker)delegate
{
btnRun.Text = "Run";
UpdateDisplay();
MessageBox.Show(ex.Message, "CPU Break", MessageBoxButtons.OK, MessageBoxIcon.Information);
});
}
});
// If the user clicked Stop manually (no exception thrown), we still want to update the UI
if (!_isRunning)
{
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}";
// 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();
// THIS is the critical link! It forces the top of the list
// to always be exactly where the CPU currently is.
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
switch (opcode)
{
case 0x00: mnemonic = "NOP"; 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 0x2B:
mnemonic = "DEC HL";
break;
case 0x36:
byte memValue = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD (HL), 0x{memValue:X2}";
instructionLength = 2;
break;
case 0x3E:
mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}";
instructionLength = 2;
break;
case 0x47:
mnemonic = "LD B, A";
break;
case 0x62:
mnemonic = "LD H, D";
break;
case 0x6B:
mnemonic = "LD L, E";
break;
case 0xAF:
mnemonic = "XOR A";
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 0xD3:
byte outPort = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"OUT (0x{outPort:X2}), A";
instructionLength = 2;
break;
case 0xDE:
byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"SBC A, 0x{sbcValue:X2}";
instructionLength = 2;
break;
case 0xED:
byte extendedOp = _memoryBus.Read((ushort)(currentPc + 1));
switch (extendedOp)
{
// Example: ED 47 is LD I, A
case 0x47:
mnemonic = "LD I, A";
instructionLength = 2; // 0xED + 0x47
break;
// Example: ED B0 is LDIR (a massive block copy instruction)
case 0xB0:
mnemonic = "LDIR";
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 0xF3:
mnemonic = "DI";
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;
}
}
}
}