From 0ef8b9f3eb8a80300ad3d221acf3f32aad7a1cf4 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Thu, 9 Apr 2026 17:31:56 +0100 Subject: [PATCH] Implemented a load more Z80 OpCodes. Added a breakpoint to the debugger --- Core/Cpu/Z80.cs | 68 +++++++++++++++++++++++++++++++- Desktop/DebuggerForm.Designer.cs | 40 ++++++++++++++++++- Desktop/DebuggerForm.cs | 59 +++++++++++++++++++++------ Desktop/DebuggerForm.resx | 3 ++ 4 files changed, 154 insertions(+), 16 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 80c8ade..eb197f9 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -160,6 +160,37 @@ namespace Core.Cpu if (result < 0) AF.Low |= 0x01; } + private byte Dec8(byte value) + { + byte result = (byte)(value - 1); + + // Store the existing Carry flag so we can preserve it + byte carry = (byte)(AF.Low & 0x01); + + // Clear all flags + AF.Low = 0; + + // Sign Flag (Bit 7) + if ((result & 0x80) != 0) AF.Low |= 0x80; + + // Zero Flag (Bit 6) + if (result == 0) AF.Low |= 0x40; + + // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 (happens if the lower nibble was 0) + if ((value & 0x0F) == 0) AF.Low |= 0x10; + + // Parity/Overflow Flag (Bit 2) - Set if the original value was 0x80 (maximum negative) + if (value == 0x80) AF.Low |= 0x04; + + // Subtract Flag (Bit 1) - ALWAYS SET for decrements + AF.Low |= 0x02; + + // Restore the original Carry Flag (Bit 0) + AF.Low |= carry; + + return result; + } + private void Cp(byte value) { byte a = AF.High; @@ -262,7 +293,17 @@ namespace Core.Cpu return 7; case 0x23: // INC HL HL.Word++; - return 6; + return 6; + case 0x28: // JR Z, e + offset = (sbyte)FetchByte(); + + // Check if the Zero Flag (Bit 6) IS set + if ((AF.Low & 0x40) != 0) + { + PC = (ushort)(PC + offset); + return 12; // Jump taken + } + return 7; // Jump not taken case 0x2B: // DEC HL HL.Word--; return 6; @@ -276,6 +317,17 @@ namespace Core.Cpu return 12; // Jump taken } return 7; // Jump not taken + case 0x35: // DEC (HL) + // Read the current byte from memory + byte memValue = _memory.Read(HL.Word); + + // Decrement it and update flags + byte decremented = Dec8(memValue); + + // Write the new value back to memory + _memory.Write(HL.Word, decremented); + + return 11; // Takes 11 T-States case 0x36: // LD (HL), n byte nValue = FetchByte(); _memory.Write(HL.Word, nValue); @@ -310,6 +362,20 @@ namespace Core.Cpu _ioBus.Write(portAddress, AF.High); return 11; // Takes 11 T-States + case 0xD9: // EXX + ushort tempBC = BC.Word; + BC.Word = BC_Prime.Word; + BC_Prime.Word = tempBC; + + ushort tempDE = DE.Word; + DE.Word = DE_Prime.Word; + DE_Prime.Word = tempDE; + + ushort tempHL = HL.Word; + HL.Word = HL_Prime.Word; + HL_Prime.Word = tempHL; + + return 4; // Takes 4 T-States case 0xDE: // SBC A, n Sbc(FetchByte()); return 7; diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index db37b5d..689e36f 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -44,6 +44,10 @@ lstDisassembly = new ListBox(); lstStack = new ListBox(); btnExit = new Button(); + saveFileDialog1 = new SaveFileDialog(); + label1 = new Label(); + txtBreakpoint = new TextBox(); + label2 = new Label(); SuspendLayout(); // // lblAF @@ -128,7 +132,7 @@ // // txtMemoryStart // - txtMemoryStart.Location = new Point(238, 10); + txtMemoryStart.Location = new Point(300, 21); txtMemoryStart.Margin = new Padding(2); txtMemoryStart.Name = "txtMemoryStart"; txtMemoryStart.Size = new Size(121, 27); @@ -161,7 +165,7 @@ // // btnRefreshMemory // - btnRefreshMemory.Location = new Point(376, 7); + btnRefreshMemory.Location = new Point(425, 21); btnRefreshMemory.Margin = new Padding(2); btnRefreshMemory.Name = "btnRefreshMemory"; btnRefreshMemory.Size = new Size(90, 27); @@ -208,11 +212,39 @@ btnExit.UseVisualStyleBackColor = true; btnExit.Click += btnExit_Click; // + // label1 + // + label1.AutoSize = true; + label1.Location = new Point(289, 288); + label1.Name = "label1"; + label1.Size = new Size(81, 20); + label1.TabIndex = 19; + label1.Text = "Breakpoint"; + // + // txtBreakpoint + // + txtBreakpoint.Location = new Point(376, 281); + txtBreakpoint.Name = "txtBreakpoint"; + txtBreakpoint.Size = new Size(125, 27); + txtBreakpoint.TabIndex = 20; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(233, 21); + label2.Name = "label2"; + label2.Size = new Size(62, 20); + label2.TabIndex = 21; + label2.Text = "Address"; + // // DebuggerForm // AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(928, 350); + Controls.Add(label2); + Controls.Add(txtBreakpoint); + Controls.Add(label1); Controls.Add(btnExit); Controls.Add(lstStack); Controls.Add(lstDisassembly); @@ -254,6 +286,10 @@ private ListBox lstStack; private Button btnExit; public ListBox lstDisassembly; + private SaveFileDialog saveFileDialog1; + private Label label1; + private TextBox txtBreakpoint; + private Label label2; //private TextBox textBox4; } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 169b671..4124c19 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -11,6 +11,7 @@ namespace Desktop private readonly Z80 _cpu; private readonly MemoryBus _memoryBus; private bool _isRunning = false; + private ushort? _breakpoint = null; public DebuggerForm(Z80 cpu, MemoryBus memoryBus) { @@ -57,18 +58,45 @@ namespace Desktop 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 so the Windows UI doesn't freeze + // Fire up a background thread await Task.Run(() => { try { - // Free-run the CPU until the flag is flipped or it crashes! 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(); } } @@ -76,22 +104,17 @@ namespace Desktop { _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(); - } + // 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 btnExit_Click(object sender, EventArgs e) @@ -186,8 +209,6 @@ namespace Desktop { 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; @@ -220,6 +241,12 @@ namespace Desktop case 0x23: mnemonic = "INC HL"; 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 0x2B: mnemonic = "DEC HL"; break; @@ -229,6 +256,9 @@ namespace Desktop mnemonic = $"JR NC, 0x{dest:X4}"; instructionLength = 2; break; + case 0x35: + mnemonic = "DEC (HL)"; + break; case 0x36: byte memValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD (HL), 0x{memValue:X2}"; @@ -268,6 +298,9 @@ namespace Desktop mnemonic = $"OUT (0x{outPort:X2}), A"; instructionLength = 2; break; + case 0xD9: + mnemonic = "EXX"; + break; case 0xDE: byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"SBC A, 0x{sbcValue:X2}"; diff --git a/Desktop/DebuggerForm.resx b/Desktop/DebuggerForm.resx index 8b2ff64..5261a14 100644 --- a/Desktop/DebuggerForm.resx +++ b/Desktop/DebuggerForm.resx @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file