From 2b40960496837045e9902b77363e564e340389c7 Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Mon, 13 Apr 2026 21:19:05 +0100 Subject: [PATCH] Implemented many more OpCodes --- Core/Cpu/Z80.cs | 95 +++++++++++++++++++++++- Core/Memory/MemoryBus.cs | 5 ++ Desktop/DebuggerForm.Designer.cs | 121 ++++++++++++++++++------------- Desktop/DebuggerForm.cs | 53 ++++++++++++++ 4 files changed, 222 insertions(+), 52 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index ad7be26..48c416e 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -50,13 +50,33 @@ namespace Core.Cpu public void Reset() { PC = 0x0000; - // The Z80 initializes SP to 0xFFFF on boot - SP = 0xFFFF; + SP = 0xFFFF; // The Z80 initializes SP to 0xFFFF on boot + // Main Registers AF.Word = 0; BC.Word = 0; DE.Word = 0; HL.Word = 0; + + // Alternate Registers + AF_Prime.Word = 0; + BC_Prime.Word = 0; + DE_Prime.Word = 0; + HL_Prime.Word = 0; + + // Index Registers + IX.Word = 0; + IY.Word = 0; + + // Internal Registers + I = 0; + R = 0; + + // Hardware State + IFF1 = false; + IFF2 = false; + InterruptMode = 0; + TotalTStates = 0; // Reset the system clock! } public int Step() @@ -96,6 +116,36 @@ namespace Core.Cpu $"C:{f & 1}"; } + private void Sub(byte value) + { + byte a = AF.High; + int result = a - value; + + // Save the result back to the Accumulator + AF.High = (byte)result; + + // --- Update Flags (F Register) --- + AF.Low = 0; // Clear all flags + + // Sign Flag (Bit 7) + if ((result & 0x80) != 0) AF.Low |= 0x80; + + // Zero Flag (Bit 6) + if ((byte)result == 0) AF.Low |= 0x40; + + // Half-Carry Flag (Bit 4) - Set if borrow from bit 4 + if (((a & 0x0F) - (value & 0x0F)) < 0) AF.Low |= 0x10; + + // Overflow Flag (Bit 2) - Set if operands have different signs and result sign changes + if ((((a ^ value) & 0x80) != 0) && (((a ^ result) & 0x80) != 0)) AF.Low |= 0x04; + + // Subtract Flag (Bit 1) - ALWAYS set for CP/SUB + AF.Low |= 0x02; + + // Carry Flag (Bit 0) - Set if the overall result dropped below 0 + if (result < 0) AF.Low |= 0x01; + } + private void Sbc(byte value) { byte a = AF.High; @@ -309,6 +359,17 @@ namespace Core.Cpu _memory.Write(SP, (byte)(value & 0xFF)); } + private ushort Pop() + { + // The Z80 is Little-Endian. Low byte comes off the stack first. + byte low = _memory.Read(SP++); + + // High byte comes off second. + byte high = _memory.Read(SP++); + + return (ushort)((high << 8) | low); + } + private int ExecuteOpcode(byte opcode) { sbyte offset = 0; @@ -340,6 +401,9 @@ namespace Core.Cpu case 0x11: //LD DE, nn DE.Word = FetchWord(); return 10; + case 0x16: // LD D, n + DE.High = FetchByte(); + return 7; case 0x19: // ADD HL, DE Add16(DE.Word); return 11; @@ -429,9 +493,15 @@ namespace Core.Cpu case 0x77: // LD (HL), A _memory.Write(HL.Word, AF.High); return 7; + case 0x91: // SUB C + Sub(BC.Low); + return 4; // Takes 4 T-States case 0xA7: // AND A And(AF.High); return 4; + case 0x5F: // LD E, A + DE.Low = AF.High; + return 4; case 0xAF: // XOR A AF.High = 0; AF.Low = 0x44; @@ -442,6 +512,9 @@ namespace Core.Cpu case 0xC3: PC = FetchWord(); return 10; + case 0xC9: // RET + PC = Pop(); + return 10; case 0xCD: // CALL nn ushort callAddress = FetchWord(); Push(PC); @@ -604,6 +677,24 @@ namespace Core.Cpu byte decVal = Dec8(memVal); _memory.Write(targetAddress, decVal); return 23; + case 0x36: // LD (IY+d), n + { + sbyte offset36 = (sbyte)FetchByte(); + byte nValue = FetchByte(); + targetAddress = (ushort)(IY.Word + offset36); + + _memory.Write(targetAddress, nValue); + return 19; // Takes 19 T-States + } + case 0x71: // LD (IY+d), C + { + sbyte offset71 = (sbyte)FetchByte(); + targetAddress = (ushort)(IY.Word + offset71); + + // Write the C register (low byte of BC) to memory + _memory.Write(targetAddress, BC.Low); + return 19; // Takes 19 T-States + } case 0x75: // LD (IY+d), L sbyte offset75 = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset75); diff --git a/Core/Memory/MemoryBus.cs b/Core/Memory/MemoryBus.cs index 34280fb..c7d65d4 100644 --- a/Core/Memory/MemoryBus.cs +++ b/Core/Memory/MemoryBus.cs @@ -42,5 +42,10 @@ namespace Core.Memory // Copy the ROM Array.Copy(romData, 0, _memory, 0, romData.Length); } + + public void ClearRam() + { + //To Do + } } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.Designer.cs b/Desktop/DebuggerForm.Designer.cs index adf5aa9..64e9acd 100644 --- a/Desktop/DebuggerForm.Designer.cs +++ b/Desktop/DebuggerForm.Designer.cs @@ -53,94 +53,95 @@ lblIff1 = new Label(); lblIff2 = new Label(); lblIE = new Label(); + btnReset = new Button(); SuspendLayout(); // // lblAF // lblAF.AutoSize = true; - lblAF.Location = new Point(10, 7); + lblAF.Location = new Point(12, 9); lblAF.Margin = new Padding(2, 0, 2, 0); lblAF.Name = "lblAF"; - lblAF.Size = new Size(26, 20); + lblAF.Size = new Size(33, 25); lblAF.TabIndex = 0; lblAF.Text = "AF"; // // lblBC // lblBC.AutoSize = true; - lblBC.Location = new Point(9, 48); + lblBC.Location = new Point(11, 60); lblBC.Margin = new Padding(2, 0, 2, 0); lblBC.Name = "lblBC"; - lblBC.Size = new Size(27, 20); + lblBC.Size = new Size(33, 25); lblBC.TabIndex = 1; lblBC.Text = "BC"; // // lblDE // lblDE.AutoSize = true; - lblDE.Location = new Point(10, 100); + lblDE.Location = new Point(12, 125); lblDE.Margin = new Padding(2, 0, 2, 0); lblDE.Name = "lblDE"; - lblDE.Size = new Size(28, 20); + lblDE.Size = new Size(34, 25); lblDE.TabIndex = 2; lblDE.Text = "DE"; // // lblHL // lblHL.AutoSize = true; - lblHL.Location = new Point(10, 150); + lblHL.Location = new Point(12, 188); lblHL.Margin = new Padding(2, 0, 2, 0); lblHL.Name = "lblHL"; - lblHL.Size = new Size(27, 20); + lblHL.Size = new Size(33, 25); lblHL.TabIndex = 3; lblHL.Text = "HL"; // // lblPC // lblPC.AutoSize = true; - lblPC.Location = new Point(9, 200); + lblPC.Location = new Point(11, 250); lblPC.Margin = new Padding(2, 0, 2, 0); lblPC.Name = "lblPC"; - lblPC.Size = new Size(26, 20); + lblPC.Size = new Size(33, 25); lblPC.TabIndex = 4; lblPC.Text = "PC"; // // lblSP // lblSP.AutoSize = true; - lblSP.Location = new Point(9, 252); + lblSP.Location = new Point(11, 315); lblSP.Margin = new Padding(2, 0, 2, 0); lblSP.Name = "lblSP"; - lblSP.Size = new Size(25, 20); + lblSP.Size = new Size(32, 25); lblSP.TabIndex = 6; lblSP.Text = "SP"; // // lblFlags // lblFlags.AutoSize = true; - lblFlags.Location = new Point(88, 52); + lblFlags.Location = new Point(110, 65); lblFlags.Margin = new Padding(2, 0, 2, 0); lblFlags.Name = "lblFlags"; - lblFlags.Size = new Size(43, 20); + lblFlags.Size = new Size(53, 25); lblFlags.TabIndex = 7; lblFlags.Text = "Flags"; // // lblTStates // lblTStates.AutoSize = true; - lblTStates.Location = new Point(88, 7); + lblTStates.Location = new Point(110, 9); lblTStates.Margin = new Padding(2, 0, 2, 0); lblTStates.Name = "lblTStates"; - lblTStates.Size = new Size(63, 20); + lblTStates.Size = new Size(75, 25); lblTStates.TabIndex = 8; lblTStates.Text = "T-States"; // // txtMemoryStart // - txtMemoryStart.Location = new Point(300, 21); + txtMemoryStart.Location = new Point(375, 26); txtMemoryStart.Margin = new Padding(2); txtMemoryStart.Name = "txtMemoryStart"; - txtMemoryStart.Size = new Size(121, 27); + txtMemoryStart.Size = new Size(150, 31); txtMemoryStart.TabIndex = 9; txtMemoryStart.Text = "Memory Start"; txtMemoryStart.TextAlign = HorizontalAlignment.Center; @@ -148,10 +149,10 @@ // // btnStep // - btnStep.Location = new Point(6, 418); + btnStep.Location = new Point(8, 522); btnStep.Margin = new Padding(2); btnStep.Name = "btnStep"; - btnStep.Size = new Size(90, 27); + btnStep.Size = new Size(112, 34); btnStep.TabIndex = 12; btnStep.Text = "Step"; btnStep.UseVisualStyleBackColor = true; @@ -159,10 +160,10 @@ // // btnRun // - btnRun.Location = new Point(119, 418); + btnRun.Location = new Point(149, 522); btnRun.Margin = new Padding(2); btnRun.Name = "btnRun"; - btnRun.Size = new Size(90, 27); + btnRun.Size = new Size(112, 34); btnRun.TabIndex = 13; btnRun.Text = "Run"; btnRun.UseVisualStyleBackColor = true; @@ -170,10 +171,10 @@ // // btnRefreshMemory // - btnRefreshMemory.Location = new Point(425, 21); + btnRefreshMemory.Location = new Point(531, 26); btnRefreshMemory.Margin = new Padding(2); btnRefreshMemory.Name = "btnRefreshMemory"; - btnRefreshMemory.Size = new Size(90, 27); + btnRefreshMemory.Size = new Size(112, 34); btnRefreshMemory.TabIndex = 14; btnRefreshMemory.Text = "Refresh Memory"; btnRefreshMemory.UseVisualStyleBackColor = true; @@ -181,37 +182,39 @@ // // txtMemoryView // - txtMemoryView.Location = new Point(88, 80); + txtMemoryView.Location = new Point(110, 100); txtMemoryView.Margin = new Padding(2); txtMemoryView.Name = "txtMemoryView"; - txtMemoryView.Size = new Size(416, 195); + txtMemoryView.Size = new Size(519, 243); txtMemoryView.TabIndex = 15; txtMemoryView.Text = "Memory View Window"; // // lstDisassembly // lstDisassembly.FormattingEnabled = true; - lstDisassembly.Location = new Point(523, 11); + lstDisassembly.ItemHeight = 25; + lstDisassembly.Location = new Point(654, 14); lstDisassembly.Margin = new Padding(2); lstDisassembly.Name = "lstDisassembly"; - lstDisassembly.Size = new Size(252, 264); + lstDisassembly.Size = new Size(314, 329); lstDisassembly.TabIndex = 16; // // lstStack // lstStack.FormattingEnabled = true; - lstStack.Location = new Point(779, 11); + lstStack.ItemHeight = 25; + lstStack.Location = new Point(974, 14); lstStack.Margin = new Padding(2); lstStack.Name = "lstStack"; - lstStack.Size = new Size(130, 264); + lstStack.Size = new Size(162, 329); lstStack.TabIndex = 17; // // btnExit // - btnExit.Location = new Point(815, 416); + btnExit.Location = new Point(1019, 520); btnExit.Margin = new Padding(2); btnExit.Name = "btnExit"; - btnExit.Size = new Size(90, 27); + btnExit.Size = new Size(112, 34); btnExit.TabIndex = 18; btnExit.Text = "Full Exit"; btnExit.UseVisualStyleBackColor = true; @@ -220,80 +223,97 @@ // label1 // label1.AutoSize = true; - label1.Location = new Point(200, 278); + label1.Location = new Point(250, 348); + label1.Margin = new Padding(4, 0, 4, 0); label1.Name = "label1"; - label1.Size = new Size(81, 20); + label1.Size = new Size(97, 25); label1.TabIndex = 19; label1.Text = "Breakpoint"; // // txtBreakpoint // - txtBreakpoint.Location = new Point(376, 281); + txtBreakpoint.Location = new Point(470, 351); + txtBreakpoint.Margin = new Padding(4); txtBreakpoint.Name = "txtBreakpoint"; - txtBreakpoint.Size = new Size(125, 27); + txtBreakpoint.Size = new Size(155, 31); txtBreakpoint.TabIndex = 20; // // label2 // label2.AutoSize = true; - label2.Location = new Point(233, 21); + label2.Location = new Point(291, 26); + label2.Margin = new Padding(4, 0, 4, 0); label2.Name = "label2"; - label2.Size = new Size(62, 20); + label2.Size = new Size(77, 25); label2.TabIndex = 21; label2.Text = "Address"; // // lblIX // lblIX.AutoSize = true; - lblIX.Location = new Point(9, 298); + lblIX.Location = new Point(11, 372); lblIX.Margin = new Padding(2, 0, 2, 0); lblIX.Name = "lblIX"; - lblIX.Size = new Size(22, 20); + lblIX.Size = new Size(28, 25); lblIX.TabIndex = 22; lblIX.Text = "IX"; // // lblIY // lblIY.AutoSize = true; - lblIY.Location = new Point(10, 344); + lblIY.Location = new Point(12, 430); lblIY.Margin = new Padding(2, 0, 2, 0); lblIY.Name = "lblIY"; - lblIY.Size = new Size(21, 20); + lblIY.Size = new Size(27, 25); lblIY.TabIndex = 23; lblIY.Text = "IY"; // // lblIff1 // lblIff1.AutoSize = true; - lblIff1.Location = new Point(88, 298); + lblIff1.Location = new Point(110, 372); + lblIff1.Margin = new Padding(4, 0, 4, 0); lblIff1.Name = "lblIff1"; - lblIff1.Size = new Size(35, 20); + lblIff1.Size = new Size(45, 25); lblIff1.TabIndex = 24; lblIff1.Text = "IFF1"; // // lblIff2 // lblIff2.AutoSize = true; - lblIff2.Location = new Point(88, 345); + lblIff2.Location = new Point(110, 431); + lblIff2.Margin = new Padding(4, 0, 4, 0); lblIff2.Name = "lblIff2"; - lblIff2.Size = new Size(35, 20); + lblIff2.Size = new Size(45, 25); lblIff2.TabIndex = 25; lblIff2.Text = "IFF2"; // // lblIE // lblIE.AutoSize = true; - lblIE.Location = new Point(200, 345); + lblIE.Location = new Point(250, 431); + lblIE.Margin = new Padding(4, 0, 4, 0); lblIE.Name = "lblIE"; - lblIE.Size = new Size(109, 20); + lblIE.Size = new Size(133, 25); lblIE.TabIndex = 26; lblIE.Text = "Interrupt Mode"; // + // btnReset + // + btnReset.Location = new Point(883, 520); + btnReset.Name = "btnReset"; + btnReset.Size = new Size(112, 34); + btnReset.TabIndex = 27; + btnReset.Text = "Hard Reset"; + btnReset.UseVisualStyleBackColor = true; + btnReset.Click += btnReset_Click; + // // DebuggerForm // - AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleDimensions = new SizeF(10F, 25F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(928, 454); + ClientSize = new Size(1160, 568); + Controls.Add(btnReset); Controls.Add(lblIE); Controls.Add(lblIff2); Controls.Add(lblIff1); @@ -352,6 +372,7 @@ private Label lblIff1; private Label lblIff2; private Label lblIE; + private Button btnReset; //private TextBox textBox4; } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 55a9a82..516720f 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -117,6 +117,30 @@ namespace Desktop UpdateDisplay(); } + private void btnReset_Click(object sender, EventArgs e) + { + // 1. Safely stop the emulator if it is currently in a Run loop + if (_isRunning) + { + _isRunning = false; + btnRun.Text = "Run"; + } + + // 2. Power cycle the CPU + _cpu.Reset(); + + // Note: A true hard reset also wipes the RAM. + // If you add a RAM clear method to your MemoryBus later, call it here! + _memoryBus.ClearRam(); //To Do + + // 3. Clear the UI tracking lists + lstDisassembly.Items.Clear(); + lstStack.Items.Clear(); + + // 4. Force a full UI refresh to show the clean slate + UpdateDisplay(); + } + private void btnExit_Click(object sender, EventArgs e) { Environment.Exit(0); @@ -253,6 +277,11 @@ namespace Desktop mnemonic = $"LD DE, 0x{deHigh:X2}{deLow:X2}"; instructionLength = 3; break; + case 0x16: + byte dImm = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"LD D, 0x{dImm:X2}"; + instructionLength = 2; + break; case 0x19: mnemonic = "ADD HL, DE"; break; @@ -321,6 +350,9 @@ namespace Desktop case 0x47: mnemonic = "LD B, A"; break; + case 0x5F: + mnemonic = "LD E, A"; + break; case 0x62: mnemonic = "LD H, D"; break; @@ -330,6 +362,9 @@ namespace Desktop case 0x77: mnemonic = "LD (HL), A"; break; + case 0x91: + mnemonic = "SUB C"; + break; case 0xA7: mnemonic = "AND A"; break; @@ -346,6 +381,9 @@ namespace Desktop mnemonic = $"JP 0x{jpHigh:X2}{jpLow:X2}"; instructionLength = 3; break; + case 0xC9: + mnemonic = "RET"; + break; case 0xCD: ushort callDest = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"CALL 0x{callDest:X4}"; @@ -435,6 +473,14 @@ namespace Desktop mnemonic = $"DEC (IY{sign}{d})"; instructionLength = 3; } + else if (fdOpcode == 0x36) // LD (IY+d), n + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + byte n = _memoryBus.Read((ushort)(currentPc + 3)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}"; + instructionLength = 4; + } else if (fdOpcode == 0xCB) // FD CB prefix { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -454,6 +500,13 @@ namespace Desktop } instructionLength = 4; } + else if (fdOpcode == 0x71) // LD (IY+d), C + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IY{sign}{d}), C"; + instructionLength = 3; + } else if (fdOpcode == 0x75) // LD (IY+d), L { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));