From ed0772f27aa2cf13d0f53eca0d780775a69dbf86 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Mon, 13 Apr 2026 17:28:41 +0100 Subject: [PATCH] Implemented a load more Z80 OpCodes. Added IX, IY and Interrupts to Debugger --- Core/Cpu/Z80.cs | 113 ++++++++++++++++++++++++-- Desktop/DebuggerForm.Designer.cs | 131 +++++++++++++++++++------------ Desktop/DebuggerForm.cs | 57 ++++++++++++++ 3 files changed, 247 insertions(+), 54 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 746433a..ca3a57d 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -11,8 +11,8 @@ namespace Core.Cpu public int InterruptMode { get; private set; } = 0; // Defaults to 0 on power-up // Interrupt Flip-Flops - public bool IFF1; - public bool IFF2; + public bool IFF1 { get; private set; } = false; + public bool IFF2 { get; private set; } = false; // Main Register Set public RegisterPair AF; @@ -298,6 +298,17 @@ namespace Core.Cpu return (bits % 2) == 0; } + private void Push(ushort value) + { + // High byte goes first + SP--; + _memory.Write(SP, (byte)(value >> 8)); + + // Low byte goes second + SP--; + _memory.Write(SP, (byte)(value & 0xFF)); + } + private int ExecuteOpcode(byte opcode) { sbyte offset = 0; @@ -311,6 +322,18 @@ namespace Core.Cpu case 0x04: // INC B BC.High = Inc8(BC.High); return 4; + case 0x10: // DJNZ d + sbyte djnzOffset = (sbyte)FetchByte(); + + BC.High--; // Decrement the B register + + if (BC.High != 0) + { + PC = (ushort)(PC + djnzOffset); + return 13; // Jump taken + } + + return 8; // Loop finished, no jump case 0x11: //LD DE, nn DE.Word = FetchWord(); return 10; @@ -366,6 +389,12 @@ namespace Core.Cpu return 12; // Jump taken } return 7; // Jump not taken + case 0x32: // LD (nn), A + { + ushort destAddress = FetchWord(); + _memory.Write(destAddress, AF.High); + return 13; + } case 0x35: // DEC (HL) // Read the current byte from memory byte memValue = _memory.Read(HL.Word); @@ -394,6 +423,9 @@ namespace Core.Cpu case 0x6B: // LD L, E HL.Low = DE.Low; return 4; + case 0x77: // LD (HL), A + _memory.Write(HL.Word, AF.High); + return 7; case 0xA7: // AND A And(AF.High); return 4; @@ -407,6 +439,11 @@ namespace Core.Cpu case 0xC3: PC = FetchWord(); return 10; + case 0xCD: // CALL nn + ushort callAddress = FetchWord(); + Push(PC); + PC = callAddress; + return 17; case 0xD3: // OUT (n), A byte portOffset = FetchByte(); @@ -447,6 +484,10 @@ namespace Core.Cpu case 0xF9: // LD SP, HL SP = HL.Word; // (Use SP.Word = HL.Word if you made SP a RegisterPair) return 6; + case 0xFB: // EI + IFF1 = true; + IFF2 = true; + return 4; case 0xFD: return ExecuteFDPrefix(); default: @@ -457,6 +498,7 @@ namespace Core.Cpu { // Fetch the actual extended instruction byte extendedOpcode = _memory.Read(PC++); + byte val = 0; switch (extendedOpcode) { @@ -478,10 +520,37 @@ namespace Core.Cpu return 20; case 0x56: // IM 1 InterruptMode = 1; - return 8; // Takes 8 T-States + return 8; + case 0xB0: // LDIR + // 1. Read byte from (HL) + val = _memory.Read(HL.Word); + + // 2. Write byte to (DE) + _memory.Write(DE.Word, val); + + // 3. Increment memory pointers, Decrement byte counter + HL.Word++; + DE.Word++; + BC.Word--; + + // 4. Update Flags + // Preserve S (0x80), Z (0x40), and C (0x01). + // H (0x10) and N (0x02) are always reset to 0. + AF.Low &= 0xC1; + + // P/V Flag (Bit 2) is set to 1 if BC is not 0 + if (BC.Word != 0) + { + AF.Low |= 0x04; + + // Rewind the PC so the CPU executes this instruction again! + PC -= 2; + return 21; // Looping + } + return 16; case 0xB8: // LDDR // 1. Read byte from (HL) - byte val = _memory.Read(HL.Word); + val = _memory.Read(HL.Word); // 2. Write byte to (DE) _memory.Write(DE.Word, val); @@ -515,13 +584,47 @@ namespace Core.Cpu private int ExecuteFDPrefix() { byte opcode = FetchByte(); + ushort targetAddress = 0; + byte memVal = 0; switch (opcode) { case 0x21: // LD IY, nn IY.Word = FetchWord(); - return 14; // Takes 14 T-States + return 14; + case 0x35: // DEC (IY+d) + sbyte offset = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset); + // Read, decrement using your existing helper, and write back + memVal = _memory.Read(targetAddress); + byte decVal = Dec8(memVal); + _memory.Write(targetAddress, decVal); + return 23; + case 0x75: // LD (IY+d), L + sbyte offset75 = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset75); + // Write the low byte of HL to memory + _memory.Write(targetAddress, HL.Low); + return 19; + case 0xCB: // The FD CB nested prefix + { + sbyte offsetCB = (sbyte)FetchByte(); // This is the '01' + byte bitOpcode = FetchByte(); // This is the 'CE' + targetAddress = (ushort)(IY.Word + offsetCB); + + switch (bitOpcode) + { + case 0xCE: // SET 1, (IY+d) + memVal = _memory.Read(targetAddress); + memVal |= 0x02; // 0x02 is Binary 0000 0010 (Bit 1) + _memory.Write(targetAddress, memVal); + return 23; // Takes 23 T-States + + default: + throw new NotImplementedException($"FD CB opcode {bitOpcode:X2} not implemented!"); + } + } default: throw new NotImplementedException($"FD prefix opcode {opcode:X2} not implemented!"); } diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index 6789316..adf5aa9 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -50,94 +50,97 @@ label2 = new Label(); lblIX = new Label(); lblIY = new Label(); + lblIff1 = new Label(); + lblIff2 = new Label(); + lblIE = new Label(); SuspendLayout(); // // lblAF // lblAF.AutoSize = true; - lblAF.Location = new Point(12, 9); + lblAF.Location = new Point(10, 7); lblAF.Margin = new Padding(2, 0, 2, 0); lblAF.Name = "lblAF"; - lblAF.Size = new Size(33, 25); + lblAF.Size = new Size(26, 20); lblAF.TabIndex = 0; lblAF.Text = "AF"; // // lblBC // lblBC.AutoSize = true; - lblBC.Location = new Point(11, 60); + lblBC.Location = new Point(9, 48); lblBC.Margin = new Padding(2, 0, 2, 0); lblBC.Name = "lblBC"; - lblBC.Size = new Size(33, 25); + lblBC.Size = new Size(27, 20); lblBC.TabIndex = 1; lblBC.Text = "BC"; // // lblDE // lblDE.AutoSize = true; - lblDE.Location = new Point(12, 125); + lblDE.Location = new Point(10, 100); lblDE.Margin = new Padding(2, 0, 2, 0); lblDE.Name = "lblDE"; - lblDE.Size = new Size(34, 25); + lblDE.Size = new Size(28, 20); lblDE.TabIndex = 2; lblDE.Text = "DE"; // // lblHL // lblHL.AutoSize = true; - lblHL.Location = new Point(12, 188); + lblHL.Location = new Point(10, 150); lblHL.Margin = new Padding(2, 0, 2, 0); lblHL.Name = "lblHL"; - lblHL.Size = new Size(33, 25); + lblHL.Size = new Size(27, 20); lblHL.TabIndex = 3; lblHL.Text = "HL"; // // lblPC // lblPC.AutoSize = true; - lblPC.Location = new Point(11, 250); + lblPC.Location = new Point(9, 200); lblPC.Margin = new Padding(2, 0, 2, 0); lblPC.Name = "lblPC"; - lblPC.Size = new Size(33, 25); + lblPC.Size = new Size(26, 20); lblPC.TabIndex = 4; lblPC.Text = "PC"; // // lblSP // lblSP.AutoSize = true; - lblSP.Location = new Point(11, 315); + lblSP.Location = new Point(9, 252); lblSP.Margin = new Padding(2, 0, 2, 0); lblSP.Name = "lblSP"; - lblSP.Size = new Size(32, 25); + lblSP.Size = new Size(25, 20); lblSP.TabIndex = 6; lblSP.Text = "SP"; // // lblFlags // lblFlags.AutoSize = true; - lblFlags.Location = new Point(110, 65); + lblFlags.Location = new Point(88, 52); lblFlags.Margin = new Padding(2, 0, 2, 0); lblFlags.Name = "lblFlags"; - lblFlags.Size = new Size(53, 25); + lblFlags.Size = new Size(43, 20); lblFlags.TabIndex = 7; lblFlags.Text = "Flags"; // // lblTStates // lblTStates.AutoSize = true; - lblTStates.Location = new Point(110, 9); + lblTStates.Location = new Point(88, 7); lblTStates.Margin = new Padding(2, 0, 2, 0); lblTStates.Name = "lblTStates"; - lblTStates.Size = new Size(75, 25); + lblTStates.Size = new Size(63, 20); lblTStates.TabIndex = 8; lblTStates.Text = "T-States"; // // txtMemoryStart // - txtMemoryStart.Location = new Point(375, 26); + txtMemoryStart.Location = new Point(300, 21); txtMemoryStart.Margin = new Padding(2); txtMemoryStart.Name = "txtMemoryStart"; - txtMemoryStart.Size = new Size(150, 31); + txtMemoryStart.Size = new Size(121, 27); txtMemoryStart.TabIndex = 9; txtMemoryStart.Text = "Memory Start"; txtMemoryStart.TextAlign = HorizontalAlignment.Center; @@ -145,10 +148,10 @@ // // btnStep // - btnStep.Location = new Point(8, 523); + btnStep.Location = new Point(6, 418); btnStep.Margin = new Padding(2); btnStep.Name = "btnStep"; - btnStep.Size = new Size(112, 34); + btnStep.Size = new Size(90, 27); btnStep.TabIndex = 12; btnStep.Text = "Step"; btnStep.UseVisualStyleBackColor = true; @@ -156,10 +159,10 @@ // // btnRun // - btnRun.Location = new Point(149, 523); + btnRun.Location = new Point(119, 418); btnRun.Margin = new Padding(2); btnRun.Name = "btnRun"; - btnRun.Size = new Size(112, 34); + btnRun.Size = new Size(90, 27); btnRun.TabIndex = 13; btnRun.Text = "Run"; btnRun.UseVisualStyleBackColor = true; @@ -167,10 +170,10 @@ // // btnRefreshMemory // - btnRefreshMemory.Location = new Point(531, 26); + btnRefreshMemory.Location = new Point(425, 21); btnRefreshMemory.Margin = new Padding(2); btnRefreshMemory.Name = "btnRefreshMemory"; - btnRefreshMemory.Size = new Size(112, 34); + btnRefreshMemory.Size = new Size(90, 27); btnRefreshMemory.TabIndex = 14; btnRefreshMemory.Text = "Refresh Memory"; btnRefreshMemory.UseVisualStyleBackColor = true; @@ -178,39 +181,37 @@ // // txtMemoryView // - txtMemoryView.Location = new Point(110, 100); + txtMemoryView.Location = new Point(88, 80); txtMemoryView.Margin = new Padding(2); txtMemoryView.Name = "txtMemoryView"; - txtMemoryView.Size = new Size(519, 243); + txtMemoryView.Size = new Size(416, 195); txtMemoryView.TabIndex = 15; txtMemoryView.Text = "Memory View Window"; // // lstDisassembly // lstDisassembly.FormattingEnabled = true; - lstDisassembly.ItemHeight = 25; - lstDisassembly.Location = new Point(654, 14); + lstDisassembly.Location = new Point(523, 11); lstDisassembly.Margin = new Padding(2); lstDisassembly.Name = "lstDisassembly"; - lstDisassembly.Size = new Size(314, 329); + lstDisassembly.Size = new Size(252, 264); lstDisassembly.TabIndex = 16; // // lstStack // lstStack.FormattingEnabled = true; - lstStack.ItemHeight = 25; - lstStack.Location = new Point(974, 14); + lstStack.Location = new Point(779, 11); lstStack.Margin = new Padding(2); lstStack.Name = "lstStack"; - lstStack.Size = new Size(162, 329); + lstStack.Size = new Size(130, 264); lstStack.TabIndex = 17; // // btnExit // - btnExit.Location = new Point(1019, 520); + btnExit.Location = new Point(815, 416); btnExit.Margin = new Padding(2); btnExit.Name = "btnExit"; - btnExit.Size = new Size(112, 34); + btnExit.Size = new Size(90, 27); btnExit.TabIndex = 18; btnExit.Text = "Full Exit"; btnExit.UseVisualStyleBackColor = true; @@ -219,54 +220,83 @@ // label1 // label1.AutoSize = true; - label1.Location = new Point(361, 360); - label1.Margin = new Padding(4, 0, 4, 0); + label1.Location = new Point(200, 278); label1.Name = "label1"; - label1.Size = new Size(97, 25); + label1.Size = new Size(81, 20); label1.TabIndex = 19; label1.Text = "Breakpoint"; // // txtBreakpoint // - txtBreakpoint.Location = new Point(470, 351); - txtBreakpoint.Margin = new Padding(4, 4, 4, 4); + txtBreakpoint.Location = new Point(376, 281); txtBreakpoint.Name = "txtBreakpoint"; - txtBreakpoint.Size = new Size(155, 31); + txtBreakpoint.Size = new Size(125, 27); txtBreakpoint.TabIndex = 20; // // label2 // label2.AutoSize = true; - label2.Location = new Point(291, 26); - label2.Margin = new Padding(4, 0, 4, 0); + label2.Location = new Point(233, 21); label2.Name = "label2"; - label2.Size = new Size(77, 25); + label2.Size = new Size(62, 20); label2.TabIndex = 21; label2.Text = "Address"; // // lblIX // lblIX.AutoSize = true; - lblIX.Location = new Point(11, 373); + lblIX.Location = new Point(9, 298); + lblIX.Margin = new Padding(2, 0, 2, 0); lblIX.Name = "lblIX"; - lblIX.Size = new Size(28, 25); + lblIX.Size = new Size(22, 20); lblIX.TabIndex = 22; lblIX.Text = "IX"; // // lblIY // lblIY.AutoSize = true; - lblIY.Location = new Point(12, 430); + lblIY.Location = new Point(10, 344); + lblIY.Margin = new Padding(2, 0, 2, 0); lblIY.Name = "lblIY"; - lblIY.Size = new Size(27, 25); + lblIY.Size = new Size(21, 20); lblIY.TabIndex = 23; lblIY.Text = "IY"; // + // lblIff1 + // + lblIff1.AutoSize = true; + lblIff1.Location = new Point(88, 298); + lblIff1.Name = "lblIff1"; + lblIff1.Size = new Size(35, 20); + lblIff1.TabIndex = 24; + lblIff1.Text = "IFF1"; + // + // lblIff2 + // + lblIff2.AutoSize = true; + lblIff2.Location = new Point(88, 345); + lblIff2.Name = "lblIff2"; + lblIff2.Size = new Size(35, 20); + lblIff2.TabIndex = 25; + lblIff2.Text = "IFF2"; + // + // lblIE + // + lblIE.AutoSize = true; + lblIE.Location = new Point(200, 345); + lblIE.Name = "lblIE"; + lblIE.Size = new Size(109, 20); + lblIE.TabIndex = 26; + lblIE.Text = "Interrupt Mode"; + // // DebuggerForm // - AutoScaleDimensions = new SizeF(10F, 25F); + AutoScaleDimensions = new SizeF(8F, 20F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(1160, 568); + ClientSize = new Size(928, 454); + Controls.Add(lblIE); + Controls.Add(lblIff2); + Controls.Add(lblIff1); Controls.Add(lblIY); Controls.Add(lblIX); Controls.Add(label2); @@ -319,6 +349,9 @@ private Label label2; private Label lblIX; private Label lblIY; + private Label lblIff1; + private Label lblIff2; + private Label lblIE; //private TextBox textBox4; } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 73f26d2..2377c39 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -134,6 +134,9 @@ namespace Desktop 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()}"; @@ -232,6 +235,12 @@ namespace Desktop case 0x04: mnemonic = "INC B"; 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)); @@ -285,6 +294,13 @@ namespace Desktop 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 0x35: mnemonic = "DEC (HL)"; break; @@ -306,6 +322,9 @@ namespace Desktop case 0x6B: mnemonic = "LD L, E"; break; + case 0x77: + mnemonic = "LD (HL), A"; + break; case 0xA7: mnemonic = "AND A"; break; @@ -322,6 +341,11 @@ namespace Desktop mnemonic = $"JP 0x{jpHigh:X2}{jpLow:X2}"; instructionLength = 3; 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 0xD3: byte outPort = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"OUT (0x{outPort:X2}), A"; @@ -386,6 +410,9 @@ namespace Desktop case 0xF9: mnemonic = "LD SP, HL"; break; + case 0xFB: + mnemonic = "EI"; + break; case 0xFD: { byte fdOpcode = _memoryBus.Read((ushort)(currentPc + 1)); @@ -396,6 +423,36 @@ namespace Desktop 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 == 0xCB) // FD CB prefix + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + byte cbOpcode = _memoryBus.Read((ushort)(currentPc + 3)); + string sign = d >= 0 ? "+" : ""; + + if (cbOpcode == 0xCE) + { + mnemonic = $"SET 1, (IY{sign}{d})"; + } + else + { + mnemonic = $"FD CB {d:X2} {cbOpcode:X2}"; // Fallback + } + instructionLength = 4; + } + 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})";