Added flash attribute. Implemented more OpCodes

This commit is contained in:
2026-04-17 02:34:11 +01:00
parent c74d2cc764
commit 389df3780e
3 changed files with 349 additions and 12 deletions

View File

@@ -449,6 +449,25 @@ namespace Core.Cpu
if (result > 0xFF) AF.Low |= 0x01; 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) private void AddA(byte operand)
{ {
byte a = AF.High; byte a = AF.High;
@@ -527,6 +546,25 @@ namespace Core.Cpu
return 6; return 6;
// --- 8-Bit Increments --- // --- 8-Bit Increments ---
case 0x04: BC.High = Inc8(BC.High); return 4; // INC B 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' case 0x08: // EX AF, AF'
ushort tempAF = AF.Word; ushort tempAF = AF.Word;
AF.Word = AF_Prime.Word; AF.Word = AF_Prime.Word;
@@ -856,6 +894,24 @@ namespace Core.Cpu
case 0x85: Add(HL.Low); return 4; // ADD A, L case 0x85: Add(HL.Low); return 4; // ADD A, L
case 0x86: Add(_memory.Read(HL.Word)); return 7; // ADD A, (HL) case 0x86: Add(_memory.Read(HL.Word)); return 7; // ADD A, (HL)
case 0x87: Add(AF.High); return 4; // ADD A, A 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 0x90: Sub(BC.High); return 4; // SUB B
case 0x91: Sub(BC.Low); return 4; // SUB C case 0x91: Sub(BC.Low); return 4; // SUB C
case 0x92: Sub(DE.High); return 4; // SUB D case 0x92: Sub(DE.High); return 4; // SUB D
@@ -1149,6 +1205,23 @@ namespace Core.Cpu
return 4; // Takes 4 T-States return 4; // Takes 4 T-States
case 0xED: case 0xED:
return ExecuteExtendedPrefix(); 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 case 0xF1: // POP AF
AF.Word = Pop(); AF.Word = Pop();
return 10; return 10;
@@ -1183,6 +1256,8 @@ namespace Core.Cpu
// Fetch the actual extended instruction // Fetch the actual extended instruction
byte extendedOpcode = _memory.Read(PC++); byte extendedOpcode = _memory.Read(PC++);
byte val = 0; byte val = 0;
byte newFlags = 0;
int result = 0;
switch (extendedOpcode) switch (extendedOpcode)
{ {
@@ -1191,6 +1266,34 @@ namespace Core.Cpu
_memory.Write(dest43, BC.Low); _memory.Write(dest43, BC.Low);
_memory.Write((ushort)(dest43 + 1), BC.High); _memory.Write((ushort)(dest43 + 1), BC.High);
return 20; 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 case 0x47: // LD I, A
I = AF.High; I = AF.High;
return 9; return 9;
@@ -1215,6 +1318,40 @@ namespace Core.Cpu
DE.Low = _memory.Read(src5B); DE.Low = _memory.Read(src5B);
DE.High = _memory.Read((ushort)(src5B + 1)); DE.High = _memory.Read((ushort)(src5B + 1));
return 20; 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 case 0x73: // LD (nn), SP
ushort dest73 = FetchWord(); ushort dest73 = FetchWord();
_memory.Write(dest73, (byte)SP); _memory.Write(dest73, (byte)SP);
@@ -1230,7 +1367,7 @@ namespace Core.Cpu
// H (Bit 4) and N (Bit 1) are RESET. // H (Bit 4) and N (Bit 1) are RESET.
// C (Bit 0) is PRESERVED. // 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 & 0x80) != 0) newFlags |= 0x80; // Sign Flag
if (portVal78 == 0) newFlags |= 0x40; // Zero Flag if (portVal78 == 0) newFlags |= 0x40; // Zero Flag
@@ -1238,7 +1375,21 @@ namespace Core.Cpu
AF.Low = newFlags; 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 case 0xB0: // LDIR
// 1. Read byte from (HL) // 1. Read byte from (HL)
val = _memory.Read(HL.Word); val = _memory.Read(HL.Word);
@@ -1418,12 +1569,36 @@ namespace Core.Cpu
switch (ddOpcode) 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 case 0x21: // LD IX, nn
byte low = FetchByte(); byte low = FetchByte();
byte high = FetchByte(); byte high = FetchByte();
IX.Word = (ushort)((high << 8) | low); IX.Word = (ushort)((high << 8) | low);
return 14; // 14 T-States return 14;
case 0xE9: // JP (IX)
PC = IX.Word;
return 8;
default: default:
throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} not implemented!"); throw new NotImplementedException($"DD Prefix opcode 0x{ddOpcode:X2} not implemented!");
@@ -1441,6 +1616,43 @@ namespace Core.Cpu
case 0x21: // LD IY, nn case 0x21: // LD IY, nn
IY.Word = FetchWord(); IY.Word = FetchWord();
return 14; 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) case 0x35: // DEC (IY+d)
sbyte offset = (sbyte)FetchByte(); sbyte offset = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + offset); targetAddress = (ushort)(IY.Word + offset);
@@ -1515,6 +1727,16 @@ namespace Core.Cpu
_memory.Write(targetAddress, BC.Low); _memory.Write(targetAddress, BC.Low);
return 19; // Takes 19 T-States 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 case 0x75: // LD (IY+d), L
sbyte offset75 = (sbyte)FetchByte(); sbyte offset75 = (sbyte)FetchByte();
targetAddress = (ushort)(IY.Word + offset75); targetAddress = (ushort)(IY.Word + offset75);
@@ -1531,6 +1753,44 @@ namespace Core.Cpu
return 19; 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 case 0xCB: // The FD CB nested prefix
{ {
sbyte displacement = (sbyte)FetchByte(); sbyte displacement = (sbyte)FetchByte();

View File

@@ -582,7 +582,18 @@ namespace Desktop
case 0x85: mnemonic = "ADD A, L"; break; case 0x85: mnemonic = "ADD A, L"; break;
case 0x86: mnemonic = "ADD A, (HL)"; break; case 0x86: mnemonic = "ADD A, (HL)"; break;
case 0x87: mnemonic = "ADD A, A"; 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 --- // --- SUB r ---
case 0x90: mnemonic = "SUB B"; break; case 0x90: mnemonic = "SUB B"; break;
case 0x91: mnemonic = "SUB C"; break; case 0x91: mnemonic = "SUB C"; break;
@@ -783,6 +794,11 @@ namespace Desktop
mnemonic = $"CALL 0x{callDest:X4}"; mnemonic = $"CALL 0x{callDest:X4}";
instructionLength = 3; instructionLength = 3;
break; break;
case 0xCE: // ADC A, n
byte n = _memoryBus.Read((ushort)(currentPc + 1));
mnemonic = $"ADC A, 0x{n:X2}";
instructionLength = 2;
break;
case 0xD0: case 0xD0:
mnemonic = "RET NC"; mnemonic = "RET NC";
break; break;
@@ -809,13 +825,22 @@ namespace Desktop
case 0xDD: case 0xDD:
{ {
byte ddOpcode = _memoryBus.Read((ushort)(currentPc + 1)); byte ddOpcode = _memoryBus.Read((ushort)(currentPc + 1));
if (ddOpcode == 0x09) // ADD IX, BC
if (ddOpcode == 0x21) // LD IX, nn {
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)); ushort ixVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
mnemonic = $"LD IX, 0x{ixVal:X4}"; mnemonic = $"LD IX, 0x{ixVal:X4}";
instructionLength = 4; instructionLength = 4;
} }
else if (ddOpcode == 0xE9) // JP (IX)
{
mnemonic = "JP (IX)";
instructionLength = 2;
}
else else
{ {
mnemonic = $"DD PREFIX UNKNOWN (0x{ddOpcode:X2})"; mnemonic = $"DD PREFIX UNKNOWN (0x{ddOpcode:X2})";
@@ -856,6 +881,10 @@ namespace Desktop
mnemonic = $"LD (0x{bcAddr:X4}), BC"; mnemonic = $"LD (0x{bcAddr:X4}), BC";
instructionLength = 4; instructionLength = 4;
break; break;
case 0x44: // NEG
mnemonic = "NEG";
instructionLength = 2;
break;
case 0x47: case 0x47:
mnemonic = "LD I, A"; mnemonic = "LD I, A";
instructionLength = 2; // 0xED + 0x47 instructionLength = 2; // 0xED + 0x47
@@ -888,10 +917,19 @@ namespace Desktop
mnemonic = $"LD (0x{addr73:X4}), SP"; mnemonic = $"LD (0x{addr73:X4}), SP";
instructionLength = 4; instructionLength = 4;
break; break;
case 0x72: // SBC HL, SP
mnemonic = "SBC HL, SP";
instructionLength = 2;
break;
case 0x78: case 0x78:
mnemonic = "IN A, (C)"; mnemonic = "IN A, (C)";
instructionLength = 2; instructionLength = 2;
break; 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: case 0xB0:
mnemonic = "LDIR"; mnemonic = "LDIR";
instructionLength = 2; instructionLength = 2;
@@ -907,6 +945,11 @@ namespace Desktop
break; break;
} }
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 0xF1: mnemonic = "POP AF"; break;
case 0xF3: case 0xF3:
mnemonic = "DI"; mnemonic = "DI";
@@ -935,6 +978,13 @@ namespace Desktop
mnemonic = $"LD IY, 0x{iyVal:X4}"; mnemonic = $"LD IY, 0x{iyVal:X4}";
instructionLength = 4; 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) else if (fdOpcode == 0x35) // DEC (IY+d)
{ {
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
@@ -945,7 +995,7 @@ namespace Desktop
else if (fdOpcode == 0x36) // LD (IY+d), n else if (fdOpcode == 0x36) // LD (IY+d), n
{ {
sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); 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 ? "+" : ""; string sign = d >= 0 ? "+" : "";
mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}"; mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}";
instructionLength = 4; instructionLength = 4;
@@ -987,6 +1037,13 @@ namespace Desktop
mnemonic = $"LD L, (IY{signL}{offsetL})"; mnemonic = $"LD L, (IY{signL}{offsetL})";
instructionLength = 3; 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) else if (fdOpcode == 0x86) // ADD A, (IY+d)
{ {
sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
@@ -995,6 +1052,13 @@ namespace Desktop
mnemonic = $"ADD A, (IY{signAdd}{dAdd})"; mnemonic = $"ADD A, (IY{signAdd}{dAdd})";
instructionLength = 3; 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 else if (fdOpcode == 0xCB) // FD CB prefix
{ {
cbOp = _memoryBus.Read((ushort)(currentPc + 1)); cbOp = _memoryBus.Read((ushort)(currentPc + 1));

View File

@@ -14,6 +14,7 @@ namespace Desktop
private Z80 _cpu = null!; private Z80 _cpu = null!;
private MemoryBus _memoryBus = null!; private MemoryBus _memoryBus = null!;
private IO_Bus _simpleIoBus = null!; private IO_Bus _simpleIoBus = null!;
private int _ulaFrameCount = 0;
// The 16 physical colors of the ZX Spectrum (ARGB format) // The 16 physical colors of the ZX Spectrum (ARGB format)
private readonly int[] SpectrumColors = new int[] 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 so the Debugger's background thread can call it 50 times a second
public void RenderScreen() 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]; int[] pixelData = new int[256 * 192];
// Loop through the 6144 bytes of Pixel RAM // Loop through the 6144 bytes of Pixel RAM
@@ -95,10 +100,18 @@ namespace Desktop
int ink = attr & 0x07; int ink = attr & 0x07;
int paper = (attr >> 3) & 0x07; int paper = (attr >> 3) & 0x07;
int brightOffset = (attr & 0x40) != 0 ? 8 : 0; int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
bool isFlashSet = (attr & 0x80) != 0;
int inkColor = SpectrumColors[ink + brightOffset]; int inkColor = SpectrumColors[ink + brightOffset];
int paperColor = SpectrumColors[paper + 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 // Draw the 8 pixels
for (int bit = 0; bit < 8; bit++) for (int bit = 0; bit < 8; bit++)
{ {