Implemented SNA files. More OpCodes. Chuckie Egg Title SCreen!
This commit is contained in:
186
Core/Cpu/Z80.cs
186
Core/Cpu/Z80.cs
@@ -89,27 +89,40 @@ namespace Core.Cpu
|
||||
if (!IFF1) return 0;
|
||||
|
||||
// 2. Acknowledge the interrupt by immediately disabling further interrupts
|
||||
// This prevents an endless loop of interrupts triggering each other!
|
||||
IFF1 = false;
|
||||
IFF2 = false;
|
||||
|
||||
// 3. Push the current Program Counter to the stack so we can return
|
||||
// 3. Push the current Program Counter to the stack so we can return later
|
||||
Push(PC);
|
||||
|
||||
// 4. Jump to the Interrupt Service Routine
|
||||
// The ZX Spectrum standard is Mode 1, which maps directly to 0x0038
|
||||
// --- Interrupt Mode Dispatch ---
|
||||
if (InterruptMode == 1)
|
||||
{
|
||||
// IM 1: Hardcoded jump to ROM address 0x0038
|
||||
PC = 0x0038;
|
||||
return 13; // IM 1 hardware call takes 13 T-States
|
||||
}
|
||||
else if (InterruptMode == 2)
|
||||
{
|
||||
// IM 2: Dynamic Vectored Interrupts
|
||||
|
||||
// A. Form the pointer address: High byte is 'I', Low byte is the floating bus (0xFF)
|
||||
ushort vectorAddress = (ushort)((I << 8) | 0xFF);
|
||||
|
||||
// B. Read the actual 16-bit ISR address from that location in memory (Little-Endian)
|
||||
byte pcLow = _memory.Read(vectorAddress);
|
||||
byte pcHigh = _memory.Read((ushort)(vectorAddress + 1));
|
||||
|
||||
// C. Jump to the custom game routine!
|
||||
PC = (ushort)((pcHigh << 8) | pcLow);
|
||||
|
||||
return 19; // IM 2 hardware call takes 19 T-States
|
||||
}
|
||||
else
|
||||
{
|
||||
// (Games will use Mode 2 later, but the ROM uses Mode 1)
|
||||
// (IM 0 is theoretically possible but essentially unused on the standard Spectrum)
|
||||
throw new NotImplementedException($"Interrupt Mode {InterruptMode} not implemented!");
|
||||
}
|
||||
|
||||
// 5. The CPU takes exactly 13 T-States to process this hardware CALL
|
||||
return 13;
|
||||
}
|
||||
|
||||
// Helper method to calculate if a byte has an Even Parity of 1s
|
||||
@@ -146,6 +159,45 @@ namespace Core.Cpu
|
||||
return tStates;
|
||||
}
|
||||
|
||||
public void LoadSNA(byte[] snaData)
|
||||
{
|
||||
if (snaData.Length != 49179)
|
||||
throw new Exception("Invalid 48K SNA File Size!");
|
||||
|
||||
// --- 1. Load CPU Registers ---
|
||||
I = snaData[0];
|
||||
HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8));
|
||||
DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8));
|
||||
BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8));
|
||||
AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8));
|
||||
|
||||
HL.Word = (ushort)(snaData[9] | (snaData[10] << 8));
|
||||
DE.Word = (ushort)(snaData[11] | (snaData[12] << 8));
|
||||
BC.Word = (ushort)(snaData[13] | (snaData[14] << 8));
|
||||
IY.Word = (ushort)(snaData[15] | (snaData[16] << 8));
|
||||
IX.Word = (ushort)(snaData[17] | (snaData[18] << 8));
|
||||
|
||||
IFF2 = (snaData[19] & 0x04) != 0;
|
||||
IFF1 = IFF2;
|
||||
R = snaData[20];
|
||||
AF.Word = (ushort)(snaData[21] | (snaData[22] << 8));
|
||||
SP = (ushort)(snaData[23] | (snaData[24] << 8));
|
||||
InterruptMode = snaData[25];
|
||||
|
||||
// --- 2. Load the 48K RAM Dump ---
|
||||
// The RAM dump starts at byte 27 and maps perfectly to 0x4000 -> 0xFFFF
|
||||
for (int i = 0; i < 49152; i++)
|
||||
{
|
||||
_memory.Write((ushort)(0x4000 + i), snaData[27 + i]);
|
||||
}
|
||||
|
||||
// --- 3. The Magic Bullet ---
|
||||
// In the SNA format, the Program Counter (PC) isn't in the header.
|
||||
// It was PUSHED to the stack exactly 1 instruction before the snapshot was saved.
|
||||
// So, we just pop it off the stack to resume execution!
|
||||
PC = Pop();
|
||||
}
|
||||
|
||||
private void HandleInstantTapeLoad()
|
||||
{
|
||||
// 1. Grab the next block from the virtual cassette
|
||||
@@ -175,10 +227,13 @@ namespace Core.Cpu
|
||||
IX.Word = (ushort)(IX.Word + bytesToCopy);
|
||||
DE.Word = 0;
|
||||
|
||||
// 5. Set the Carry Flag to 1 (Success)
|
||||
AF.Low |= 0x01;
|
||||
// 5. Simulate the Checksum Match (Accumulator becomes 0)
|
||||
AF.High = 0x00;
|
||||
|
||||
// 6. Simulate a standard 'RET' instruction to exit the LD-BYTES routine safely
|
||||
// 6. Set Carry Flag (Bit 0) for Success, and Zero Flag (Bit 6) for Checksum Match
|
||||
AF.Low |= 0x41; // 0x41 is binary 0100 0001 (Zero and Carry both set)
|
||||
|
||||
// 7. Simulate a standard 'RET' instruction to exit the LD-BYTES routine safely
|
||||
ExecuteRet();
|
||||
}
|
||||
|
||||
@@ -522,6 +577,36 @@ namespace Core.Cpu
|
||||
AF.High = (byte)result;
|
||||
}
|
||||
|
||||
private void Adc16(ushort value)
|
||||
{
|
||||
int hl = HL.Word;
|
||||
int carry = AF.Low & 0x01;
|
||||
|
||||
// Calculate the raw integer result to check for overflows
|
||||
int result = hl + value + carry;
|
||||
|
||||
// --- Update Flags (F Register) ---
|
||||
byte newFlags = 0; // Clear all flags (which forces N to 0, correctly!)
|
||||
|
||||
// Sign Flag (Bit 7) - Set if the 16-bit result is negative (bit 15 is 1)
|
||||
if ((result & 0x8000) != 0) newFlags |= 0x80;
|
||||
|
||||
// Zero Flag (Bit 6) - Set if the entire 16-bit result is exactly 0
|
||||
if ((result & 0xFFFF) == 0) newFlags |= 0x40;
|
||||
|
||||
// Half-Carry Flag (Bit 4) - Set if there is a carry out of bit 11
|
||||
if (((hl & 0x0FFF) + (value & 0x0FFF) + carry) > 0x0FFF) newFlags |= 0x10;
|
||||
|
||||
// Overflow Flag (Bit 2) - Set if operands have the SAME sign, but result sign changes
|
||||
if ((((hl ^ ~value) & 0x8000) != 0) && (((hl ^ result) & 0x8000) != 0)) newFlags |= 0x04;
|
||||
|
||||
// Carry Flag (Bit 0) - Set if the overall 16-bit result overflowed 0xFFFF
|
||||
if (result > 0xFFFF) newFlags |= 0x01;
|
||||
|
||||
AF.Low = newFlags;
|
||||
HL.Word = (ushort)result;
|
||||
}
|
||||
|
||||
private void AddA(byte operand)
|
||||
{
|
||||
byte a = AF.High;
|
||||
@@ -625,6 +710,9 @@ namespace Core.Cpu
|
||||
AF.Word = AF_Prime.Word;
|
||||
AF_Prime.Word = tempAF;
|
||||
return 4;
|
||||
case 0x0A: //LD A (BC)
|
||||
AF.High = _memory.Read(BC.Word);
|
||||
return 7;
|
||||
case 0x0C: BC.Low = Inc8(BC.Low); return 4; // INC C
|
||||
case 0x12: // LD (DE), A
|
||||
_memory.Write(DE.Word, AF.High);
|
||||
@@ -801,20 +889,19 @@ namespace Core.Cpu
|
||||
return 7;
|
||||
case 0x28: // JR Z, e
|
||||
offset = (sbyte)FetchByte();
|
||||
|
||||
// Check if the Zero Flag (Bit 6) IS set
|
||||
// Check if the Zero Flag is set
|
||||
if ((AF.Low & 0x40) != 0)
|
||||
{
|
||||
PC = (ushort)(PC + offset);
|
||||
return 12; // Jump taken
|
||||
return 12;
|
||||
}
|
||||
return 7; // Jump not taken
|
||||
return 7;
|
||||
case 0x2A: // LD HL, (nn)
|
||||
{
|
||||
ushort srcAddress = FetchWord();
|
||||
HL.Low = _memory.Read(srcAddress);
|
||||
HL.High = _memory.Read((ushort)(srcAddress + 1));
|
||||
return 16; // Takes 16 T-States
|
||||
return 16;
|
||||
}
|
||||
case 0x2B: // DEC HL
|
||||
HL.Word--;
|
||||
@@ -1345,6 +1432,9 @@ namespace Core.Cpu
|
||||
|
||||
switch (extendedOpcode)
|
||||
{
|
||||
case 0x42: // SBC HL, BC
|
||||
Sbc16(BC.Word);
|
||||
return 15;
|
||||
case 0x43: // LD (nn), BC
|
||||
ushort dest43 = FetchWord();
|
||||
_memory.Write(dest43, BC.Low);
|
||||
@@ -1381,11 +1471,17 @@ namespace Core.Cpu
|
||||
case 0x47: // LD I, A
|
||||
I = AF.High;
|
||||
return 9;
|
||||
case 0x4A: // ADC HL, BC
|
||||
Adc16(BC.Word);
|
||||
return 15;
|
||||
case 0x4B: // LD BC, (nn)
|
||||
ushort src4B = FetchWord();
|
||||
BC.Low = _memory.Read(src4B);
|
||||
BC.High = _memory.Read((ushort)(src4B + 1));
|
||||
return 20; // Takes 20 T-States
|
||||
return 20;
|
||||
case 0x4D: // RETI Does not affect IFF1 or IFF2
|
||||
PC = Pop();
|
||||
return 14;
|
||||
case 0x52: // SBC HL, DE
|
||||
Sbc16(DE.Word);
|
||||
return 15;
|
||||
@@ -1397,11 +1493,20 @@ namespace Core.Cpu
|
||||
case 0x56: // IM 1
|
||||
InterruptMode = 1;
|
||||
return 8;
|
||||
case 0x5A: // ADC HL, DE
|
||||
Adc16(DE.Word);
|
||||
return 15;
|
||||
case 0x5B: // LD DE, (nn)
|
||||
ushort src5B = FetchWord();
|
||||
DE.Low = _memory.Read(src5B);
|
||||
DE.High = _memory.Read((ushort)(src5B + 1));
|
||||
return 20;
|
||||
case 0x62: // SBC HL, HL
|
||||
Sbc16(HL.Word);
|
||||
return 15;
|
||||
case 0x6A: // ADC HL, HL
|
||||
Adc16(HL.Word);
|
||||
return 15;
|
||||
case 0x72: // SBC HL, SP
|
||||
int carryIn = AF.Low & 0x01;
|
||||
int hlVal = HL.Word;
|
||||
@@ -1462,7 +1567,10 @@ namespace Core.Cpu
|
||||
return 12;
|
||||
case 0x79: // OUT (C), A
|
||||
_simpleIoBus.WritePort(BC.Word, AF.High);
|
||||
return 12; // 12 T-States
|
||||
return 12;
|
||||
case 0x7A: // ADC HL, SP
|
||||
Adc16(SP);
|
||||
return 15;
|
||||
case 0x7B: // LD SP, (nn)
|
||||
// 1. Fetch the absolute 16-bit memory address from the instruction stream
|
||||
byte addrLow = FetchByte();
|
||||
@@ -1540,6 +1648,7 @@ namespace Core.Cpu
|
||||
private int ExecuteCBPrefix()
|
||||
{
|
||||
byte cbOpcode = FetchByte();
|
||||
bool oldCarry = false;
|
||||
|
||||
// Extract the exact same mathematical properties
|
||||
int operation = cbOpcode >> 6; // 00 = Shift, 01 = BIT, 10 = RES, 11 = SET
|
||||
@@ -1602,6 +1711,33 @@ namespace Core.Cpu
|
||||
// Shift left, and loop the falling bit back into Bit 0
|
||||
val = (byte)((val << 1) | (carryOut ? 1 : 0));
|
||||
break;
|
||||
case 1: // RRC (Rotate Right Circular)
|
||||
// 1. Grab Bit 0 before it falls off to set the Carry flag and loop to Bit 7
|
||||
carryOut = (val & 0x01) != 0;
|
||||
|
||||
// 2. Shift right by 1, and loop the falling bit directly back into Bit 7
|
||||
val = (byte)((val >> 1) | (carryOut ? 0x80 : 0x00));
|
||||
break;
|
||||
case 2: // RL (Rotate Left through Carry)
|
||||
// 1. Grab the CURRENT Carry flag from the AF register
|
||||
oldCarry = (AF.Low & 0x01) != 0;
|
||||
|
||||
// 2. Grab Bit 7 before it falls off to become the NEW Carry flag
|
||||
carryOut = (val & 0x80) != 0;
|
||||
|
||||
// 3. Shift left by 1, and drop the OLD carry flag directly into Bit 0
|
||||
val = (byte)((val << 1) | (oldCarry ? 0x01 : 0x00));
|
||||
break;
|
||||
case 3: // RR (Rotate Right through Carry)
|
||||
// 1. Grab the CURRENT Carry flag from the AF register
|
||||
oldCarry = (AF.Low & 0x01) != 0;
|
||||
|
||||
// 2. Grab Bit 0 before it falls off to become the NEW Carry flag
|
||||
carryOut = (val & 0x01) != 0;
|
||||
|
||||
// 3. Shift right by 1, and drop the OLD carry flag into Bit 7
|
||||
val = (byte)((val >> 1) | (oldCarry ? 0x80 : 0x00));
|
||||
break;
|
||||
case 4: // SLA (Shift Left Arithmetic)
|
||||
// 1. Grab Bit 7 before it falls off to set the Carry flag
|
||||
carryOut = (val & 0x80) != 0;
|
||||
@@ -1610,6 +1746,20 @@ namespace Core.Cpu
|
||||
// (In C#, a standard left shift automatically pads Bit 0 with a 0)
|
||||
val = (byte)(val << 1);
|
||||
break;
|
||||
case 5: // SRA (Shift Right Arithmetic)
|
||||
// 1. Grab Bit 0 before it falls off to set the Carry flag
|
||||
carryOut = (val & 0x01) != 0;
|
||||
|
||||
// 2. Grab the current Sign bit (Bit 7) so we can preserve it
|
||||
byte signBit = (byte)(val & 0x80);
|
||||
|
||||
// 3. Shift the byte right by 1.
|
||||
// (Because 'val' is unsigned, C# naturally pads the top with a 0)
|
||||
val = (byte)(val >> 1);
|
||||
|
||||
// 4. Force the preserved sign bit back into Bit 7
|
||||
val |= signBit;
|
||||
break;
|
||||
case 7: // SRL (Shift Right Logical)
|
||||
// 1. Grab Bit 0 before it falls off to set the Carry flag
|
||||
carryOut = (val & 0x01) != 0;
|
||||
|
||||
@@ -323,6 +323,7 @@ namespace Desktop
|
||||
case 0x08:
|
||||
mnemonic = "EX AF, AF'";
|
||||
break;
|
||||
case 0x0A: mnemonic = "LD A, (BC)"; break;
|
||||
case 0x12: mnemonic = "LD (DE), A"; break;
|
||||
case 0x13: mnemonic = "INC DE"; break;
|
||||
case 0x33: mnemonic = "INC SP"; break;
|
||||
@@ -391,9 +392,7 @@ namespace Desktop
|
||||
break;
|
||||
case 0x18:
|
||||
sbyte dUnconditional = (sbyte)_memoryBus.Read((ushort)(currentPc + 1));
|
||||
// Calculate the target address based on the PC *after* this 2-byte instruction
|
||||
ushort targetAddressUnconditional = (ushort)(currentPc + 2 + dUnconditional);
|
||||
|
||||
mnemonic = $"JR 0x{targetAddressUnconditional:X4}";
|
||||
instructionLength = 2;
|
||||
break;
|
||||
@@ -981,6 +980,7 @@ namespace Desktop
|
||||
|
||||
switch (extendedOp)
|
||||
{
|
||||
case 0x42: mnemonic = "SBC HL, BC"; instructionLength = 2; break;
|
||||
case 0x43:
|
||||
ushort bcAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
|
||||
mnemonic = $"LD (0x{bcAddr:X4}), BC";
|
||||
@@ -994,11 +994,17 @@ namespace Desktop
|
||||
mnemonic = "LD I, A";
|
||||
instructionLength = 2; // 0xED + 0x47
|
||||
break;
|
||||
// Inside your ED prefix switch statement in the debugger:
|
||||
case 0x4A: mnemonic = "ADC HL, BC"; instructionLength = 2; break;
|
||||
case 0x4B:
|
||||
ushort addr4B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
|
||||
mnemonic = $"LD BC, (0x{addr4B:X4})";
|
||||
instructionLength = 4;
|
||||
break;
|
||||
case 0x4D:
|
||||
mnemonic = "RETI";
|
||||
instructionLength = 2;
|
||||
break;
|
||||
case 0x52:
|
||||
mnemonic = "SBC HL, DE";
|
||||
instructionLength = 2; // ED 52
|
||||
@@ -1012,11 +1018,14 @@ namespace Desktop
|
||||
mnemonic = "IM 1";
|
||||
instructionLength = 2;
|
||||
break;
|
||||
case 0x5A: mnemonic = "ADC HL, DE"; instructionLength = 2; break;
|
||||
case 0x5B:
|
||||
ushort addr5B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
|
||||
mnemonic = $"LD DE, (0x{addr5B:X4})";
|
||||
instructionLength = 4;
|
||||
break;
|
||||
case 0x6A: mnemonic = "ADC HL, HL"; instructionLength = 2; break;
|
||||
case 0x62: mnemonic = "SBC HL, HL"; instructionLength = 2; break;
|
||||
case 0x73:
|
||||
ushort addr73 = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8));
|
||||
mnemonic = $"LD (0x{addr73:X4}), SP";
|
||||
@@ -1030,6 +1039,7 @@ namespace Desktop
|
||||
mnemonic = "IN A, (C)";
|
||||
instructionLength = 2;
|
||||
break;
|
||||
case 0x7A: mnemonic = "ADC HL, SP"; 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})";
|
||||
|
||||
15
Desktop/Form1.Designer.cs
generated
15
Desktop/Form1.Designer.cs
generated
@@ -32,6 +32,7 @@
|
||||
menuStrip1 = new MenuStrip();
|
||||
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||
openToolStripMenuItem = new ToolStripMenuItem();
|
||||
openSnapshotToolStripMenuItem = new ToolStripMenuItem();
|
||||
((System.ComponentModel.ISupportInitialize)picScreen).BeginInit();
|
||||
menuStrip1.SuspendLayout();
|
||||
SuspendLayout();
|
||||
@@ -59,7 +60,7 @@
|
||||
//
|
||||
// fileToolStripMenuItem
|
||||
//
|
||||
fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { openToolStripMenuItem });
|
||||
fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { openToolStripMenuItem, openSnapshotToolStripMenuItem });
|
||||
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
|
||||
fileToolStripMenuItem.Size = new Size(54, 29);
|
||||
fileToolStripMenuItem.Text = "File";
|
||||
@@ -67,10 +68,17 @@
|
||||
// openToolStripMenuItem
|
||||
//
|
||||
openToolStripMenuItem.Name = "openToolStripMenuItem";
|
||||
openToolStripMenuItem.Size = new Size(158, 34);
|
||||
openToolStripMenuItem.Text = "Open";
|
||||
openToolStripMenuItem.Size = new Size(270, 34);
|
||||
openToolStripMenuItem.Text = "Open TAP";
|
||||
openToolStripMenuItem.Click += loadTAPToolStripMenuItem_Click;
|
||||
//
|
||||
// openSnapshotToolStripMenuItem
|
||||
//
|
||||
openSnapshotToolStripMenuItem.Name = "openSnapshotToolStripMenuItem";
|
||||
openSnapshotToolStripMenuItem.Size = new Size(270, 34);
|
||||
openSnapshotToolStripMenuItem.Text = "Open Snapshot";
|
||||
openSnapshotToolStripMenuItem.Click += openSNAToolStripMenuItem_Click;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(10F, 25F);
|
||||
@@ -96,5 +104,6 @@
|
||||
private MenuStrip menuStrip1;
|
||||
private ToolStripMenuItem fileToolStripMenuItem;
|
||||
private ToolStripMenuItem openToolStripMenuItem;
|
||||
private ToolStripMenuItem openSnapshotToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,18 @@ namespace Desktop
|
||||
}
|
||||
}
|
||||
}
|
||||
private void openSNAToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using (OpenFileDialog ofd = new OpenFileDialog())
|
||||
{
|
||||
ofd.Filter = "Spectrum Snapshot Files (*.sna)|*.sna";
|
||||
if (ofd.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
byte[] snaBytes = System.IO.File.ReadAllBytes(ofd.FileName);
|
||||
_cpu.LoadSNA(snaBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public so the Debugger's background thread can call it 50 times a second
|
||||
public void RenderScreen()
|
||||
|
||||
Reference in New Issue
Block a user