Added Debugger

This commit is contained in:
2026-04-08 22:36:26 +01:00
parent d857c88d12
commit 5550eb8c91
5 changed files with 563 additions and 4 deletions

243
Desktop/DebuggerForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,243 @@
namespace Desktop
{
partial class DebuggerForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
lblAF = new Label();
lblBC = new Label();
lblDE = new Label();
lblHL = new Label();
lblPC = new Label();
lblSP = new Label();
lblFlags = new Label();
lblTStates = new Label();
txtMemoryStart = new TextBox();
btnStep = new Button();
btnRun = new Button();
btnRefreshMemory = new Button();
txtMemoryView = new RichTextBox();
lstDisassembly = new ListBox();
lstStack = new ListBox();
btnExit = new Button();
SuspendLayout();
//
// lblAF
//
lblAF.AutoSize = true;
lblAF.Location = new Point(12, 9);
lblAF.Name = "lblAF";
lblAF.Size = new Size(33, 25);
lblAF.TabIndex = 0;
lblAF.Text = "AF";
//
// lblBC
//
lblBC.AutoSize = true;
lblBC.Location = new Point(11, 60);
lblBC.Name = "lblBC";
lblBC.Size = new Size(33, 25);
lblBC.TabIndex = 1;
lblBC.Text = "BC";
//
// lblDE
//
lblDE.AutoSize = true;
lblDE.Location = new Point(12, 125);
lblDE.Name = "lblDE";
lblDE.Size = new Size(34, 25);
lblDE.TabIndex = 2;
lblDE.Text = "DE";
//
// lblHL
//
lblHL.AutoSize = true;
lblHL.Location = new Point(13, 187);
lblHL.Name = "lblHL";
lblHL.Size = new Size(33, 25);
lblHL.TabIndex = 3;
lblHL.Text = "HL";
//
// lblPC
//
lblPC.AutoSize = true;
lblPC.Location = new Point(11, 250);
lblPC.Name = "lblPC";
lblPC.Size = new Size(33, 25);
lblPC.TabIndex = 4;
lblPC.Text = "PC";
//
// lblSP
//
lblSP.AutoSize = true;
lblSP.Location = new Point(11, 315);
lblSP.Name = "lblSP";
lblSP.Size = new Size(32, 25);
lblSP.TabIndex = 6;
lblSP.Text = "SP";
//
// lblFlags
//
lblFlags.AutoSize = true;
lblFlags.Location = new Point(110, 65);
lblFlags.Name = "lblFlags";
lblFlags.Size = new Size(53, 25);
lblFlags.TabIndex = 7;
lblFlags.Text = "Flags";
//
// lblTStates
//
lblTStates.AutoSize = true;
lblTStates.Location = new Point(110, 9);
lblTStates.Name = "lblTStates";
lblTStates.Size = new Size(75, 25);
lblTStates.TabIndex = 8;
lblTStates.Text = "T-States";
//
// txtMemoryStart
//
txtMemoryStart.Location = new Point(298, 12);
txtMemoryStart.Name = "txtMemoryStart";
txtMemoryStart.Size = new Size(150, 31);
txtMemoryStart.TabIndex = 9;
txtMemoryStart.Text = "Memory Start";
txtMemoryStart.TextAlign = HorizontalAlignment.Center;
txtMemoryStart.TextChanged += btnRefreshMemory_Click;
//
// btnStep
//
btnStep.Location = new Point(11, 394);
btnStep.Name = "btnStep";
btnStep.Size = new Size(112, 34);
btnStep.TabIndex = 12;
btnStep.Text = "Step";
btnStep.UseVisualStyleBackColor = true;
btnStep.Click += btnStep_Click;
//
// btnRun
//
btnRun.Location = new Point(152, 394);
btnRun.Name = "btnRun";
btnRun.Size = new Size(112, 34);
btnRun.TabIndex = 13;
btnRun.Text = "Run";
btnRun.UseVisualStyleBackColor = true;
//
// btnRefreshMemory
//
btnRefreshMemory.Location = new Point(470, 9);
btnRefreshMemory.Name = "btnRefreshMemory";
btnRefreshMemory.Size = new Size(112, 34);
btnRefreshMemory.TabIndex = 14;
btnRefreshMemory.Text = "Refresh Memory";
btnRefreshMemory.UseVisualStyleBackColor = true;
btnRefreshMemory.Click += btnRefreshMemory_Click;
//
// txtMemoryView
//
txtMemoryView.Location = new Point(110, 100);
txtMemoryView.Name = "txtMemoryView";
txtMemoryView.Size = new Size(472, 266);
txtMemoryView.TabIndex = 15;
txtMemoryView.Text = "Memory View Window";
//
// lstDisassembly
//
lstDisassembly.FormattingEnabled = true;
lstDisassembly.ItemHeight = 25;
lstDisassembly.Location = new Point(635, 12);
lstDisassembly.Name = "lstDisassembly";
lstDisassembly.Size = new Size(231, 354);
lstDisassembly.TabIndex = 16;
//
// lstStack
//
lstStack.FormattingEnabled = true;
lstStack.ItemHeight = 25;
lstStack.Location = new Point(904, 12);
lstStack.Name = "lstStack";
lstStack.Size = new Size(231, 354);
lstStack.TabIndex = 17;
//
// btnExit
//
btnExit.Location = new Point(1023, 391);
btnExit.Name = "btnExit";
btnExit.Size = new Size(112, 34);
btnExit.TabIndex = 18;
btnExit.Text = "Full Exit";
btnExit.UseVisualStyleBackColor = true;
btnExit.Click += btnExit_Click;
//
// DebuggerForm
//
AutoScaleDimensions = new SizeF(10F, 25F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1157, 437);
Controls.Add(btnExit);
Controls.Add(lstStack);
Controls.Add(lstDisassembly);
Controls.Add(txtMemoryView);
Controls.Add(btnRefreshMemory);
Controls.Add(btnRun);
Controls.Add(btnStep);
Controls.Add(txtMemoryStart);
Controls.Add(lblTStates);
Controls.Add(lblFlags);
Controls.Add(lblSP);
Controls.Add(lblPC);
Controls.Add(lblHL);
Controls.Add(lblDE);
Controls.Add(lblBC);
Controls.Add(lblAF);
Name = "DebuggerForm";
Text = "DebuggerForm";
ResumeLayout(false);
PerformLayout();
}
#endregion
private Label lblAF;
private Label lblBC;
private Label lblDE;
private Label lblHL;
private Label lblPC;
private Label lblSP;
private Label lblFlags;
private Label lblTStates;
private TextBox txtMemoryStart;
private Button btnStep;
private Button btnRun;
private Button btnRefreshMemory;
private RichTextBox txtMemoryView;
private ListBox lstDisassembly;
private ListBox lstStack;
private Button btnExit;
//private TextBox textBox4;
}
}

176
Desktop/DebuggerForm.cs Normal file
View File

@@ -0,0 +1,176 @@
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;
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 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();
}
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();
// The Z80 stack starts at 0xFFFF and grows downwards.
// If SP is at the very top (e.g., 0xFFFF), we don't want to read past the end of memory and crash!
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 long
// This switch statement will grow as you add more opcodes to the CPU!
switch (opcode)
{
case 0x00:
mnemonic = "NOP";
break;
case 0x3E:
// LD A, n (Loads the next byte into register A)
byte nextByte = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"LD A, 0x{nextByte:X2}";
instructionLength = 2; // This instruction takes up 2 bytes
break;
default:
mnemonic = $"UNKNOWN (0x{opcode:X2})";
break;
}
// Add to the list box
lstDisassembly.Items.Add($"{currentPc:X4}: {mnemonic}");
// Advance to the start of the next instruction
currentPc += (ushort)instructionLength;
}
}
}
}

120
Desktop/DebuggerForm.resx Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -7,8 +7,8 @@ namespace Desktop
{
public partial class Form1 : Form
{
private Z80 _cpu;
private MemoryBus _memoryBus;
private Z80 _cpu = null!;
private MemoryBus _memoryBus = null!;
public Form1()
{
@@ -33,7 +33,9 @@ namespace Desktop
// 4. Initialize the CPU with the populated memory
_cpu = new Z80(_memoryBus);
MessageBox.Show("ROM loaded and CPU initialized successfully!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
DebuggerForm debugger = new DebuggerForm(_cpu, _memoryBus);
debugger.Show();
//MessageBox.Show("ROM loaded and CPU initialized successfully!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{