diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index e310264..0d2326b 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -1009,6 +1009,17 @@ namespace Core.Cpu if ((AF.Low & 0x01) != 0) PC = addr; return 10; } + case 0xDB: // IN A, (n) + // 1. Fetch the immediate port offset byte + byte portOffsetDB = FetchByte(); + + // 2. The Z80 puts 'A' on the top 8 bits, and 'n' on the bottom 8 bits + ushort portAddressDB = (ushort)((AF.High << 8) | portOffsetDB); + + // 3. Read from the I/O bus and store the result straight into the Accumulator + AF.High = _simpleIoBus.ReadPort(portAddressDB); + + return 11; case 0xE2: // JP PO, nn (Parity Odd / No Overflow) { ushort addr = FetchWord(); @@ -1251,7 +1262,7 @@ namespace Core.Cpu throw new NotImplementedException($"Opcode 0x{opcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); } } - private int ExecuteExtendedPrefix() + private int ExecuteExtendedPrefix() //ED { // Fetch the actual extended instruction byte extendedOpcode = _memory.Read(PC++); @@ -1376,6 +1387,9 @@ namespace Core.Cpu AF.Low = newFlags; return 12; + case 0x79: // OUT (C), A + _simpleIoBus.WritePort(BC.Word, AF.High); + return 12; // 12 T-States case 0x7B: // LD SP, (nn) // 1. Fetch the absolute 16-bit memory address from the instruction stream byte addrLow = FetchByte(); @@ -1596,6 +1610,65 @@ namespace Core.Cpu IX.Word = (ushort)((high << 8) | low); return 14; + case 0x36: // LD (IX+d), n + // 1. Fetch the displacement byte first + sbyte offset36 = (sbyte)FetchByte(); + + // 2. Fetch the immediate 8-bit value second + byte n36 = FetchByte(); + + // 3. Calculate the exact memory address (IX + offset) + ushort address36 = (ushort)(IX.Word + offset36); + + // 4. Write the immediate value directly into memory + _memory.Write(address36, n36); + + return 19; + case 0x74: // LD (IX+d), H + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset74 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IX + offset) + ushort address74 = (ushort)(IX.Word + offset74); + + // 3. Write the contents of the L register (Low byte of HL) into memory + _memory.Write(address74, HL.High); + + return 19; + case 0x75: // LD (IX+d), L + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset75 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IX + offset) + ushort address75 = (ushort)(IX.Word + offset75); + + // 3. Write the contents of the L register (Low byte of HL) into memory + _memory.Write(address75, HL.Low); + + return 19; + case 0xE1: // POP IX + // 1. Read the low byte from the top of the stack + byte popLow = _memory.Read(SP); + SP++; // Move stack pointer up + + // 2. Read the high byte + byte popHigh = _memory.Read(SP); + SP++; // Move stack pointer up again + + // 3. Combine them and store in IX + IX.Word = (ushort)((popHigh << 8) | popLow); + + return 14; + case 0xE5: // PUSH IX + // 1. Decrement the stack pointer and write the HIGH byte + SP--; + _memory.Write(SP, IX.High); + + // 2. Decrement the stack pointer again and write the LOW byte + SP--; + _memory.Write(SP, IX.Low); + + return 15; case 0xE9: // JP (IX) PC = IX.Word; return 8; @@ -1727,6 +1800,17 @@ namespace Core.Cpu _memory.Write(targetAddress, BC.Low); return 19; // Takes 19 T-States } + case 0x72: // LD (IY+d), D + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset72 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IY + offset) + ushort address72 = (ushort)(IY.Word + offset72); + + // 3. Write the contents of the D register (High byte of DE) into memory + _memory.Write(address72, DE.High); + + return 19; // 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(); diff --git a/Core/Io/IO_Bus.cs b/Core/Io/IO_Bus.cs index b473df8..1361e84 100644 --- a/Core/Io/IO_Bus.cs +++ b/Core/Io/IO_Bus.cs @@ -5,8 +5,10 @@ namespace Core.Io { public class IO_Bus { - // 8 rows representing the Spectrum keyboard matrix. Default to 0xFF (unpressed). - public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + public byte BorderColorIndex { get; private set; } = 7; // 7 is White + + // 8 rows representing the Spectrum keyboard matrix. Default to 0xFF (unpressed). + public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; public byte ReadPort(ushort portAddress) { @@ -35,9 +37,17 @@ namespace Core.Io return 0xFF; } - public void WritePort(ushort portAddress, byte portValue) + public void WritePort(ushort portAddress, byte portValue) { + // The ULA intercepts any write to an even port address + if ((portAddress & 0x01) == 0) + { + // The bottom 3 bits (0-2) define the border color! + BorderColorIndex = (byte)(portValue & 0x07); + // (Bits 3 and 4 handle the cassette MIC output and the internal speaker, + // which we will need when we start playing audio!) + } } -} + } } \ No newline at end of file diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 9c562c6..7b6dcb8 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -822,6 +822,11 @@ namespace Desktop case 0xD8: mnemonic = "RET C"; break; + case 0xDB: // IN A, (n) + n = _memoryBus.Read((ushort)(currentPc + 1)); + mnemonic = $"IN A, (0x{n:X2})"; + instructionLength = 2; + break; case 0xDD: { byte ddOpcode = _memoryBus.Read((ushort)(currentPc + 1)); @@ -836,6 +841,39 @@ namespace Desktop mnemonic = $"LD IX, 0x{ixVal:X4}"; instructionLength = 4; } + else if (ddOpcode == 0x36) // LD (IX+d), n + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + n = _memoryBus.Read((ushort)(currentPc + 3)); + + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IX{sign}{d}), 0x{n:X2}"; + instructionLength = 4; + } + else if (ddOpcode == 0x74) // LD (IX+d), H + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IX{sign}{d}), H"; + instructionLength = 3; + } + else if (ddOpcode == 0x75) // LD (IX+d), L + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IX{sign}{d}), L"; + instructionLength = 3; + } + else if (ddOpcode == 0xE1) // POP IX + { + mnemonic = "POP IX"; + instructionLength = 2; + } + else if (ddOpcode == 0xE5) // PUSH IX + { + mnemonic = "PUSH IX"; + instructionLength = 2; + } else if (ddOpcode == 0xE9) // JP (IX) { mnemonic = "JP (IX)"; @@ -1037,6 +1075,13 @@ namespace Desktop mnemonic = $"LD L, (IY{signL}{offsetL})"; instructionLength = 3; } + else if (fdOpcode == 0x72) // LD (IY+d), D + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IY{sign}{d}), D"; + instructionLength = 3; + } else if (fdOpcode == 0x74) // LD (IY+d), H { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs index 389d04b..6d1916f 100644 --- a/Desktop/Form1.Designer.cs +++ b/Desktop/Form1.Designer.cs @@ -34,21 +34,24 @@ // // picScreen // + picScreen.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; picScreen.BackColor = Color.Black; - picScreen.Location = new Point(12, 12); + picScreen.Location = new Point(13, 13); + picScreen.Margin = new Padding(4); picScreen.Name = "picScreen"; - picScreen.Size = new Size(768, 576); + picScreen.Size = new Size(960, 768); picScreen.SizeMode = PictureBoxSizeMode.Zoom; picScreen.TabIndex = 0; picScreen.TabStop = false; // // Form1 // - AutoScaleDimensions = new SizeF(8F, 20F); + AutoScaleDimensions = new SizeF(10F, 25F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(791, 596); + ClientSize = new Size(987, 791); Controls.Add(picScreen); KeyPreview = true; + Margin = new Padding(4); Name = "Form1"; Text = "Parsons Sinclair ZX Spectrum 48K - 2026"; ((System.ComponentModel.ISupportInitialize)picScreen).EndInit(); diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs index 9d2387d..8ef1a55 100644 --- a/Desktop/Form1.cs +++ b/Desktop/Form1.cs @@ -73,10 +73,19 @@ namespace Desktop 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]; + + // --- NEW: Expanded screen size (32px border on all sides) --- + const int screenWidth = 320; + const int screenHeight = 256; + const int borderSize = 32; + + int[] pixelData = new int[screenWidth * screenHeight]; + + // --- NEW: Fill the background with the Border Color --- + // (Note: The hardware border always uses standard brightness, never bright) + int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex]; + Array.Fill(pixelData, currentBorderColor); // Loop through the 6144 bytes of Pixel RAM for (int offset = 0; offset < 6144; offset++) @@ -84,14 +93,12 @@ namespace Desktop ushort address = (ushort)(0x4000 + offset); byte pixels = _memoryBus.Read(address); - // Unwind the Sinclair interlace int y = ((offset & 0x0700) >> 8) | ((offset & 0x00E0) >> 2) | ((offset & 0x1800) >> 5); int x = (offset & 0x001F) * 8; - // Fetch Color Attributes int attrRow = y / 8; int attrCol = x / 8; ushort attrAddress = (ushort)(0x5800 + (attrRow * 32) + attrCol); @@ -101,12 +108,12 @@ namespace Desktop 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; @@ -116,14 +123,20 @@ namespace Desktop for (int bit = 0; bit < 8; bit++) { bool isPixelSet = (pixels & (1 << (7 - bit))) != 0; - pixelData[(y * 256) + (x + bit)] = isPixelSet ? inkColor : paperColor; + + // --- NEW: Add the 32px border offset to our X and Y calculations! --- + int renderY = y + borderSize; + int renderX = x + borderSize + bit; + + // Map it to our new, wider pixel array + pixelData[(renderY * screenWidth) + renderX] = isPixelSet ? inkColor : paperColor; } } - // Blast it to the PictureBox - Bitmap bmp = new Bitmap(256, 192, PixelFormat.Format32bppArgb); + // --- NEW: Update Bitmap dimensions to match the new 320x256 array --- + Bitmap bmp = new Bitmap(screenWidth, screenHeight, PixelFormat.Format32bppArgb); BitmapData bmpData = bmp.LockBits( - new Rectangle(0, 0, 256, 192), + new Rectangle(0, 0, screenWidth, screenHeight), ImageLockMode.WriteOnly, bmp.PixelFormat); @@ -133,8 +146,6 @@ namespace Desktop if (picScreen.Image != null) picScreen.Image.Dispose(); picScreen.Image = bmp; } - - // Helper method to update the IO Bus state private void UpdateMatrix(int row, int col, bool isPressed) { if (isPressed) @@ -167,6 +178,16 @@ namespace Desktop { switch (key) { + // Row 1: A, S, D, F, G + case Keys.A: UpdateMatrix(1, 0, isPressed); break; + case Keys.S: UpdateMatrix(1, 1, isPressed); break; + case Keys.D: UpdateMatrix(1, 2, isPressed); break; + case Keys.F: UpdateMatrix(1, 3, isPressed); break; + case Keys.G: UpdateMatrix(1, 4, isPressed); break; + + // Row 5: + case Keys.P: UpdateMatrix(5, 0, isPressed); break; + // Row 6: ENTER, L, K, J, H case Keys.Enter: UpdateMatrix(6, 0, isPressed); break; case Keys.L: UpdateMatrix(6, 1, isPressed); break; @@ -179,21 +200,12 @@ namespace Desktop case Keys.M: UpdateMatrix(7, 2, isPressed); break; case Keys.N: UpdateMatrix(7, 3, isPressed); break; case Keys.B: UpdateMatrix(7, 4, isPressed); break; + case Keys.ControlKey: UpdateMatrix(7, 1, isPressed); break; // Symbol Shift - // Row 1: A, S, D, F, G - case Keys.A: UpdateMatrix(1, 0, isPressed); break; - case Keys.S: UpdateMatrix(1, 1, isPressed); break; - case Keys.D: UpdateMatrix(1, 2, isPressed); break; - case Keys.F: UpdateMatrix(1, 3, isPressed); break; - case Keys.G: UpdateMatrix(1, 4, isPressed); break; + - // Map the rest of the alphabet and numbers here as you need them! + } - } - - private void Form1_KeyDown(object sender, KeyEventArgs e) - { - - } + } } } \ No newline at end of file