diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 0e1b210..0969367 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -5,6 +5,9 @@ namespace Core.Cpu { public partial class Z80 { + //T-State counter + public long TotalTStates { get; set; } + // Main Register Set public RegisterPair AF; public RegisterPair BC; @@ -52,9 +55,24 @@ namespace Core.Cpu { // Fetch the next opcode and increment the Program Counter byte opcode = _memory.Read(PC++); + int tStates = ExecuteOpcode(opcode); + TotalTStates += tStates; // Decode and execute - return ExecuteOpcode(opcode); + return tStates; + } + + public string GetFlagsString() + { + byte f = AF.Low; + return $"S:{(f >> 7) & 1} " + + $"Z:{(f >> 6) & 1} " + + $"Y:{(f >> 5) & 1} " + // Undocumented flag + $"H:{(f >> 4) & 1} " + + $"X:{(f >> 3) & 1} " + // Undocumented flag + $"P/V:{(f >> 2) & 1} " + + $"N:{(f >> 1) & 1} " + + $"C:{f & 1}"; } private int ExecuteOpcode(byte opcode) diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs new file mode 100644 index 0000000..97598d1 --- /dev/null +++ b/Desktop/DebuggerForm.Designer.cs @@ -0,0 +1,243 @@ +namespace Desktop +{ + partial class DebuggerForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs new file mode 100644 index 0000000..2efccc2 --- /dev/null +++ b/Desktop/DebuggerForm.cs @@ -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; + } + } + } +} \ No newline at end of file diff --git a/Desktop/DebuggerForm.resx b/Desktop/DebuggerForm.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Desktop/DebuggerForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 0af0e38..978d9e3 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -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) {