diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index 18de5d5..e310264 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -449,6 +449,25 @@ namespace Core.Cpu if (result > 0xFF) AF.Low |= 0x01; } + private void AdcA(byte operand) + { + int aVal = AF.High; + int carryIn = AF.Low & 0x01; + + int result = aVal + operand + carryIn; + + byte newFlags = 0; + + if ((result & 0x80) != 0) newFlags |= 0x80; // S Flag + if ((result & 0xFF) == 0) newFlags |= 0x40; // Z Flag + if (((aVal & 0x0F) + (operand & 0x0F) + carryIn) > 0x0F) newFlags |= 0x10; // H Flag + if ((((aVal ^ ~operand) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; // P/V Flag + if (result > 0xFF) newFlags |= 0x01; // C Flag + + AF.Low = newFlags; + AF.High = (byte)result; + } + private void AddA(byte operand) { byte a = AF.High; @@ -527,6 +546,25 @@ namespace Core.Cpu return 6; // --- 8-Bit Increments --- case 0x04: BC.High = Inc8(BC.High); return 4; // INC B + case 0x07: // RLCA + // 1. Grab the top bit (Bit 7) before it rotates + byte topBit = (byte)(AF.High >> 7); + + // 2. Shift A left by 1, and drop the top bit into the newly empty Bit 0 + AF.High = (byte)((AF.High << 1) | topBit); + + // --- RLCA Specific Flag Rules --- + // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2). + byte newFlags = (byte)(AF.Low & 0xC4); + + // H (Bit 4) and N (Bit 1) are forcefully reset to 0 (which the bitwise AND above just did). + + // C (Bit 0): Set if the bit that rotated off the top was a 1 + if (topBit != 0) newFlags |= 0x01; + + AF.Low = newFlags; + + return 4; // 4 T-States case 0x08: // EX AF, AF' ushort tempAF = AF.Word; AF.Word = AF_Prime.Word; @@ -856,6 +894,24 @@ namespace Core.Cpu case 0x85: Add(HL.Low); return 4; // ADD A, L case 0x86: Add(_memory.Read(HL.Word)); return 7; // ADD A, (HL) case 0x87: Add(AF.High); return 4; // ADD A, A + // --- ADC A, Register Family --- + case 0x88: AdcA(BC.High); return 4; // ADC A, B + case 0x89: AdcA(BC.Low); return 4; // ADC A, C + case 0x8A: AdcA(DE.High); return 4; // ADC A, D + case 0x8B: AdcA(DE.Low); return 4; // ADC A, E + case 0x8C: AdcA(HL.High); return 4; // ADC A, H + case 0x8D: AdcA(HL.Low); return 4; // ADC A, L + case 0x8F: AdcA(AF.High); return 4; // ADC A, A + + // --- ADC A, Memory --- + case 0x8E: // ADC A, (HL) + AdcA(_memory.Read(HL.Word)); + return 7; + + // --- ADC A, Immediate --- + case 0xCE: // ADC A, n + AdcA(FetchByte()); + return 7; case 0x90: Sub(BC.High); return 4; // SUB B case 0x91: Sub(BC.Low); return 4; // SUB C case 0x92: Sub(DE.High); return 4; // SUB D @@ -1149,6 +1205,23 @@ namespace Core.Cpu return 4; // Takes 4 T-States case 0xED: return ExecuteExtendedPrefix(); + case 0xEE: // XOR n + byte xorImm = FetchByte(); + + // Perform the bitwise XOR against the Accumulator + AF.High = (byte)(AF.High ^ xorImm); + + // --- Update Flags --- + // Start with a clean slate of 0 to perfectly clear C, H, and N! + newFlags = 0; + + if ((AF.High & 0x80) != 0) newFlags |= 0x80; // Sign Flag + if (AF.High == 0) newFlags |= 0x40; // Zero Flag + if (CalculateParity(AF.High)) newFlags |= 0x04; // Parity/Overflow Flag + + AF.Low = newFlags; + + return 7; // Takes 7 T-States case 0xF1: // POP AF AF.Word = Pop(); return 10; @@ -1183,6 +1256,8 @@ namespace Core.Cpu // Fetch the actual extended instruction byte extendedOpcode = _memory.Read(PC++); byte val = 0; + byte newFlags = 0; + int result = 0; switch (extendedOpcode) { @@ -1191,6 +1266,34 @@ namespace Core.Cpu _memory.Write(dest43, BC.Low); _memory.Write((ushort)(dest43 + 1), BC.High); return 20; + case 0x44: // NEG + int aBefore = AF.High; + result = 0 - aBefore; + + newFlags = 0; + + // S Flag (Bit 7): Set if the result is negative + if ((result & 0x80) != 0) newFlags |= 0x80; + + // Z Flag (Bit 6): Set if the result is exactly zero + if ((result & 0xFF) == 0) newFlags |= 0x40; + + // H Flag (Bit 4): Set if there was a borrow from Bit 3 + if ((0 - (aBefore & 0x0F)) < 0) newFlags |= 0x10; + + // P/V Flag (Bit 2): Overflow happens ONLY if A was 0x80 (-128) + if (aBefore == 0x80) newFlags |= 0x04; + + // N Flag (Bit 1): Always set to 1 for NEG + newFlags |= 0x02; + + // C Flag (Bit 0): Set if A was not 0 before the operation + if (aBefore != 0) newFlags |= 0x01; + + AF.Low = newFlags; + AF.High = (byte)result; + + return 8; // 8 T-States case 0x47: // LD I, A I = AF.High; return 9; @@ -1215,6 +1318,40 @@ namespace Core.Cpu DE.Low = _memory.Read(src5B); DE.High = _memory.Read((ushort)(src5B + 1)); return 20; + case 0x72: // SBC HL, SP + int carryIn = AF.Low & 0x01; + int hlVal = HL.Word; + int spVal = SP; + + // Perform the full 16-bit subtraction including the carry flag + result = hlVal - spVal - carryIn; + + newFlags = 0; + + // S Flag (Bit 7): Set if the 16-bit result is negative (Bit 15 is 1) + if ((result & 0x8000) != 0) newFlags |= 0x80; + + // Z Flag (Bit 6): Set if the full 16-bit result is exactly 0 + if ((result & 0xFFFF) == 0) newFlags |= 0x40; + + // H Flag (Bit 4): Set if there was a borrow from Bit 11 + if (((hlVal & 0x0FFF) - (spVal & 0x0FFF) - carryIn) < 0) newFlags |= 0x10; + + // P/V Flag (Bit 2): Set on Overflow + // Overflow happens if the signs of the operands are different, + // AND the sign of the result is different from the original HL + if ((((hlVal ^ spVal) & (hlVal ^ result)) & 0x8000) != 0) newFlags |= 0x04; + + // N Flag (Bit 1): Always set to 1 for a subtraction + newFlags |= 0x02; + + // C Flag (Bit 0): Set if the total result underflows 0 (Borrow from Bit 15) + if (result < 0) newFlags |= 0x01; + + AF.Low = newFlags; + HL.Word = (ushort)result; + + return 15; // 15 T-States case 0x73: // LD (nn), SP ushort dest73 = FetchWord(); _memory.Write(dest73, (byte)SP); @@ -1230,7 +1367,7 @@ namespace Core.Cpu // H (Bit 4) and N (Bit 1) are RESET. // C (Bit 0) is PRESERVED. - byte newFlags = (byte)(AF.Low & 0x01); // Preserve Carry + newFlags = (byte)(AF.Low & 0x01); // Preserve Carry if ((portVal78 & 0x80) != 0) newFlags |= 0x80; // Sign Flag if (portVal78 == 0) newFlags |= 0x40; // Zero Flag @@ -1238,7 +1375,21 @@ namespace Core.Cpu AF.Low = newFlags; - return 12; // Takes 12 T-States + return 12; + case 0x7B: // LD SP, (nn) + // 1. Fetch the absolute 16-bit memory address from the instruction stream + byte addrLow = FetchByte(); + byte addrHigh = FetchByte(); + ushort address7B = (ushort)((addrHigh << 8) | addrLow); + + // 2. Read the 16-bit value stored at that exact memory location (Little-Endian!) + byte spLow = _memory.Read(address7B); + byte spHigh = _memory.Read((ushort)(address7B + 1)); + + // 3. Load the resulting 16-bit value directly into the Stack Pointer + SP = (ushort)((spHigh << 8) | spLow); + + return 20; case 0xB0: // LDIR // 1. Read byte from (HL) val = _memory.Read(HL.Word); @@ -1418,12 +1569,36 @@ namespace Core.Cpu switch (ddOpcode) { + case 0x09: // ADD IX, BC + int result = IX.Word + BC.Word; + + // --- 16-Bit Flag Calculation --- + // Preserve S (Bit 7), Z (Bit 6), and P/V (Bit 2). + byte newFlags = (byte)(AF.Low & 0xC4); + + // Half-Carry (H - Bit 4): Set if carry from Bit 11 + if (((IX.Word & 0x0FFF) + (BC.Word & 0x0FFF)) > 0x0FFF) + newFlags |= 0x10; + + // Carry (C - Bit 0): Set if the total result overflows 16 bits + if (result > 0xFFFF) + newFlags |= 0x01; + + // (N - Bit 1 is left at 0 because the bitwise AND above cleared it) + + AF.Low = newFlags; + IX.Word = (ushort)result; + + return 15; case 0x21: // LD IX, nn byte low = FetchByte(); byte high = FetchByte(); IX.Word = (ushort)((high << 8) | low); - return 14; // 14 T-States + return 14; + case 0xE9: // JP (IX) + PC = IX.Word; + return 8; default: throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} not implemented!"); @@ -1441,6 +1616,43 @@ namespace Core.Cpu case 0x21: // LD IY, nn IY.Word = FetchWord(); return 14; + case 0x34: // INC (IY+d) + // 1. Fetch displacement and calculate memory address + sbyte offset34 = (sbyte)FetchByte(); + ushort address34 = (ushort)(IY.Word + offset34); + + // 2. Read the value from memory + byte valBefore = _memory.Read(address34); + + // 3. Increment the value + int result = valBefore + 1; + + // --- 8-Bit Increment Flag Calculation --- + // CRITICAL: We must preserve the existing Carry flag (Bit 0)! + byte newFlags = (byte)(AF.Low & 0x01); + + // S Flag (Bit 7): Set if the result is negative + if ((result & 0x80) != 0) newFlags |= 0x80; + + // Z Flag (Bit 6): Set if the result is exactly zero (wrapped from 255 to 0) + if ((result & 0xFF) == 0) newFlags |= 0x40; + + // H Flag (Bit 4): Set if there was a carry out of Bit 3 + if ((valBefore & 0x0F) == 0x0F) newFlags |= 0x10; + + // P/V Flag (Bit 2): Set on Overflow + // For INC, overflow ONLY happens if we increment 0x7F (+127) and it wraps to 0x80 (-128) + if (valBefore == 0x7F) newFlags |= 0x04; + + // N Flag (Bit 1): Always reset to 0 for an increment + // (Our bitwise AND at the top already cleared it) + + AF.Low = newFlags; + + // 4. Write the modified value back to memory + _memory.Write(address34, (byte)result); + + return 23; // 23 T-States case 0x35: // DEC (IY+d) sbyte offset = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset); @@ -1515,6 +1727,16 @@ namespace Core.Cpu _memory.Write(targetAddress, BC.Low); return 19; // Takes 19 T-States } + case 0x74: // LD (IY+d), H + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset74 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IY + offset) + ushort address74 = (ushort)(IY.Word + offset74); + + // 3. Write the contents of the H register into memory at that address + _memory.Write(address74, HL.High); + return 19; case 0x75: // LD (IY+d), L sbyte offset75 = (sbyte)FetchByte(); targetAddress = (ushort)(IY.Word + offset75); @@ -1531,6 +1753,44 @@ namespace Core.Cpu return 19; } + case 0x96: // SUB (IY+d) + // 1. Fetch the displacement byte and calculate the address + sbyte offset96 = (sbyte)FetchByte(); + ushort address96 = (ushort)(IY.Word + offset96); + + // 2. Read the value from memory + byte subVal = _memory.Read(address96); + + // 3. Perform the subtraction from the Accumulator + int aVal = AF.High; + result = aVal - subVal; + + // --- 8-Bit Subtraction Flag Calculation --- + newFlags = 0; + + // S Flag (Bit 7): Set if the result is negative + if ((result & 0x80) != 0) newFlags |= 0x80; + + // Z Flag (Bit 6): Set if the result is exactly zero + if ((result & 0xFF) == 0) newFlags |= 0x40; + + // H Flag (Bit 4): Set if there was a borrow from Bit 3 + if (((aVal & 0x0F) - (subVal & 0x0F)) < 0) newFlags |= 0x10; + + // P/V Flag (Bit 2): Set on Overflow + // (Happens if subtracting a negative from a positive gives a negative, or vice versa) + if ((((aVal ^ subVal) & (aVal ^ result)) & 0x80) != 0) newFlags |= 0x04; + + // N Flag (Bit 1): Always set to 1 for a subtraction + newFlags |= 0x02; + + // C Flag (Bit 0): Set if a borrow was needed (Accumulator was smaller than memory value) + if (aVal < subVal) newFlags |= 0x01; + + AF.Low = newFlags; + AF.High = (byte)result; + + return 19; case 0xCB: // The FD CB nested prefix { sbyte displacement = (sbyte)FetchByte(); diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 195cdc8..9c562c6 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -582,7 +582,18 @@ namespace Desktop case 0x85: mnemonic = "ADD A, L"; break; case 0x86: mnemonic = "ADD A, (HL)"; break; case 0x87: mnemonic = "ADD A, A"; break; - + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + string[] registers = { "B", "C", "D", "E", "H", "L", "(HL)", "A" }; + mnemonic = $"ADC A, {registers[opcode - 0x88]}"; + instructionLength = 1; + break; // --- SUB r --- case 0x90: mnemonic = "SUB B"; break; case 0x91: mnemonic = "SUB C"; break; @@ -779,10 +790,15 @@ namespace Desktop instructionLength = 2; 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; + ushort callDest = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); + mnemonic = $"CALL 0x{callDest:X4}"; + instructionLength = 3; + break; + case 0xCE: // ADC A, n + byte n = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"ADC A, 0x{n:X2}"; + instructionLength = 2; + break; case 0xD0: mnemonic = "RET NC"; break; @@ -809,13 +825,22 @@ namespace Desktop case 0xDD: { byte ddOpcode = _memoryBus.Read((ushort)(currentPc + 1)); - - if (ddOpcode == 0x21) // LD IX, nn + if (ddOpcode == 0x09) // ADD IX, BC + { + mnemonic = "ADD IX, BC"; + instructionLength = 2; + } + else if (ddOpcode == 0x21) // LD IX, nn { ushort ixVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD IX, 0x{ixVal:X4}"; instructionLength = 4; } + else if (ddOpcode == 0xE9) // JP (IX) + { + mnemonic = "JP (IX)"; + instructionLength = 2; + } else { mnemonic = $"DD PREFIX UNKNOWN (0x{ddOpcode:X2})"; @@ -856,6 +881,10 @@ namespace Desktop mnemonic = $"LD (0x{bcAddr:X4}), BC"; instructionLength = 4; break; + case 0x44: // NEG + mnemonic = "NEG"; + instructionLength = 2; + break; case 0x47: mnemonic = "LD I, A"; instructionLength = 2; // 0xED + 0x47 @@ -888,10 +917,19 @@ namespace Desktop mnemonic = $"LD (0x{addr73:X4}), SP"; instructionLength = 4; break; + case 0x72: // SBC HL, SP + mnemonic = "SBC HL, SP"; + instructionLength = 2; + break; case 0x78: mnemonic = "IN A, (C)"; instructionLength = 2; break; + case 0x7B: // LD SP, (nn) + ushort nn = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); + mnemonic = $"LD SP, (0x{nn:X4})"; + instructionLength = 4; + break; case 0xB0: mnemonic = "LDIR"; instructionLength = 2; @@ -907,6 +945,11 @@ namespace Desktop break; } break; + case 0xEE: + byte xorVal = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"XOR 0x{xorVal:X2}"; + instructionLength = 2; + break; case 0xF1: mnemonic = "POP AF"; break; case 0xF3: mnemonic = "DI"; @@ -935,6 +978,13 @@ namespace Desktop mnemonic = $"LD IY, 0x{iyVal:X4}"; instructionLength = 4; } + else if (fdOpcode == 0x34) // INC (IY+d) + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"INC (IY{sign}{d})"; + instructionLength = 3; + } else if (fdOpcode == 0x35) // DEC (IY+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -945,7 +995,7 @@ namespace Desktop else if (fdOpcode == 0x36) // LD (IY+d), n { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); - byte n = _memoryBus.Read((ushort)(currentPc + 3)); + n = _memoryBus.Read((ushort)(currentPc + 3)); string sign = d >= 0 ? "+" : ""; mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}"; instructionLength = 4; @@ -987,6 +1037,13 @@ namespace Desktop mnemonic = $"LD L, (IY{signL}{offsetL})"; instructionLength = 3; } + else if (fdOpcode == 0x74) // LD (IY+d), H + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IY{sign}{d}), H"; + instructionLength = 3; + } else if (fdOpcode == 0x86) // ADD A, (IY+d) { sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -995,6 +1052,13 @@ namespace Desktop mnemonic = $"ADD A, (IY{signAdd}{dAdd})"; instructionLength = 3; } + else if (fdOpcode == 0x96) // SUB (IY+d) + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"SUB (IY{sign}{d})"; + instructionLength = 3; + } else if (fdOpcode == 0xCB) // FD CB prefix { cbOp = _memoryBus.Read((ushort)(currentPc + 1)); diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index caa947e..9d2387d 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -14,6 +14,7 @@ namespace Desktop private Z80 _cpu = null!; private MemoryBus _memoryBus = null!; private IO_Bus _simpleIoBus = null!; + private int _ulaFrameCount = 0; // The 16 physical colors of the ZX Spectrum (ARGB format) private readonly int[] SpectrumColors = new int[] @@ -71,6 +72,10 @@ namespace Desktop // Public so the Debugger's background thread can call it 50 times a second public void RenderScreen() { + _ulaFrameCount++; + // The Spectrum flashes every 16 frames. + // This boolean flips between true and false every 16 frames. + bool invertFlashPhase = (_ulaFrameCount % 32) >= 16; int[] pixelData = new int[256 * 192]; // Loop through the 6144 bytes of Pixel RAM @@ -95,10 +100,18 @@ namespace Desktop int ink = attr & 0x07; int paper = (attr >> 3) & 0x07; int brightOffset = (attr & 0x40) != 0 ? 8 : 0; - + bool isFlashSet = (attr & 0x80) != 0; int inkColor = SpectrumColors[ink + brightOffset]; int paperColor = SpectrumColors[paper + brightOffset]; + if (isFlashSet && invertFlashPhase) + { + // Swap the ink and paper colors for this character block! + int temp = inkColor; + inkColor = paperColor; + paperColor = temp; + } + // Draw the 8 pixels for (int bit = 0; bit < 8; bit++) {