diff --git a/Assets/Iris.meta b/Assets/Iris.meta new file mode 100644 index 0000000..3e37f79 --- /dev/null +++ b/Assets/Iris.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c85535aab73500e4e93bc6a3430ff694 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.CPU.meta b/Assets/Iris/Iris.CPU.meta new file mode 100644 index 0000000..31e9ced --- /dev/null +++ b/Assets/Iris/Iris.CPU.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a8eb82c88394f8a409faf989c85e64b4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs b/Assets/Iris/Iris.CPU/ARM_Interpreter.cs new file mode 100644 index 0000000..52eab54 --- /dev/null +++ b/Assets/Iris/Iris.CPU/ARM_Interpreter.cs @@ -0,0 +1,2041 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Iris.CPU.CPU_Core; + +namespace Iris.CPU +{ + internal sealed class ARM_Interpreter + { + private readonly CPU_Core _cpu; + private readonly InstructionLUTEntry[] _instructionLUT = new InstructionLUTEntry[1 << 12]; + + internal ARM_Interpreter(CPU_Core cpu) + { + _cpu = cpu; + + unsafe + { + InstructionListEntry[] InstructionList = + [ + // ADC + new(0x0fe0_0000, 0x02a0_0000, &ADC, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x00a0_0000, &ADC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x00a0_0080, &ADC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x00a0_0010, &ADC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // ADD + new(0x0fe0_0000, 0x0280_0000, &ADD, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0080_0000, &ADD, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0080_0080, &ADD, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0080_0010, &ADD, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // AND + new(0x0fe0_0000, 0x0200_0000, &AND, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0000_0000, &AND, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0000_0080, &AND, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0000_0010, &AND, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // B + new(0x0f00_0000, 0x0a00_0000, &B, [Model.ARM7TDMI]), + + // BL + new(0x0f00_0000, 0x0b00_0000, &BL, [Model.ARM7TDMI]), + + // BIC + new(0x0fe0_0000, 0x03c0_0000, &BIC, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x01c0_0000, &BIC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x01c0_0080, &BIC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x01c0_0010, &BIC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // BX + new(0x0fff_fff0, 0x012f_ff10, &BX, [Model.ARM7TDMI]), + + // CMN + new(0x0ff0_f000, 0x0370_0000, &CMN, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0170_0000, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0170_0080, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0170_0010, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + new(0x0ff0_f000, 0x0370_f000, &CMN, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0170_f000, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0170_f080, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0170_f010, &CMN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // CMP + new(0x0ff0_f000, 0x0350_0000, &CMP, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0150_0000, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0150_0080, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0150_0010, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + new(0x0ff0_f000, 0x0350_f000, &CMP, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0150_f000, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0150_f080, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0150_f010, &CMP, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // EOR + new(0x0fe0_0000, 0x0220_0000, &EOR, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0020_0000, &EOR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0020_0080, &EOR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0020_0010, &EOR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // LDM + new(0x0e50_0000, 0x0810_0000, &LDM1, [Model.ARM7TDMI]), + new(0x0e50_8000, 0x0850_0000, &LDM2, [Model.ARM7TDMI]), + //new(0x0e50_8000, 0x0850_8000, &LDM3, new List{ Model.ARM7TDMI }), + + // LDR + new(0x0c50_0000, 0x0410_0000, &LDR, [Model.ARM7TDMI]), + + // LDRB + new(0x0c50_0000, 0x0450_0000, &LDRB, [Model.ARM7TDMI]), + + // LDRH + new(0x0e10_00f0, 0x0010_00b0, &LDRH, [Model.ARM7TDMI]), + + // LDRSB + new(0x0e10_00f0, 0x0010_00d0, &LDRSB, [Model.ARM7TDMI]), + + // LDRSH + new(0x0e10_00f0, 0x0010_00f0, &LDRSH, [Model.ARM7TDMI]), + + // MLA + new(0x0fe0_00f0, 0x0020_0090, &MLA, [Model.ARM7TDMI]), + + // MOV + new(0x0fef_0000, 0x03a0_0000, &MOV, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fef_0090, 0x01a0_0000, &MOV, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fef_0090, 0x01a0_0080, &MOV, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fef_0090, 0x01a0_0010, &MOV, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // MRS + new(0x0fbf_0fff, 0x010f_0000, &MRS, [Model.ARM7TDMI]), + + // MSR + new(0x0fb0_f000, 0x0320_f000, &MSR, [Model.ARM7TDMI]), // Immediate operand + new(0x0fb0_fff0, 0x0120_f000, &MSR, [Model.ARM7TDMI]), // Register operand + + // MUL + new(0x0fe0_f0f0, 0x0000_0090, &MUL, [Model.ARM7TDMI]), + + // MVN + new(0x0fef_0000, 0x03e0_0000, &MVN, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fef_0090, 0x01e0_0000, &MVN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fef_0090, 0x01e0_0080, &MVN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fef_0090, 0x01e0_0010, &MVN, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // ORR + new(0x0fe0_0000, 0x0380_0000, &ORR, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0180_0000, &ORR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0180_0080, &ORR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0180_0010, &ORR, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // RSB + new(0x0fe0_0000, 0x0260_0000, &RSB, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0060_0000, &RSB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0060_0080, &RSB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0060_0010, &RSB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // RSC + new(0x0fe0_0000, 0x02e0_0000, &RSC, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x00e0_0000, &RSC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x00e0_0080, &RSC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x00e0_0010, &RSC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // SBC + new(0x0fe0_0000, 0x02c0_0000, &SBC, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x00c0_0000, &SBC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x00c0_0080, &SBC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x00c0_0010, &SBC, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // SMLAL + new(0x0fe0_00f0, 0x00e0_0090, &SMLAL, [Model.ARM7TDMI]), + + // SMULL + new(0x0fe0_00f0, 0x00c0_0090, &SMULL, [Model.ARM7TDMI]), + + // STM + new(0x0e50_0000, 0x0800_0000, &STM1, [Model.ARM7TDMI]), + new(0x0e70_0000, 0x0840_0000, &STM2, [Model.ARM7TDMI]), + + // STR + new(0x0c50_0000, 0x0400_0000, &STR, [Model.ARM7TDMI]), + + // STRB + new(0x0c50_0000, 0x0440_0000, &STRB, [Model.ARM7TDMI]), + + // STRH + new(0x0e10_00f0, 0x0000_00b0, &STRH, [Model.ARM7TDMI]), + + // SUB + new(0x0fe0_0000, 0x0240_0000, &SUB, [Model.ARM7TDMI]), // I bit is 1 + new(0x0fe0_0090, 0x0040_0000, &SUB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0fe0_0090, 0x0040_0080, &SUB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0fe0_0090, 0x0040_0010, &SUB, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // SWI + new(0x0f00_0000, 0x0f00_0000, &SWI, [Model.ARM7TDMI]), + + // SWP + new(0x0ff0_0ff0, 0x0100_0090, &SWP, [Model.ARM7TDMI]), + + // SWPB + new(0x0ff0_0ff0, 0x0140_0090, &SWPB, [Model.ARM7TDMI]), + + // TEQ + new(0x0ff0_f000, 0x0330_0000, &TEQ, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0130_0000, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0130_0080, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0130_0010, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + new(0x0ff0_f000, 0x0330_f000, &TEQ, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0130_f000, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0130_f080, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0130_f010, &TEQ, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // TST + new(0x0ff0_f000, 0x0310_0000, &TST, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0110_0000, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0110_0080, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0110_0010, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + new(0x0ff0_f000, 0x0310_f000, &TST, [Model.ARM7TDMI]), // I bit is 1 + new(0x0ff0_f090, 0x0110_f000, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 0 + new(0x0ff0_f090, 0x0110_f080, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 1 and bit[4] is 0 + new(0x0ff0_f090, 0x0110_f010, &TST, [Model.ARM7TDMI]), // I bit is 0, bit[7] is 0 and bit[4] is 1 + + // UMLAL + new(0x0fe0_00f0, 0x00a0_0090, &UMLAL, [Model.ARM7TDMI]), + + // UMULL + new(0x0fe0_00f0, 0x0080_0090, &UMULL, [Model.ARM7TDMI]), + ]; + + for (UInt32 instruction = 0; instruction < _instructionLUT.Length; ++instruction) + { + bool unknownInstruction = true; + + foreach (InstructionListEntry entry in InstructionList) + { + if (((instruction & InstructionLUTHash(entry._mask)) == InstructionLUTHash(entry._expected)) && (entry._modelList.Contains(_cpu._model))) + { + _instructionLUT[instruction] = new(entry._handler); + unknownInstruction = false; + break; + } + } + + if (unknownInstruction) + _instructionLUT[instruction] = new(&UNKNOWN); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt32 InstructionLUTHash(UInt32 value) + { + return ((value >> 16) & 0xff0) | ((value >> 4) & 0x00f); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal UInt64 Step() + { + UInt32 instruction = _cpu._callbackInterface._read32(_cpu.NextInstructionAddress); + _cpu.NextInstructionAddress += 4; + + UInt32 cond = (instruction >> 28) & 0b1111; + + if (_cpu.ConditionPassed(cond)) + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(_cpu.Reg); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regPC = _cpu.NextInstructionAddress + 4; + + ref InstructionLUTEntry instructionLUTDataRef = ref MemoryMarshal.GetArrayDataReference(_instructionLUT); + ref InstructionLUTEntry instructionLUTEntry = ref Unsafe.Add(ref instructionLUTDataRef, InstructionLUTHash(instruction)); + + unsafe + { + return instructionLUTEntry._handler(_cpu, instruction); + } + } + else + { + return 1; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetPC(CPU_Core cpu, UInt32 value) + { + cpu.NextInstructionAddress = value; + } + + private static void SetReg(CPU_Core cpu, UInt32 i, UInt32 value) + { + if (i == PC) + { + SetPC(cpu, value); + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = value; + } + } + + // Addressing mode 1 + private static (UInt32 shifterOperand, UInt32 shifterCarryOut) GetShifterOperand(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + + UInt32 shifterOperand = 0; + UInt32 shifterCarryOut = 0; + + if (i == 1) // 32-bit immediate + { + UInt32 rotateImm = (instruction >> 8) & 0b1111; + UInt32 imm = instruction & 0xff; + + shifterOperand = BitOperations.RotateRight(imm, (int)(rotateImm * 2)); + shifterCarryOut = (rotateImm == 0) ? cpu.GetFlag(Flag.C) : (shifterOperand >> 31); + } + else + { + UInt32 shift = (instruction >> 5) & 0b11; + UInt32 r = (instruction >> 4) & 1; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + if (r == 0) // Immediate shifts + { + UInt32 shiftImm = (instruction >> 7) & 0b1_1111; + + UInt32 value = regRm; + int shiftAmount = (int)shiftImm; + + switch (shift) + { + case 0b00: // Logical shift left + if (shiftAmount == 0) + { + shifterOperand = value; + shifterCarryOut = cpu.GetFlag(Flag.C); + } + else + { + shifterOperand = value << shiftAmount; + shifterCarryOut = (value >> (32 - shiftAmount)) & 1; + } + break; + case 0b01: // Logical shift right + if (shiftAmount == 0) + { + shifterOperand = 0; + shifterCarryOut = value >> 31; + } + else + { + shifterOperand = value >> shiftAmount; + shifterCarryOut = (value >> (shiftAmount - 1)) & 1; + } + break; + case 0b10: // Arithmetic shift right + if (shiftAmount == 0) + { + shifterOperand = ((value >> 31) == 0) ? 0 : 0xffff_ffff; + shifterCarryOut = value >> 31; + } + else + { + shifterOperand = ArithmeticShiftRight(value, shiftAmount); + shifterCarryOut = (value >> (shiftAmount - 1)) & 1; + } + break; + case 0b11: // Rotate right + if (shiftAmount == 0) + { + shifterOperand = (cpu.GetFlag(Flag.C) << 31) | (value >> 1); + shifterCarryOut = value & 1; + } + else + { + shifterOperand = BitOperations.RotateRight(value, shiftAmount); + shifterCarryOut = (value >> (shiftAmount - 1)) & 1; + } + break; + } + } + else // Register shifts + { + UInt32 rs = (instruction >> 8) & 0b1111; + + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + UInt32 value = (rm == PC) ? (regPC + 4) : regRm; + int shiftAmount = (int)(regRs & 0xff); + + switch (shift) + { + case 0b00: // Logical shift left + if (shiftAmount == 0) + { + shifterOperand = value; + shifterCarryOut = cpu.GetFlag(Flag.C); + } + else if (shiftAmount < 32) + { + shifterOperand = value << shiftAmount; + shifterCarryOut = (value >> (32 - shiftAmount)) & 1; + } + else if (shiftAmount == 32) + { + shifterOperand = 0; + shifterCarryOut = value & 1; + } + else + { + shifterOperand = 0; + shifterCarryOut = 0; + } + break; + case 0b01: // Logical shift right + if (shiftAmount == 0) + { + shifterOperand = value; + shifterCarryOut = cpu.GetFlag(Flag.C); + } + else if (shiftAmount < 32) + { + shifterOperand = value >> shiftAmount; + shifterCarryOut = (value >> (shiftAmount - 1)) & 1; + } + else if (shiftAmount == 32) + { + shifterOperand = 0; + shifterCarryOut = value >> 31; + } + else + { + shifterOperand = 0; + shifterCarryOut = 0; + } + break; + case 0b10: // Arithmetic shift right + if (shiftAmount == 0) + { + shifterOperand = value; + shifterCarryOut = cpu.GetFlag(Flag.C); + } + else if (shiftAmount < 32) + { + shifterOperand = ArithmeticShiftRight(value, shiftAmount); + shifterCarryOut = (value >> (shiftAmount - 1)) & 1; + } + else + { + shifterOperand = ((value >> 31) == 0) ? 0 : 0xffff_ffff; + shifterCarryOut = value >> 31; + } + break; + case 0b11: // Rotate right + if (shiftAmount == 0) + { + shifterOperand = value; + shifterCarryOut = cpu.GetFlag(Flag.C); + } + else if ((shiftAmount & 0b1_1111) == 0) + { + shifterOperand = value; + shifterCarryOut = value >> 31; + } + else + { + shifterOperand = BitOperations.RotateRight(value, shiftAmount & 0b1_1111); + shifterCarryOut = (value >> ((shiftAmount & 0b1_1111) - 1)) & 1; + } + break; + } + } + } + + return (shifterOperand, shifterCarryOut); + } + + // Addressing mode 2 + private static UInt32 GetAddress(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 p = (instruction >> 24) & 1; + UInt32 u = (instruction >> 23) & 1; + UInt32 w = (instruction >> 21) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 index = 0; + + if (i == 0) // Immediate + { + UInt32 offset = instruction & 0xfff; + + index = offset; + } + else + { + UInt32 shiftImm = (instruction >> 7) & 0b1_1111; + UInt32 shift = (instruction >> 5) & 0b11; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + if ((shiftImm == 0) && (shift == 0)) // Register + { + index = regRm; + } + else // Scaled register + { + switch (shift) + { + case 0b00: // LSL + index = regRm << (int)shiftImm; + break; + case 0b01: // LSR + if (shiftImm == 0) + index = 0; + else + index = regRm >> (int)shiftImm; + break; + case 0b10: // ASR + if (shiftImm == 0) + index = ((regRm >> 31) == 1) ? 0xffff_ffff : 0; + else + index = ArithmeticShiftRight(regRm, (int)shiftImm); + break; + case 0b11: + if (shiftImm == 0) // RRX + index = (cpu.GetFlag(Flag.C) << 31) | (regRm >> 1); + else // ROR + index = BitOperations.RotateRight(regRm, (int)shiftImm); + break; + } + } + } + + UInt32 regRnIndexed = (u == 1) ? (regRn + index) : (regRn - index); + + UInt32 address; + + if (p == 0) // Post-indexed + { + address = regRn; + SetReg(cpu, rn, regRnIndexed); + } + else if (w == 0) // Offset + { + address = regRnIndexed; + } + else // Pre-indexed + { + address = regRnIndexed; + SetReg(cpu, rn, regRnIndexed); + } + + return address; + } + + // Addressing mode 3 + private static UInt32 GetAddress_Misc(CPU_Core cpu, UInt32 instruction) + { + UInt32 p = (instruction >> 24) & 1; + UInt32 u = (instruction >> 23) & 1; + UInt32 i = (instruction >> 22) & 1; + UInt32 w = (instruction >> 21) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 index; + + if (i == 1) // Immediate + { + UInt32 immH = (instruction >> 8) & 0b1111; + UInt32 immL = instruction & 0b1111; + + UInt32 offset = (immH << 4) | immL; + index = offset; + } + else // Register + { + UInt32 rm = instruction & 0b1111; + + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + index = regRm; + } + + UInt32 regRnIndexed = (u == 1) ? (regRn + index) : (regRn - index); + + UInt32 address; + + if (p == 0) // Post-indexed + { + address = regRn; + SetReg(cpu, rn, regRnIndexed); + } + else if (w == 0) // Offset + { + address = regRnIndexed; + } + else // Pre-indexed + { + address = regRnIndexed; + SetReg(cpu, rn, regRnIndexed); + } + + return address; + } + + // Addressing mode 4 + private static (UInt32 startAddress, UInt32 endAddress) GetAddress_Multiple(CPU_Core cpu, UInt32 instruction) + { + UInt32 p = (instruction >> 24) & 1; + UInt32 u = (instruction >> 23) & 1; + UInt32 w = (instruction >> 21) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 registerList = instruction & 0xffff; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 increment = (registerList == 0) ? 0x40 : ((UInt32)BitOperations.PopCount(registerList) * 4); + + UInt32 startAddress, endAddress; + UInt32 value; + + if (u == 1) // increment + { + value = regRn + increment; + + if (p == 0) // after + { + startAddress = regRn; + endAddress = value - 4; + } + else // before + { + startAddress = regRn + 4; + endAddress = value; + } + } + else // decrement + { + value = regRn - increment; + + if (p == 0) // after + { + startAddress = value + 4; + endAddress = regRn; + } + else // before + { + startAddress = value; + endAddress = regRn - 4; + } + } + + if (w == 1) + SetReg(cpu, rn, value); + + return (startAddress, endAddress); + } + + private static UInt64 UNKNOWN(CPU_Core cpu, UInt32 instruction) + { + throw new Exception(string.Format("Iris.CPU.ARM_Interpreter: Unknown ARM instruction 0x{0:x8} at address 0x{1:x8}", instruction, cpu.NextInstructionAddress - 4)); + } + + private static UInt64 ADC(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand + (UInt64)cpu.GetFlag(Flag.C); + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 ADD(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 AND(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + SetReg(cpu, rd, leftOperand & rightOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 B(CPU_Core cpu, UInt32 instruction) + { + UInt32 imm = instruction & 0xff_ffff; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + SetPC(cpu, regPC + (SignExtend(imm, 24) << 2)); + + return 3; + } + + private static UInt64 BL(CPU_Core cpu, UInt32 instruction) + { + UInt32 imm = instruction & 0xff_ffff; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, LR); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regLR = cpu.NextInstructionAddress; + SetPC(cpu, regPC + (SignExtend(imm, 24) << 2)); + + return 3; + } + + private static UInt64 BIC(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + SetReg(cpu, rd, leftOperand & ~rightOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 BX(CPU_Core cpu, UInt32 instruction) + { + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + cpu.CPSR = (cpu.CPSR & ~(1u << 5)) | ((regRm & 1) << 5); + SetPC(cpu, regRm & 0xffff_fffe); + + return 3; + } + + private static UInt64 CMN(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + if (rd == PC) + { + if ((cpu.CPSR & ModeMask) == UserMode) + cpu.CPSR = (cpu.CPSR & ~0xf000_0000) | (aluOut & 0xf000_0000); + else + cpu.SetCPSR((cpu.CPSR & ~0xf000_00c3) | (aluOut & 0xf000_0003) | (((aluOut >> 26) & 0b11) << 6)); + + return shiftRs ? 4u : 3u; + } + else + { + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, aluOut)); + + return shiftRs ? 2u : 1u; + } + } + + private static UInt64 CMP(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + if (rd == PC) + { + if ((cpu.CPSR & ModeMask) == UserMode) + cpu.CPSR = (cpu.CPSR & ~0xf000_0000) | (aluOut & 0xf000_0000); + else + cpu.SetCPSR((cpu.CPSR & ~0xf000_00c3) | (aluOut & 0xf000_0003) | (((aluOut >> 26) & 0b11) << 6)); + + return shiftRs ? 4u : 3u; + } + else + { + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, aluOut)); + + return shiftRs ? 2u : 1u; + } + } + + private static UInt64 EOR(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + SetReg(cpu, rd, leftOperand ^ rightOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 LDM1(CPU_Core cpu, UInt32 instruction) + { + UInt32 registerList = instruction & 0xffff; + + (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); + + UInt32 address = startAddress; + + if (registerList == 0) + { + SetPC(cpu, cpu._callbackInterface._read32(address)); + + return 5; + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + + for (int i = 0; i <= 14; ++i) + { + if (((registerList >> i) & 1) == 1) + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = cpu._callbackInterface._read32(address); + address += 4; + } + } + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + + if (((registerList >> 15) & 1) == 1) + { + SetPC(cpu, cpu._callbackInterface._read32(address) & 0xffff_fffc); + + return n + 4; + } + else + { + return n + 2; + } + } + } + + private static UInt64 LDM2(CPU_Core cpu, UInt32 instruction) + { + UInt32 registerList = instruction & 0x7fff; + + (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); + + UInt32 address = startAddress; + + if (registerList == 0) + { + SetPC(cpu, cpu._callbackInterface._read32(address)); + + return 5; + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + + for (int i = 0; i <= 14; ++i) + { + if (((registerList >> i) & 1) == 1) + { + UInt32 value = cpu._callbackInterface._read32(address); + + switch (i) + { + case 8: + cpu.Reg8_usr = value; + break; + case 9: + cpu.Reg9_usr = value; + break; + case 10: + cpu.Reg10_usr = value; + break; + case 11: + cpu.Reg11_usr = value; + break; + case 12: + cpu.Reg12_usr = value; + break; + case 13: + cpu.Reg13_usr = value; + break; + case 14: + cpu.Reg14_usr = value; + break; + default: + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = value; + break; + } + } + + address += 4; + } + } + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + return n + 2; + } + } + + private static UInt64 LDR(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + UInt32 address = GetAddress(cpu, instruction); + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read32(address), (int)(8 * (address & 0b11))); + + if (rd == PC) + { + SetPC(cpu, data & 0xffff_fffc); + + return 5; + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd = data; + + return 3; + } + } + + private static UInt64 LDRB(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + UInt32 address = GetAddress(cpu, instruction); + Byte data = cpu._callbackInterface._read8(address); + SetReg(cpu, rd, data); + + return (rd == PC) ? 5u : 3u; + } + + private static UInt64 LDRH(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + UInt32 address = GetAddress_Misc(cpu, instruction); + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read16(address), (int)(8 * (address & 1))); + SetReg(cpu, rd, data); + + return (rd == PC) ? 5u : 3u; + } + + private static UInt64 LDRSB(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + UInt32 address = GetAddress_Misc(cpu, instruction); + Byte data = cpu._callbackInterface._read8(address); + SetReg(cpu, rd, SignExtend(data, 8)); + + return (rd == PC) ? 5u : 3u; + } + + private static UInt64 LDRSH(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + UInt32 address = GetAddress_Misc(cpu, instruction); + + if ((address & 1) == 1) + { + Byte data = cpu._callbackInterface._read8(address); + SetReg(cpu, rd, SignExtend(data, 8)); + } + else + { + UInt16 data = cpu._callbackInterface._read16(address); + SetReg(cpu, rd, SignExtend(data, 16)); + } + + return (rd == PC) ? 5u : 3u; + } + + private static UInt64 MLA(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rd = (instruction >> 16) & 0b1111; + UInt32 rn = (instruction >> 12) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + SetReg(cpu, rd, (regRm * regRs) + regRn); + + if (s == 1) + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + } + + return m + 2; + } + + private static UInt64 MOV(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + SetReg(cpu, rd, shifterOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 MRS(CPU_Core cpu, UInt32 instruction) + { + UInt32 r = (instruction >> 22) & 1; + UInt32 rd = (instruction >> 12) & 0b1111; + + SetReg(cpu, rd, (r == 1) ? cpu.SPSR : cpu.CPSR); + + return 1; + } + + private static UInt64 MSR(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 r = (instruction >> 22) & 1; + UInt32 fieldMask = (instruction >> 16) & 0b1111; + + UInt32 operand; + + if (i == 1) + { + UInt32 rotateImm = (instruction >> 8) & 0b1111; + UInt32 imm = instruction & 0xff; + + operand = BitOperations.RotateRight(imm, (int)(2 * rotateImm)); + } + else + { + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + operand = regRm; + } + + UInt32 mask = (UInt32)((((fieldMask >> 0) & 1) == 1) ? 0x0000_00ff : 0) + | (UInt32)((((fieldMask >> 1) & 1) == 1) ? 0x0000_ff00 : 0) + | (UInt32)((((fieldMask >> 2) & 1) == 1) ? 0x00ff_0000 : 0) + | (UInt32)((((fieldMask >> 3) & 1) == 1) ? 0xff00_0000 : 0); + + if (r == 0) + { + if ((cpu.CPSR & ModeMask) == UserMode) + { + mask &= 0xff00_0000; + cpu.CPSR = (cpu.CPSR & ~mask) | (operand & mask); + } + else + { + cpu.SetCPSR((cpu.CPSR & ~mask) | (operand & mask)); + } + } + else + { + cpu.SPSR = (cpu.SPSR & ~mask) | (operand & mask); + } + + return 1; + } + + private static UInt64 MUL(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rd = (instruction >> 16) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + SetReg(cpu, rd, regRm * regRs); + + if (s == 1) + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + } + + return m + 1; + } + + private static UInt64 MVN(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + SetReg(cpu, rd, ~shifterOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 ORR(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + SetReg(cpu, rd, leftOperand | rightOperand); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 RSB(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = shifterOperand; + UInt32 rightOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 RSC(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = shifterOperand; + UInt32 rightOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand - (UInt64)Not(cpu.GetFlag(Flag.C)); + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 SBC(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand - (UInt64)Not(cpu.GetFlag(Flag.C)); + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 SMLAL(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rdHi = (instruction >> 16) & 0b1111; + UInt32 rdLo = (instruction >> 12) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, rdHi); + ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, rdLo); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + Int64 result = (Int64)(Int32)regRm * (Int64)(Int32)regRs; + UInt64 resultLo = (UInt64)(UInt32)result + (UInt64)regRdLo; + UInt32 resultHi = (UInt32)(result >> 32) + regRdHi + CarryFrom(resultLo); + SetReg(cpu, rdLo, (UInt32)resultLo); + SetReg(cpu, rdHi, resultHi); + + if (s == 1) + { + cpu.SetFlag(Flag.N, regRdHi >> 31); + cpu.SetFlag(Flag.Z, ((regRdHi == 0) && (regRdLo == 0)) ? 1u : 0u); + } + + return m + 3; + } + + private static UInt64 SMULL(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rdHi = (instruction >> 16) & 0b1111; + UInt32 rdLo = (instruction >> 12) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + Int64 result = (Int64)(Int32)regRm * (Int64)(Int32)regRs; + SetReg(cpu, rdLo, (UInt32)result); + SetReg(cpu, rdHi, (UInt32)(result >> 32)); + + if (s == 1) + { + ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, rdHi); + ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, rdLo); + + cpu.SetFlag(Flag.N, regRdHi >> 31); + cpu.SetFlag(Flag.Z, ((regRdHi == 0) && (regRdLo == 0)) ? 1u : 0u); + } + + return m + 2; + } + + private static UInt64 STM1(CPU_Core cpu, UInt32 instruction) + { + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 registerList = instruction & 0xffff; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 oldRegRn = regRn; + + (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); + + UInt32 address = startAddress; + + if (registerList == 0) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + cpu._callbackInterface._write32(address, regPC + 4); + + return 2; + } + else + { + for (int i = 0; i <= 14; ++i) + { + if (((registerList >> i) & 1) == 1) + { + if ((i == rn) && ((registerList & ~(0xffff << i)) == 0)) + { + cpu._callbackInterface._write32(address, oldRegRn); + } + else + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + cpu._callbackInterface._write32(address, regRi); + } + + address += 4; + } + } + + if (((registerList >> 15) & 1) == 1) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + cpu._callbackInterface._write32(address, regPC + 4); + } + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + return n + 1; + } + } + + private static UInt64 STM2(CPU_Core cpu, UInt32 instruction) + { + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 registerList = instruction & 0xffff; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + + UInt32 oldRegRn = rn switch + { + 8 => cpu.Reg8_usr, + 9 => cpu.Reg9_usr, + 10 => cpu.Reg10_usr, + 11 => cpu.Reg11_usr, + 12 => cpu.Reg12_usr, + 13 => cpu.Reg13_usr, + 14 => cpu.Reg14_usr, + _ => Unsafe.Add(ref regDataRef, rn), + }; + + (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); + + UInt32 address = startAddress; + + if (registerList == 0) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + cpu._callbackInterface._write32(address, regPC + 4); + + return 2; + } + else + { + for (int i = 0; i <= 14; ++i) + { + if (((registerList >> i) & 1) == 1) + { + if ((i == rn) && ((registerList & ~(0xffff << i)) == 0)) + { + cpu._callbackInterface._write32(address, oldRegRn); + } + else + { + UInt32 value = i switch + { + 8 => cpu.Reg8_usr, + 9 => cpu.Reg9_usr, + 10 => cpu.Reg10_usr, + 11 => cpu.Reg11_usr, + 12 => cpu.Reg12_usr, + 13 => cpu.Reg13_usr, + 14 => cpu.Reg14_usr, + _ => Unsafe.Add(ref regDataRef, i), + }; + + cpu._callbackInterface._write32(address, value); + } + + address += 4; + } + } + + if (((registerList >> 15) & 1) == 1) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + cpu._callbackInterface._write32(address, regPC + 4); + } + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + return n + 1; + } + } + + private static UInt64 STR(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + UInt32 data = (rd == PC) ? (regPC + 4) : regRd; + UInt32 address = GetAddress(cpu, instruction); + cpu._callbackInterface._write32(address, data); + + return 2; + } + + private static UInt64 STRB(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + UInt32 data = (rd == PC) ? (regPC + 4) : regRd; + UInt32 address = GetAddress(cpu, instruction); + cpu._callbackInterface._write8(address, (Byte)data); + + return 2; + } + + private static UInt64 STRH(CPU_Core cpu, UInt32 instruction) + { + UInt32 rd = (instruction >> 12) & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + UInt32 data = (rd == PC) ? (regPC + 4) : regRd; + UInt32 address = GetAddress_Misc(cpu, instruction); + cpu._callbackInterface._write16(address, (UInt16)data); + + return 2; + } + + private static UInt64 SUB(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 s = (instruction >> 20) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, _) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + SetReg(cpu, rd, (UInt32)result); + + if (s == 1) + { + if (rd == PC) + { + cpu.SetCPSR(cpu.SPSR); + + return shiftRs ? 4u : 3u; + } + else + { + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return shiftRs ? 2u : 1u; + } + } + else + { + return (shiftRs ? 2u : 1u) + ((rd == PC) ? 2u : 0u); + } + } + + private static UInt64 SWI(CPU_Core cpu, UInt32 instruction) + { + return cpu._callbackInterface._handleSWI(); + } + + private static UInt64 SWP(CPU_Core cpu, UInt32 instruction) + { + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt32 temp = BitOperations.RotateRight(cpu._callbackInterface._read32(regRn), (int)(8 * (regRn & 0b11))); + cpu._callbackInterface._write32(regRn, regRm); + SetReg(cpu, rd, temp); + + return 4; + } + + private static UInt64 SWPB(CPU_Core cpu, UInt32 instruction) + { + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + Byte temp = cpu._callbackInterface._read8(regRn); + cpu._callbackInterface._write8(regRn, (Byte)regRm); + SetReg(cpu, rd, temp); + + return 4; + } + + private static UInt64 TEQ(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt32 aluOut = leftOperand ^ rightOperand; + + if (rd == PC) + { + if ((cpu.CPSR & ModeMask) == UserMode) + cpu.CPSR = (cpu.CPSR & ~0xf000_0000) | (aluOut & 0xf000_0000); + else + cpu.SetCPSR((cpu.CPSR & ~0xf000_00c3) | (aluOut & 0xf000_0003) | (((aluOut >> 26) & 0b11) << 6)); + + return shiftRs ? 4u : 3u; + } + else + { + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + + private static UInt64 TST(CPU_Core cpu, UInt32 instruction) + { + UInt32 i = (instruction >> 25) & 1; + UInt32 rn = (instruction >> 16) & 0b1111; + UInt32 rd = (instruction >> 12) & 0b1111; + UInt32 r = (instruction >> 4) & 1; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + (UInt32 shifterOperand, UInt32 shifterCarryOut) = GetShifterOperand(cpu, instruction); + + bool shiftRs = (i == 0) && (r == 1); + + UInt32 leftOperand = ((rn == PC) && shiftRs) ? (regPC + 4) : regRn; + UInt32 rightOperand = shifterOperand; + + UInt32 aluOut = leftOperand & rightOperand; + + if (rd == PC) + { + if ((cpu.CPSR & ModeMask) == UserMode) + cpu.CPSR = (cpu.CPSR & ~0xf000_0000) | (aluOut & 0xf000_0000); + else + cpu.SetCPSR((cpu.CPSR & ~0xf000_00c3) | (aluOut & 0xf000_0003) | (((aluOut >> 26) & 0b11) << 6)); + + return shiftRs ? 4u : 3u; + } + else + { + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, shifterCarryOut); + + return shiftRs ? 2u : 1u; + } + } + + private static UInt64 UMLAL(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rdHi = (instruction >> 16) & 0b1111; + UInt32 rdLo = (instruction >> 12) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, rdHi); + ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, rdLo); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + UInt64 result = (UInt64)regRm * (UInt64)regRs; + UInt64 resultLo = (UInt64)(UInt32)result + (UInt64)regRdLo; + UInt32 resultHi = (UInt32)(result >> 32) + regRdHi + CarryFrom(resultLo); + SetReg(cpu, rdLo, (UInt32)resultLo); + SetReg(cpu, rdHi, resultHi); + + if (s == 1) + { + cpu.SetFlag(Flag.N, regRdHi >> 31); + cpu.SetFlag(Flag.Z, ((regRdHi == 0) && (regRdLo == 0)) ? 1u : 0u); + } + + return m + 3; + } + + private static UInt64 UMULL(CPU_Core cpu, UInt32 instruction) + { + UInt32 s = (instruction >> 20) & 1; + UInt32 rdHi = (instruction >> 16) & 0b1111; + UInt32 rdLo = (instruction >> 12) & 0b1111; + UInt32 rs = (instruction >> 8) & 0b1111; + UInt32 rm = instruction & 0b1111; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); + UInt64 result = (UInt64)regRm * (UInt64)regRs; + SetReg(cpu, rdLo, (UInt32)result); + SetReg(cpu, rdHi, (UInt32)(result >> 32)); + + if (s == 1) + { + ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, rdHi); + ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, rdLo); + + cpu.SetFlag(Flag.N, regRdHi >> 31); + cpu.SetFlag(Flag.Z, ((regRdHi == 0) && (regRdLo == 0)) ? 1u : 0u); + } + + return m + 2; + } + } +} diff --git a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta b/Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta new file mode 100644 index 0000000..6e3e21b --- /dev/null +++ b/Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4c4deba1667702f49b08a4ccab53688e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.CPU/CPU_Core.cs b/Assets/Iris/Iris.CPU/CPU_Core.cs new file mode 100644 index 0000000..14336aa --- /dev/null +++ b/Assets/Iris/Iris.CPU/CPU_Core.cs @@ -0,0 +1,575 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Iris.CPU +{ + public sealed class CPU_Core + { + public enum Model + { + ARM7TDMI, + ARM946ES + } + + public delegate Byte Read8_Delegate(UInt32 address); + public delegate UInt16 Read16_Delegate(UInt32 address); + public delegate UInt32 Read32_Delegate(UInt32 address); + public delegate void Write8_Delegate(UInt32 address, Byte value); + public delegate void Write16_Delegate(UInt32 address, UInt16 value); + public delegate void Write32_Delegate(UInt32 address, UInt32 value); + public delegate UInt64 HandleSWI_Delegate(); + public delegate UInt64 HandleIRQ_Delegate(); + + // could have used function pointers (delegate*) for performance instead of delegates but it's less flexible (cannot use non-static function for instance) + //public readonly struct CallbackInterface + //( + // Read8_Delegate read8, + // Read16_Delegate read16, + // Read32_Delegate read32, + // Write8_Delegate write8, + // Write16_Delegate write16, + // Write32_Delegate write32, + // HandleSWI_Delegate handleSWI, + // HandleIRQ_Delegate handleIRQ + //) + //{ + // internal readonly Read8_Delegate _read8 = read8; + // internal readonly Read16_Delegate _read16 = read16; + // internal readonly Read32_Delegate _read32 = read32; + // internal readonly Write8_Delegate _write8 = write8; + // internal readonly Write16_Delegate _write16 = write16; + // internal readonly Write32_Delegate _write32 = write32; + // internal readonly HandleSWI_Delegate _handleSWI = handleSWI; + // internal readonly HandleIRQ_Delegate _handleIRQ = handleIRQ; + //} + + public readonly struct CallbackInterface + { + public CallbackInterface + ( + Read8_Delegate read8, + Read16_Delegate read16, + Read32_Delegate read32, + Write8_Delegate write8, + Write16_Delegate write16, + Write32_Delegate write32, + HandleSWI_Delegate handleSWI, + HandleIRQ_Delegate handleIRQ + ) + { + _read8 = read8; + _read16 = read16; + _read32 = read32; + _write8 = write8; + _write16 = write16; + _write32 = write32; + _handleSWI = handleSWI; + _handleIRQ = handleIRQ; + } + + internal readonly Read8_Delegate _read8; + internal readonly Read16_Delegate _read16; + internal readonly Read32_Delegate _read32; + internal readonly Write8_Delegate _write8; + internal readonly Write16_Delegate _write16; + internal readonly Write32_Delegate _write32; + internal readonly HandleSWI_Delegate _handleSWI; + internal readonly HandleIRQ_Delegate _handleIRQ; + } + + public enum Signal + { + High, + Low + } + + internal enum Flag + { + V = 28, + C = 29, + Z = 30, + N = 31 + } + + internal unsafe readonly struct InstructionListEntry(T mask, T expected, delegate* handler, List modelList) + { + internal readonly T _mask = mask; + internal readonly T _expected = expected; + internal unsafe readonly delegate* _handler = handler; + internal readonly List _modelList = modelList; + } + + internal unsafe readonly struct InstructionLUTEntry(delegate* handler) + { + internal unsafe readonly delegate* _handler = handler; + } + + internal const UInt32 ModeMask = 0b1_1111; + internal const UInt32 UserMode = 0b1_0000; + internal const UInt32 SystemMode = 0b1_1111; + internal const UInt32 SupervisorMode = 0b1_0011; + internal const UInt32 AbortMode = 0b1_0111; + internal const UInt32 UndefinedMode = 0b1_1011; + internal const UInt32 InterruptMode = 0b1_0010; + internal const UInt32 FastInterruptMode = 0b1_0001; + + public const UInt32 SP = 13; + public const UInt32 LR = 14; + public const UInt32 PC = 15; + + public readonly UInt32[] Reg = new UInt32[16]; + public UInt32 CPSR; + public UInt32 SPSR; + + public UInt32 Reg8_usr, Reg9_usr, Reg10_usr, Reg11_usr, Reg12_usr, Reg13_usr, Reg14_usr; + public UInt32 Reg13_svc, Reg14_svc; + public UInt32 Reg13_abt, Reg14_abt; + public UInt32 Reg13_und, Reg14_und; + public UInt32 Reg13_irq, Reg14_irq; + public UInt32 Reg8_fiq, Reg9_fiq, Reg10_fiq, Reg11_fiq, Reg12_fiq, Reg13_fiq, Reg14_fiq; + public UInt32 SPSR_svc, SPSR_abt, SPSR_und, SPSR_irq, SPSR_fiq; + + internal readonly Model _model; + internal readonly CallbackInterface _callbackInterface; + + private readonly ARM_Interpreter _armInterpreter; + private readonly THUMB_Interpreter _thumbInterpreter; + + public UInt32 NextInstructionAddress; + public Signal NIRQ; + + public CPU_Core(Model model, CallbackInterface callbackInterface) + { + _model = model; + _callbackInterface = callbackInterface; + _armInterpreter = new(this); + _thumbInterpreter = new(this); + } + + public void ResetState() + { + Array.Clear(Reg,0,Reg.Length); + + CPSR = 0b1_0000; + SPSR = 0; + + Reg8_usr = 0; + Reg9_usr = 0; + Reg10_usr = 0; + Reg11_usr = 0; + Reg12_usr = 0; + Reg13_usr = 0; + Reg14_usr = 0; + + Reg13_svc = 0; + Reg14_svc = 0; + + Reg13_abt = 0; + Reg14_abt = 0; + + Reg13_und = 0; + Reg14_und = 0; + + Reg13_irq = 0; + Reg14_irq = 0; + + Reg8_fiq = 0; + Reg9_fiq = 0; + Reg10_fiq = 0; + Reg11_fiq = 0; + Reg12_fiq = 0; + Reg13_fiq = 0; + Reg14_fiq = 0; + + SPSR_svc = 0; + SPSR_abt = 0; + SPSR_und = 0; + SPSR_irq = 0; + SPSR_fiq = 0; + + NextInstructionAddress = 0; + NIRQ = Signal.High; + } + + public void LoadState(BinaryReader reader) + { + foreach (ref UInt32 reg in Reg.AsSpan()) + reg = reader.ReadUInt32(); + + CPSR = reader.ReadUInt32(); + SPSR = reader.ReadUInt32(); + + Reg8_usr = reader.ReadUInt32(); + Reg9_usr = reader.ReadUInt32(); + Reg10_usr = reader.ReadUInt32(); + Reg11_usr = reader.ReadUInt32(); + Reg12_usr = reader.ReadUInt32(); + Reg13_usr = reader.ReadUInt32(); + Reg14_usr = reader.ReadUInt32(); + + Reg13_svc = reader.ReadUInt32(); + Reg14_svc = reader.ReadUInt32(); + + Reg13_abt = reader.ReadUInt32(); + Reg14_abt = reader.ReadUInt32(); + + Reg13_und = reader.ReadUInt32(); + Reg14_und = reader.ReadUInt32(); + + Reg13_irq = reader.ReadUInt32(); + Reg14_irq = reader.ReadUInt32(); + + Reg8_fiq = reader.ReadUInt32(); + Reg9_fiq = reader.ReadUInt32(); + Reg10_fiq = reader.ReadUInt32(); + Reg11_fiq = reader.ReadUInt32(); + Reg12_fiq = reader.ReadUInt32(); + Reg13_fiq = reader.ReadUInt32(); + Reg14_fiq = reader.ReadUInt32(); + + SPSR_svc = reader.ReadUInt32(); + SPSR_abt = reader.ReadUInt32(); + SPSR_und = reader.ReadUInt32(); + SPSR_irq = reader.ReadUInt32(); + SPSR_fiq = reader.ReadUInt32(); + + NextInstructionAddress = reader.ReadUInt32(); + NIRQ = (Signal)reader.ReadInt32(); + } + + public void SaveState(BinaryWriter writer) + { + foreach (UInt32 reg in Reg) + writer.Write(reg); + + writer.Write(CPSR); + writer.Write(SPSR); + + writer.Write(Reg8_usr); + writer.Write(Reg9_usr); + writer.Write(Reg10_usr); + writer.Write(Reg11_usr); + writer.Write(Reg12_usr); + writer.Write(Reg13_usr); + writer.Write(Reg14_usr); + + writer.Write(Reg13_svc); + writer.Write(Reg14_svc); + + writer.Write(Reg13_abt); + writer.Write(Reg14_abt); + + writer.Write(Reg13_und); + writer.Write(Reg14_und); + + writer.Write(Reg13_irq); + writer.Write(Reg14_irq); + + writer.Write(Reg8_fiq); + writer.Write(Reg9_fiq); + writer.Write(Reg10_fiq); + writer.Write(Reg11_fiq); + writer.Write(Reg12_fiq); + writer.Write(Reg13_fiq); + writer.Write(Reg14_fiq); + + writer.Write(SPSR_svc); + writer.Write(SPSR_abt); + writer.Write(SPSR_und); + writer.Write(SPSR_irq); + writer.Write(SPSR_fiq); + + writer.Write(NextInstructionAddress); + writer.Write((int)NIRQ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UInt64 Step() + { + UInt32 i = (CPSR >> 7) & 1; + + if ((i == 0) && (NIRQ == Signal.Low)) + return _callbackInterface._handleIRQ(); + + UInt32 t = (CPSR >> 5) & 1; + + if (t == 0) + return _armInterpreter.Step(); + else + return _thumbInterpreter.Step(); + } + + public void SetCPSR(UInt32 value) + { + UInt32 previousMode = CPSR & ModeMask; + UInt32 newMode = value & ModeMask; + + CPSR = value | 0b1_0000; + + if (previousMode != newMode) + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(Reg); + ref UInt32 reg8 = ref Unsafe.Add(ref regDataRef, 8); + ref UInt32 reg9 = ref Unsafe.Add(ref regDataRef, 9); + ref UInt32 reg10 = ref Unsafe.Add(ref regDataRef, 10); + ref UInt32 reg11 = ref Unsafe.Add(ref regDataRef, 11); + ref UInt32 reg12 = ref Unsafe.Add(ref regDataRef, 12); + ref UInt32 reg13 = ref Unsafe.Add(ref regDataRef, 13); + ref UInt32 reg14 = ref Unsafe.Add(ref regDataRef, 14); + + // save previous mode registers + switch (previousMode) + { + case UserMode: + case SystemMode: + Reg8_usr = reg8; + Reg9_usr = reg9; + Reg10_usr = reg10; + Reg11_usr = reg11; + Reg12_usr = reg12; + Reg13_usr = reg13; + Reg14_usr = reg14; + break; + case SupervisorMode: + Reg8_usr = reg8; + Reg9_usr = reg9; + Reg10_usr = reg10; + Reg11_usr = reg11; + Reg12_usr = reg12; + Reg13_svc = reg13; + Reg14_svc = reg14; + SPSR_svc = SPSR; + break; + case AbortMode: + Reg8_usr = reg8; + Reg9_usr = reg9; + Reg10_usr = reg10; + Reg11_usr = reg11; + Reg12_usr = reg12; + Reg13_abt = reg13; + Reg14_abt = reg14; + SPSR_abt = SPSR; + break; + case UndefinedMode: + Reg8_usr = reg8; + Reg9_usr = reg9; + Reg10_usr = reg10; + Reg11_usr = reg11; + Reg12_usr = reg12; + Reg13_und = reg13; + Reg14_und = reg14; + SPSR_und = SPSR; + break; + case InterruptMode: + Reg8_usr = reg8; + Reg9_usr = reg9; + Reg10_usr = reg10; + Reg11_usr = reg11; + Reg12_usr = reg12; + Reg13_irq = reg13; + Reg14_irq = reg14; + SPSR_irq = SPSR; + break; + case FastInterruptMode: + Reg8_fiq = reg8; + Reg9_fiq = reg9; + Reg10_fiq = reg10; + Reg11_fiq = reg11; + Reg12_fiq = reg12; + Reg13_fiq = reg13; + Reg14_fiq = reg14; + SPSR_fiq = SPSR; + break; + } + + // load new mode registers + switch (newMode) + { + case UserMode: + case SystemMode: + reg8 = Reg8_usr; + reg9 = Reg9_usr; + reg10 = Reg10_usr; + reg11 = Reg11_usr; + reg12 = Reg12_usr; + reg13 = Reg13_usr; + reg14 = Reg14_usr; + break; + case SupervisorMode: + reg8 = Reg8_usr; + reg9 = Reg9_usr; + reg10 = Reg10_usr; + reg11 = Reg11_usr; + reg12 = Reg12_usr; + reg13 = Reg13_svc; + reg14 = Reg14_svc; + SPSR = SPSR_svc; + break; + case AbortMode: + reg8 = Reg8_usr; + reg9 = Reg9_usr; + reg10 = Reg10_usr; + reg11 = Reg11_usr; + reg12 = Reg12_usr; + reg13 = Reg13_abt; + reg14 = Reg14_abt; + SPSR = SPSR_abt; + break; + case UndefinedMode: + reg8 = Reg8_usr; + reg9 = Reg9_usr; + reg10 = Reg10_usr; + reg11 = Reg11_usr; + reg12 = Reg12_usr; + reg13 = Reg13_und; + reg14 = Reg14_und; + SPSR = SPSR_und; + break; + case InterruptMode: + reg8 = Reg8_usr; + reg9 = Reg9_usr; + reg10 = Reg10_usr; + reg11 = Reg11_usr; + reg12 = Reg12_usr; + reg13 = Reg13_irq; + reg14 = Reg14_irq; + SPSR = SPSR_irq; + break; + case FastInterruptMode: + reg8 = Reg8_fiq; + reg9 = Reg9_fiq; + reg10 = Reg10_fiq; + reg11 = Reg11_fiq; + reg12 = Reg12_fiq; + reg13 = Reg13_fiq; + reg14 = Reg14_fiq; + SPSR = SPSR_fiq; + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal UInt32 GetFlag(Flag flag) + { + return (CPSR >> (int)flag) & 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetFlag(Flag flag, UInt32 value) + { + CPSR = (CPSR & ~(1u << (int)flag)) | (value << (int)flag); + } + + internal bool ConditionPassed(UInt32 cond) + { + return cond switch + { + // EQ + 0b0000 => GetFlag(Flag.Z) == 1, + // NE + 0b0001 => GetFlag(Flag.Z) == 0, + // CS/HS + 0b0010 => GetFlag(Flag.C) == 1, + // CC/LO + 0b0011 => GetFlag(Flag.C) == 0, + // MI + 0b0100 => GetFlag(Flag.N) == 1, + // PL + 0b0101 => GetFlag(Flag.N) == 0, + // VS + 0b0110 => GetFlag(Flag.V) == 1, + // VC + 0b0111 => GetFlag(Flag.V) == 0, + // HI + 0b1000 => (GetFlag(Flag.C) == 1) && (GetFlag(Flag.Z) == 0), + // LS + 0b1001 => (GetFlag(Flag.C) == 0) || (GetFlag(Flag.Z) == 1), + // GE + 0b1010 => GetFlag(Flag.N) == GetFlag(Flag.V), + // LT + 0b1011 => GetFlag(Flag.N) != GetFlag(Flag.V), + // GT + 0b1100 => (GetFlag(Flag.Z) == 0) && (GetFlag(Flag.N) == GetFlag(Flag.V)), + // LE + 0b1101 => (GetFlag(Flag.Z) == 1) || (GetFlag(Flag.N) != GetFlag(Flag.V)), + // AL + 0b1110 => true, + // NV + 0b1111 => false, + // should never happen + _ => throw new Exception($"Iris.CPU.CPU_Core: Wrong condition code {cond}"), + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 Not(UInt32 flag) + { + return flag ^ 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 CarryFrom(UInt64 result) + { + return (result > 0xffff_ffff) ? 1u : 0u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 BorrowFrom(UInt64 result) + { + return (result >= 0x8000_0000_0000_0000) ? 1u : 0u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 OverflowFrom_Addition(UInt32 leftOperand, UInt32 rightOperand, UInt32 result) + { + return (((leftOperand >> 31) == (rightOperand >> 31)) + && ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 OverflowFrom_Subtraction(UInt32 leftOperand, UInt32 rightOperand, UInt32 result) + { + return (((leftOperand >> 31) != (rightOperand >> 31)) + && ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 ArithmeticShiftRight(UInt32 value, int shiftAmount) + { + return (UInt32)((Int32)value >> shiftAmount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt32 SignExtend(UInt32 value, int size) + { + return value | ~((value & (1u << (size - 1))) - 1); + } + + internal static UInt64 ComputeMultiplicationCycleCount(UInt32 leftMultiplier, UInt32 rightMultiplier) + { + static UInt64 ComputeMultiplierCycleCount(UInt32 multiplier) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool CheckMultiplierAgainstMask(UInt32 multiplier, UInt32 mask) + { + UInt32 masked = multiplier & mask; + return (masked == 0) || (masked == mask); + } + + if (CheckMultiplierAgainstMask(multiplier, 0xffff_ff00)) + return 1; + else if (CheckMultiplierAgainstMask(multiplier, 0xffff_0000)) + return 2; + else if (CheckMultiplierAgainstMask(multiplier, 0xff00_0000)) + return 3; + else + return 4; + } + + return Math.Max(ComputeMultiplierCycleCount(leftMultiplier), ComputeMultiplierCycleCount(rightMultiplier)); + } + } +} diff --git a/Assets/Iris/Iris.CPU/CPU_Core.cs.meta b/Assets/Iris/Iris.CPU/CPU_Core.cs.meta new file mode 100644 index 0000000..6630d24 --- /dev/null +++ b/Assets/Iris/Iris.CPU/CPU_Core.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ad832993e3b0bc4695925ea366d4d9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs b/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs new file mode 100644 index 0000000..28f6fad --- /dev/null +++ b/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs @@ -0,0 +1,1524 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Iris.CPU.CPU_Core; + +namespace Iris.CPU +{ + internal sealed class THUMB_Interpreter + { + private readonly CPU_Core _cpu; + private readonly InstructionLUTEntry[] _instructionLUT = new InstructionLUTEntry[1 << 10]; + + internal THUMB_Interpreter(CPU_Core cpu) + { + _cpu = cpu; + + unsafe + { + InstructionListEntry[] InstructionList = + [ + // ADC + new(0xffc0, 0x4140, &ADC, [Model.ARM7TDMI]), + + // ADD + new(0xfe00, 0x1c00, &ADD1, [Model.ARM7TDMI]), + new(0xf800, 0x3000, &ADD2, [Model.ARM7TDMI]), + new(0xfe00, 0x1800, &ADD3, [Model.ARM7TDMI]), + new(0xff00, 0x4400, &ADD4, [Model.ARM7TDMI]), + new(0xf800, 0xa000, &ADD5, [Model.ARM7TDMI]), + new(0xf800, 0xa800, &ADD6, [Model.ARM7TDMI]), + new(0xff80, 0xb000, &ADD7, [Model.ARM7TDMI]), + + // AND + new(0xffc0, 0x4000, &AND, [Model.ARM7TDMI]), + + // ASR + new(0xf800, 0x1000, &ASR1, [Model.ARM7TDMI]), + new(0xffc0, 0x4100, &ASR2, [Model.ARM7TDMI]), + + // B + new(0xff00, 0xd000, &B1, [Model.ARM7TDMI]), // condition field 0b0000 + new(0xff00, 0xd100, &B1, [Model.ARM7TDMI]), // condition field 0b0001 + new(0xff00, 0xd200, &B1, [Model.ARM7TDMI]), // condition field 0b0010 + new(0xff00, 0xd300, &B1, [Model.ARM7TDMI]), // condition field 0b0011 + new(0xff00, 0xd400, &B1, [Model.ARM7TDMI]), // condition field 0b0100 + new(0xff00, 0xd500, &B1, [Model.ARM7TDMI]), // condition field 0b0101 + new(0xff00, 0xd600, &B1, [Model.ARM7TDMI]), // condition field 0b0110 + new(0xff00, 0xd700, &B1, [Model.ARM7TDMI]), // condition field 0b0111 + new(0xff00, 0xd800, &B1, [Model.ARM7TDMI]), // condition field 0b1000 + new(0xff00, 0xd900, &B1, [Model.ARM7TDMI]), // condition field 0b1001 + new(0xff00, 0xda00, &B1, [Model.ARM7TDMI]), // condition field 0b1010 + new(0xff00, 0xdb00, &B1, [Model.ARM7TDMI]), // condition field 0b1011 + new(0xff00, 0xdc00, &B1, [Model.ARM7TDMI]), // condition field 0b1100 + new(0xff00, 0xdd00, &B1, [Model.ARM7TDMI]), // condition field 0b1101 + new(0xf800, 0xe000, &B2, [Model.ARM7TDMI]), + + // BIC + new(0xffc0, 0x4380, &BIC, [Model.ARM7TDMI]), + + // BL + new(0xf000, 0xf000, &BL, [Model.ARM7TDMI]), + + // BX + new(0xff80, 0x4700, &BX, [Model.ARM7TDMI]), + + // CMN + new(0xffc0, 0x42c0, &CMN, [Model.ARM7TDMI]), + + // CMP + new(0xf800, 0x2800, &CMP1, [Model.ARM7TDMI]), + new(0xffc0, 0x4280, &CMP2, [Model.ARM7TDMI]), + new(0xff00, 0x4500, &CMP3, [Model.ARM7TDMI]), + + // EOR + new(0xffc0, 0x4040, &EOR, [Model.ARM7TDMI]), + + // LDMIA + new(0xf800, 0xc800, &LDMIA, [Model.ARM7TDMI]), + + // LDR + new(0xf800, 0x6800, &LDR1, [Model.ARM7TDMI]), + new(0xfe00, 0x5800, &LDR2, [Model.ARM7TDMI]), + new(0xf800, 0x4800, &LDR3, [Model.ARM7TDMI]), + new(0xf800, 0x9800, &LDR4, [Model.ARM7TDMI]), + + // LDRB + new(0xf800, 0x7800, &LDRB1, [Model.ARM7TDMI]), + new(0xfe00, 0x5c00, &LDRB2, [Model.ARM7TDMI]), + + // LDRH + new(0xf800, 0x8800, &LDRH1, [Model.ARM7TDMI]), + new(0xfe00, 0x5a00, &LDRH2, [Model.ARM7TDMI]), + + // LDRSB + new(0xfe00, 0x5600, &LDRSB, [Model.ARM7TDMI]), + + // LDRSH + new(0xfe00, 0x5e00, &LDRSH, [Model.ARM7TDMI]), + + // LSL + new(0xf800, 0x0000, &LSL1, [Model.ARM7TDMI]), + new(0xffc0, 0x4080, &LSL2, [Model.ARM7TDMI]), + + // LSR + new(0xf800, 0x0800, &LSR1, [Model.ARM7TDMI]), + new(0xffc0, 0x40c0, &LSR2, [Model.ARM7TDMI]), + + // MOV + new(0xf800, 0x2000, &MOV1, [Model.ARM7TDMI]), + //new(0xffc0, 0x1c00, &MOV2, new List{ Model.ARM7TDMI }), + new(0xff00, 0x4600, &MOV3, [Model.ARM7TDMI]), + + // MUL + new(0xffc0, 0x4340, &MUL, [Model.ARM7TDMI]), + + // MVN + new(0xffc0, 0x43c0, &MVN, [Model.ARM7TDMI]), + + // NEG + new(0xffc0, 0x4240, &NEG, [Model.ARM7TDMI]), + + // ORR + new(0xffc0, 0x4300, &ORR, [Model.ARM7TDMI]), + + // POP + new(0xfe00, 0xbc00, &POP, [Model.ARM7TDMI]), + + // PUSH + new(0xfe00, 0xb400, &PUSH, [Model.ARM7TDMI]), + + // ROR + new(0xffc0, 0x41c0, &ROR, [Model.ARM7TDMI]), + + // SBC + new(0xffc0, 0x4180, &SBC, [Model.ARM7TDMI]), + + // STMIA + new(0xf800, 0xc000, &STMIA, [Model.ARM7TDMI]), + + // STR + new(0xf800, 0x6000, &STR1, [Model.ARM7TDMI]), + new(0xfe00, 0x5000, &STR2, [Model.ARM7TDMI]), + new(0xf800, 0x9000, &STR3, [Model.ARM7TDMI]), + + // STRB + new(0xf800, 0x7000, &STRB1, [Model.ARM7TDMI]), + new(0xfe00, 0x5400, &STRB2, [Model.ARM7TDMI]), + + // STRH + new(0xf800, 0x8000, &STRH1, [Model.ARM7TDMI]), + new(0xfe00, 0x5200, &STRH2, [Model.ARM7TDMI]), + + // SUB + new(0xfe00, 0x1e00, &SUB1, [Model.ARM7TDMI]), + new(0xf800, 0x3800, &SUB2, [Model.ARM7TDMI]), + new(0xfe00, 0x1a00, &SUB3, [Model.ARM7TDMI]), + new(0xff80, 0xb080, &SUB4, [Model.ARM7TDMI]), + + // SWI + new(0xff00, 0xdf00, &SWI, [Model.ARM7TDMI]), + + // TST + new(0xffc0, 0x4200, &TST, [Model.ARM7TDMI]), + ]; + + for (UInt16 instruction = 0; instruction < _instructionLUT.Length; ++instruction) + { + bool unknownInstruction = true; + + foreach (InstructionListEntry entry in InstructionList) + { + if (((instruction & InstructionLUTHash(entry._mask)) == InstructionLUTHash(entry._expected)) && (entry._modelList.Contains(_cpu._model))) + { + _instructionLUT[instruction] = new(entry._handler); + unknownInstruction = false; + break; + } + } + + if (unknownInstruction) + _instructionLUT[instruction] = new(&UNKNOWN); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt16 InstructionLUTHash(UInt16 value) + { + return (UInt16)(value >> 6); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal UInt64 Step() + { + UInt16 instruction = _cpu._callbackInterface._read16(_cpu.NextInstructionAddress); + _cpu.NextInstructionAddress += 2; + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(_cpu.Reg); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regPC = _cpu.NextInstructionAddress + 2; + + ref InstructionLUTEntry instructionLUTDataRef = ref MemoryMarshal.GetArrayDataReference(_instructionLUT); + ref InstructionLUTEntry instructionLUTEntry = ref Unsafe.Add(ref instructionLUTDataRef, InstructionLUTHash(instruction)); + + unsafe + { + return instructionLUTEntry._handler(_cpu, instruction); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetPC(CPU_Core cpu, UInt32 value) + { + cpu.NextInstructionAddress = value & 0xffff_fffe; + } + + private static void SetReg(CPU_Core cpu, UInt32 i, UInt32 value) + { + if (i == PC) + { + SetPC(cpu, value); + } + else + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = value; + } + } + + private static UInt64 UNKNOWN(CPU_Core cpu, UInt16 instruction) + { + throw new Exception(string.Format("Iris.CPU.THUMB_Interpreter: Unknown THUMB instruction 0x{0:x4} at address 0x{1:x8}", instruction, cpu.NextInstructionAddress - 2)); + } + + private static UInt64 ADC(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRd; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand + (UInt64)cpu.GetFlag(Flag.C); + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 ADD1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = imm; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 ADD2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRd; + UInt32 rightOperand = imm; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 ADD3(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 ADD4(CPU_Core cpu, UInt16 instruction) + { + UInt16 h1 = (UInt16)((instruction >> 7) & 1); + UInt16 h2 = (UInt16)((instruction >> 6) & 1); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + rd |= (UInt16)(h1 << 3); + rm |= (UInt16)(h2 << 3); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + SetReg(cpu, rd, regRd + regRm); + + return (rd == PC) ? 3u : 1u; + } + + private static UInt64 ADD5(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regRd = (regPC & 0xffff_fffc) + (imm * 4u); + + return 1; + } + + private static UInt64 ADD6(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + regRd = regSP + (imm * 4u); + + return 1; + } + + private static UInt64 ADD7(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)(instruction & 0x7f); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + regSP += imm * 4u; + + return 1; + } + + private static UInt64 AND(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd &= regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 ASR1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = imm; + + if (shiftAmount == 0) + { + cpu.SetFlag(Flag.C, regRm >> 31); + regRd = ((regRm >> 31) == 0) ? 0 : 0xffff_ffff; + } + else + { + cpu.SetFlag(Flag.C, (regRm >> (shiftAmount - 1)) & 1); + regRd = ArithmeticShiftRight(regRm, shiftAmount); + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 ASR2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rs = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = (int)(regRs & 0xff); + + if (shiftAmount == 0) + { + // nothing to do + } + else if (shiftAmount < 32) + { + cpu.SetFlag(Flag.C, (regRd >> (shiftAmount - 1)) & 1); + regRd = ArithmeticShiftRight(regRd, shiftAmount); + } + else + { + cpu.SetFlag(Flag.C, regRd >> 31); + regRd = ((regRd >> 31) == 0) ? 0 : 0xffff_ffff; + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 2; + } + + private static UInt64 B1(CPU_Core cpu, UInt16 instruction) + { + UInt16 cond = (UInt16)((instruction >> 8) & 0b1111); + UInt16 imm = (UInt16)(instruction & 0xff); + + if (cpu.ConditionPassed(cond)) + { + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + SetPC(cpu, regPC + (SignExtend(imm, 8) << 1)); + + return 3; + } + else + { + return 1; + } + } + + private static UInt64 B2(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)(instruction & 0x7ff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + SetPC(cpu, regPC + (SignExtend(imm, 11) << 1)); + + return 3; + } + + private static UInt64 BIC(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd &= ~regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 BL(CPU_Core cpu, UInt16 instruction) + { + UInt16 h = (UInt16)((instruction >> 11) & 0b11); + UInt16 offset = (UInt16)(instruction & 0x7ff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, LR); + + if (h == 0b10) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regLR = regPC + (SignExtend(offset, 11) << 12); + } + else if (h == 0b11) + { + // save NextInstructionAddress because it's invalidated by SetPC + UInt32 nextInstructionAddress = cpu.NextInstructionAddress; + + SetPC(cpu, regLR + (UInt32)(offset << 1)); + regLR = nextInstructionAddress | 1; + } + + return 4; + } + + private static UInt64 BX(CPU_Core cpu, UInt16 instruction) + { + UInt16 h2 = (UInt16)((instruction >> 6) & 1); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + + rm |= (UInt16)(h2 << 3); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + cpu.CPSR = (cpu.CPSR & ~(1u << 5)) | ((regRm & 1) << 5); + SetPC(cpu, regRm); + + return 3; + } + + private static UInt64 CMN(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rn = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand + (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, CarryFrom(result)); + cpu.SetFlag(Flag.V, OverflowFrom_Addition(leftOperand, rightOperand, aluOut)); + + return 1; + } + + private static UInt64 CMP1(CPU_Core cpu, UInt16 instruction) + { + UInt16 rn = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = imm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, aluOut)); + + return 1; + } + + private static UInt64 CMP2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rn = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, aluOut)); + + return 1; + } + + private static UInt64 CMP3(CPU_Core cpu, UInt16 instruction) + { + UInt16 h1 = (UInt16)((instruction >> 7) & 1); + UInt16 h2 = (UInt16)((instruction >> 6) & 1); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rn = (UInt16)(instruction & 0b111); + + rn |= (UInt16)(h1 << 3); + rm |= (UInt16)(h2 << 3); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + UInt32 aluOut = (UInt32)result; + + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, aluOut)); + + return 1; + } + + private static UInt64 EOR(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd ^= regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 LDMIA(CPU_Core cpu, UInt16 instruction) + { + UInt16 rn = (UInt16)((instruction >> 8) & 0b111); + UInt16 registerList = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 address = regRn; + + if (registerList == 0) + { + regRn += 0x40; + SetPC(cpu, cpu._callbackInterface._read32(address)); + + return 5; + } + else + { + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + regRn += n * 4; + + for (int i = 0; i <= 7; ++i) + { + if (((registerList >> i) & 1) == 1) + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = cpu._callbackInterface._read32(address); + address += 4; + } + } + + return n + 2; + } + } + + private static UInt64 LDR1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + (imm * 4u); + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read32(address), (int)(8 * (address & 0b11))); + regRd = data; + + return 3; + } + + private static UInt64 LDR2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read32(address), (int)(8 * (address & 0b11))); + regRd = data; + + return 3; + } + + private static UInt64 LDR3(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + UInt32 address = regPC + (imm * 4u); + UInt32 data = cpu._callbackInterface._read32(address); + regRd = data; + + return 3; + } + + private static UInt64 LDR4(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + UInt32 address = regSP + (imm * 4u); + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read32(address), (int)(8 * (address & 0b11))); + regRd = data; + + return 3; + } + + private static UInt64 LDRB1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + imm; + Byte data = cpu._callbackInterface._read8(address); + regRd = data; + + return 3; + } + + private static UInt64 LDRB2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + Byte data = cpu._callbackInterface._read8(address); + regRd = data; + + return 3; + } + + private static UInt64 LDRH1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + (imm * 2u); + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read16(address), (int)(8 * (address & 1))); + regRd = data; + + return 3; + } + + private static UInt64 LDRH2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + UInt32 data = BitOperations.RotateRight(cpu._callbackInterface._read16(address), (int)(8 * (address & 1))); + regRd = data; + + return 3; + } + + private static UInt64 LDRSB(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + Byte data = cpu._callbackInterface._read8(address); + regRd = SignExtend(data, 8); + + return 3; + } + + private static UInt64 LDRSH(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + + if ((address & 1) == 1) + { + Byte data = cpu._callbackInterface._read8(address); + regRd = SignExtend(data, 8); + } + else + { + UInt16 data = cpu._callbackInterface._read16(address); + regRd = SignExtend(data, 16); + } + + return 3; + } + + private static UInt64 LSL1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = imm; + + if (shiftAmount == 0) + { + regRd = regRm; + } + else + { + cpu.SetFlag(Flag.C, (regRm >> (32 - shiftAmount)) & 1); + regRd = regRm << shiftAmount; + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 LSL2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rs = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = (int)(regRs & 0xff); + + if (shiftAmount == 0) + { + // nothing to do + } + else if (shiftAmount < 32) + { + cpu.SetFlag(Flag.C, (regRd >> (32 - shiftAmount)) & 1); + regRd <<= shiftAmount; + } + else if (shiftAmount == 32) + { + cpu.SetFlag(Flag.C, regRd & 1); + regRd = 0; + } + else + { + cpu.SetFlag(Flag.C, 0); + regRd = 0; + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 2; + } + + private static UInt64 LSR1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = imm; + + if (shiftAmount == 0) + { + cpu.SetFlag(Flag.C, regRm >> 31); + regRd = 0; + } + else + { + cpu.SetFlag(Flag.C, (regRm >> (shiftAmount - 1)) & 1); + regRd = regRm >> shiftAmount; + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 LSR2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rs = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + int shiftAmount = (int)(regRs & 0xff); + + if (shiftAmount == 0) + { + // nothing to do + } + else if (shiftAmount < 32) + { + cpu.SetFlag(Flag.C, (regRd >> (shiftAmount - 1)) & 1); + regRd >>= shiftAmount; + } + else if (shiftAmount == 32) + { + cpu.SetFlag(Flag.C, regRd >> 31); + regRd = 0; + } + else + { + cpu.SetFlag(Flag.C, 0); + regRd = 0; + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 2; + } + + private static UInt64 MOV1(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd = imm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 MOV3(CPU_Core cpu, UInt16 instruction) + { + UInt16 h1 = (UInt16)((instruction >> 7) & 1); + UInt16 h2 = (UInt16)((instruction >> 6) & 1); + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + rd |= (UInt16)(h1 << 3); + rm |= (UInt16)(h2 << 3); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + + SetReg(cpu, rd, regRm); + + return (rd == PC) ? 3u : 1u; + } + + private static UInt64 MUL(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt64 m = ComputeMultiplicationCycleCount(regRd, regRm); + regRd *= regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return m + 1; + } + + private static UInt64 MVN(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd = ~regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 NEG(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = 0; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 ORR(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + regRd |= regRm; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 1; + } + + private static UInt64 POP(CPU_Core cpu, UInt16 instruction) + { + UInt16 r = (UInt16)((instruction >> 8) & 1); + UInt16 registerList = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + UInt32 address = regSP; + regSP += 4 * (r + n); + + for (int i = 0; i <= 7; ++i) + { + if (((registerList >> i) & 1) == 1) + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + regRi = cpu._callbackInterface._read32(address); + address += 4; + } + } + + if (r == 1) + { + SetPC(cpu, cpu._callbackInterface._read32(address)); + + return n + 5; + } + else + { + return n + 4; + } + } + + private static UInt64 PUSH(CPU_Core cpu, UInt16 instruction) + { + UInt16 r = (UInt16)((instruction >> 8) & 1); + UInt16 registerList = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + regSP -= 4 * (r + n); + UInt32 address = regSP; + + for (int i = 0; i <= 7; ++i) + { + if (((registerList >> i) & 1) == 1) + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + cpu._callbackInterface._write32(address, regRi); + address += 4; + } + } + + if (r == 1) + { + ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, LR); + + cpu._callbackInterface._write32(address, regLR); + + return n + 2; + } + else + { + return n + 1; + } + } + + private static UInt64 ROR(CPU_Core cpu, UInt16 instruction) + { + UInt16 rs = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, rs); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + if ((regRs & 0xff) == 0) + { + // nothing to do + } + else if ((regRs & 0b1_1111) == 0) + { + cpu.SetFlag(Flag.C, regRd >> 31); + } + else + { + cpu.SetFlag(Flag.C, (regRd >> (int)((regRs & 0b1_1111) - 1)) & 1); + regRd = BitOperations.RotateRight(regRd, (int)(regRs & 0b1_1111)); + } + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + + return 2; + } + + private static UInt64 SBC(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRd; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand - (UInt64)Not(cpu.GetFlag(Flag.C)); + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 STMIA(CPU_Core cpu, UInt16 instruction) + { + UInt16 rn = (UInt16)((instruction >> 8) & 0b111); + UInt16 registerList = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 address = regRn; + + if (registerList == 0) + { + ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, PC); + + regRn += 0x40; + cpu._callbackInterface._write32(address, regPC + 2); + + return 2; + } + else + { + UInt32 n = (UInt32)BitOperations.PopCount(registerList); + UInt32 oldRegRn = regRn; + regRn += n * 4; + + for (int i = 0; i <= 7; ++i) + { + if (((registerList >> i) & 1) == 1) + { + if ((i == rn) && ((registerList & ~(0xff << i)) == 0)) + { + cpu._callbackInterface._write32(address, oldRegRn); + } + else + { + ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, i); + + cpu._callbackInterface._write32(address, regRi); + } + + address += 4; + } + } + + return n + 1; + } + } + + private static UInt64 STR1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + (imm * 4u); + cpu._callbackInterface._write32(address, regRd); + + return 2; + } + + private static UInt64 STR2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + cpu._callbackInterface._write32(address, regRd); + + return 2; + } + + private static UInt64 STR3(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + UInt32 address = regSP + (imm * 4u); + cpu._callbackInterface._write32(address, regRd); + + return 2; + } + + private static UInt64 STRB1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + imm; + cpu._callbackInterface._write8(address, (Byte)regRd); + + return 2; + } + + private static UInt64 STRB2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + cpu._callbackInterface._write8(address, (Byte)regRd); + + return 2; + } + + private static UInt64 STRH1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b1_1111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + (imm * 2u); + cpu._callbackInterface._write16(address, (UInt16)regRd); + + return 2; + } + + private static UInt64 STRH2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 address = regRn + regRm; + cpu._callbackInterface._write16(address, (UInt16)regRd); + + return 2; + } + + private static UInt64 SUB1(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = imm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 SUB2(CPU_Core cpu, UInt16 instruction) + { + UInt16 rd = (UInt16)((instruction >> 8) & 0b111); + UInt16 imm = (UInt16)(instruction & 0xff); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRd; + UInt32 rightOperand = imm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 SUB3(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 6) & 0b111); + UInt16 rn = (UInt16)((instruction >> 3) & 0b111); + UInt16 rd = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, rd); + + UInt32 leftOperand = regRn; + UInt32 rightOperand = regRm; + + UInt64 result = (UInt64)leftOperand - (UInt64)rightOperand; + regRd = (UInt32)result; + + cpu.SetFlag(Flag.N, regRd >> 31); + cpu.SetFlag(Flag.Z, (regRd == 0) ? 1u : 0u); + cpu.SetFlag(Flag.C, Not(BorrowFrom(result))); + cpu.SetFlag(Flag.V, OverflowFrom_Subtraction(leftOperand, rightOperand, regRd)); + + return 1; + } + + private static UInt64 SUB4(CPU_Core cpu, UInt16 instruction) + { + UInt16 imm = (UInt16)(instruction & 0x7f); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, SP); + + regSP -= (UInt32)imm << 2; + + return 1; + } + + private static UInt64 SWI(CPU_Core cpu, UInt16 instruction) + { + return cpu._callbackInterface._handleSWI(); + } + + private static UInt64 TST(CPU_Core cpu, UInt16 instruction) + { + UInt16 rm = (UInt16)((instruction >> 3) & 0b111); + UInt16 rn = (UInt16)(instruction & 0b111); + + ref UInt32 regDataRef = ref MemoryMarshal.GetArrayDataReference(cpu.Reg); + ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, rm); + ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, rn); + + UInt32 aluOut = regRn & regRm; + + cpu.SetFlag(Flag.N, aluOut >> 31); + cpu.SetFlag(Flag.Z, (aluOut == 0) ? 1u : 0u); + + return 1; + } + } +} diff --git a/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta b/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta new file mode 100644 index 0000000..25c53e6 --- /dev/null +++ b/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 300995068a952f743aaddf6f4736c84c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.Common.meta b/Assets/Iris/Iris.Common.meta new file mode 100644 index 0000000..fec6a2f --- /dev/null +++ b/Assets/Iris/Iris.Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 51d282b83f644064f8adda50edd656d7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.Common/Scheduler.cs b/Assets/Iris/Iris.Common/Scheduler.cs new file mode 100644 index 0000000..e88761e --- /dev/null +++ b/Assets/Iris/Iris.Common/Scheduler.cs @@ -0,0 +1,138 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Iris.Common +{ + public sealed class Scheduler(int taskListSize, int scheduledTaskListSize) + { + public delegate void Task_Delegate(UInt64 cycleCountDelay); + private readonly Task_Delegate[] _taskList = new Task_Delegate[taskListSize]; + + private struct ScheduledTaskListEntry + { + internal int _id; + internal UInt64 _cycleCount; + } + + private readonly ScheduledTaskListEntry[] _scheduledTaskList = new ScheduledTaskListEntry[scheduledTaskListSize]; // sorted by _cycleCount from smallest to largest + private int _scheduledTaskCount; + + private UInt64 _cycleCounter; + + public void ResetState() + { + _scheduledTaskCount = 0; + _cycleCounter = 0; + } + + public void LoadState(BinaryReader reader) + { + foreach (ref ScheduledTaskListEntry entry in _scheduledTaskList.AsSpan()) + { + entry._id = reader.ReadInt32(); + entry._cycleCount = reader.ReadUInt64(); + } + + _scheduledTaskCount = reader.ReadInt32(); + _cycleCounter = reader.ReadUInt64(); + } + + public void SaveState(BinaryWriter writer) + { + foreach (ScheduledTaskListEntry entry in _scheduledTaskList) + { + writer.Write(entry._id); + writer.Write(entry._cycleCount); + } + + writer.Write(_scheduledTaskCount); + writer.Write(_cycleCounter); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UInt64 GetCycleCounter() + { + return _cycleCounter; + } + + public void AdvanceCycleCounter(UInt64 cycleCount) + { + _cycleCounter += cycleCount; + + // process tasks + ref readonly ScheduledTaskListEntry firstEntry = ref MemoryMarshal.GetArrayDataReference(_scheduledTaskList); + ref Task_Delegate taskListDataRef = ref MemoryMarshal.GetArrayDataReference(_taskList); + + while ((_scheduledTaskCount > 0) && (firstEntry._cycleCount <= _cycleCounter)) + { + // save the task + ScheduledTaskListEntry entry = firstEntry; + + // remove it from the list + --_scheduledTaskCount; + + if (_scheduledTaskCount > 0) + Array.Copy(_scheduledTaskList, 1, _scheduledTaskList, 0, _scheduledTaskCount); + + // execute it + Unsafe.Add(ref taskListDataRef, entry._id)(_cycleCounter - entry._cycleCount); + } + } + + public void RegisterTask(int id, Task_Delegate task) + { + _taskList[id] = task; + } + + public void ScheduleTask(int id, UInt64 cycleCount) + { + // convert cycleCount from relative to absolute + cycleCount += _cycleCounter; + + // get the position and reference of the new task + // (searching is done backward because a new task is more likely to be inserted towards the end) + int index = _scheduledTaskCount; + ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1); + + while ((index > 0) && (entry._cycleCount > cycleCount)) + { + --index; + entry = ref Unsafe.Subtract(ref entry, 1); + } + + entry = ref Unsafe.Add(ref entry, 1); + + // insert the new task + if (index < _scheduledTaskCount) + Array.Copy(_scheduledTaskList, index, _scheduledTaskList, index + 1, _scheduledTaskCount - index); + + entry._id = id; + entry._cycleCount = cycleCount; + + ++_scheduledTaskCount; + } + + public void CancelTask(int id) + { + int index = 0; + ref ScheduledTaskListEntry entry = ref MemoryMarshal.GetArrayDataReference(_scheduledTaskList); + + while (index < _scheduledTaskCount) + { + if (entry._id == id) + { + --_scheduledTaskCount; + + if (index < _scheduledTaskCount) + Array.Copy(_scheduledTaskList, index + 1, _scheduledTaskList, index, _scheduledTaskCount - index); + + return; + } + + ++index; + entry = ref Unsafe.Add(ref entry, 1); + } + } + } +} diff --git a/Assets/Iris/Iris.Common/Scheduler.cs.meta b/Assets/Iris/Iris.Common/Scheduler.cs.meta new file mode 100644 index 0000000..e9fcd36 --- /dev/null +++ b/Assets/Iris/Iris.Common/Scheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b12f8d8d769413c4bbeca054bf9013fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.Common/System.cs b/Assets/Iris/Iris.Common/System.cs new file mode 100644 index 0000000..e98796b --- /dev/null +++ b/Assets/Iris/Iris.Common/System.cs @@ -0,0 +1,43 @@ +namespace Iris.Common +{ + public abstract class System : IDisposable + { + public delegate void PollInput_Delegate(); + public delegate void PresentFrame_Delegate(UInt16[] frameBuffer); + + public enum Key + { + A, + B, + Select, + Start, + Right, + Left, + Up, + Down, + R, + L, + X, + Y, + } + + public enum KeyStatus + { + Input = 0, + NoInput = 1 + } + + public abstract void Dispose(); + + public abstract void ResetState(bool skipIntro); + public abstract void LoadState(BinaryReader reader); + public abstract void SaveState(BinaryWriter writer); + + public abstract void LoadROM(string filename); + public abstract void SetKeyStatus(Key key, KeyStatus status); + + public abstract bool IsRunning(); + public abstract void Run(); + public abstract void Pause(); + } +} diff --git a/Assets/Iris/Iris.Common/System.cs.meta b/Assets/Iris/Iris.Common/System.cs.meta new file mode 100644 index 0000000..5eed498 --- /dev/null +++ b/Assets/Iris/Iris.Common/System.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d321636c09403e249a26ee8f85908765 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA.meta b/Assets/Iris/Iris.GBA.meta new file mode 100644 index 0000000..458c459 --- /dev/null +++ b/Assets/Iris/Iris.GBA.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 16247def3e95ffe48b0f4909962cd91a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/BIOS.cs b/Assets/Iris/Iris.GBA/BIOS.cs new file mode 100644 index 0000000..f7868d4 --- /dev/null +++ b/Assets/Iris/Iris.GBA/BIOS.cs @@ -0,0 +1,117 @@ +using System.Runtime.InteropServices; + +namespace Iris.GBA +{ + internal sealed class BIOS : IDisposable + { + private const int KB = 1024; + private const int BIOS_Size = 16 * KB; + private readonly IntPtr _bios = Marshal.AllocHGlobal(BIOS_Size); + + private const UInt32 BIOS_StartAddress = 0x0000_0000; + private const UInt32 BIOS_EndAddress = 0x0000_4000; + + private CPU.CPU_Core _cpu; + private bool _disposed; + + internal BIOS() + { + Byte[] data; + + try + { + data = File.ReadAllBytes("gba_bios.bin"); + } + catch (FileNotFoundException) + { + throw new Exception("Iris.GBA.BIOS: Could not find BIOS"); + } + catch + { + throw new Exception("Iris.GBA.BIOS: Could not read BIOS"); + } + + if (data.Length != BIOS_Size) + throw new Exception("Iris.GBA.BIOS: Wrong BIOS size"); + + Marshal.Copy(data, 0, _bios, BIOS_Size); + } + + ~BIOS() + { + Dispose(); + } + + public void Dispose() + { + if (_disposed) + return; + + Marshal.FreeHGlobal(_bios); + + GC.SuppressFinalize(this); + _disposed = true; + } + + internal void Initialize(CPU.CPU_Core cpu, Memory memory) + { + _cpu = cpu; + + memory.Map(_bios, BIOS_Size, BIOS_StartAddress, BIOS_EndAddress, Memory.Flag.AllRead); + } + + internal void Reset(bool skipIntro) + { + if (skipIntro) + { + _cpu.Reg[CPU.CPU_Core.SP] = 0x300_7f00; + _cpu.Reg[CPU.CPU_Core.LR] = 0x800_0000; + + _cpu.CPSR = 0x1f; + + _cpu.Reg13_svc = 0x300_7fe0; + _cpu.Reg13_irq = 0x300_7fa0; + + _cpu.NextInstructionAddress = 0x800_0000; + } + else + { + _cpu.CPSR = 0xd3; + _cpu.NextInstructionAddress = 0; + } + } + + internal Byte Read8(UInt32 address) + { + return 0; + } + + internal UInt16 Read16(UInt32 address) + { + return 0; + } + + internal UInt32 Read32(UInt32 address) + { + return 0; + } + + internal UInt64 HandleSWI() + { + _cpu.Reg14_svc = _cpu.NextInstructionAddress; + _cpu.SPSR_svc = _cpu.CPSR; + _cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x93u); + _cpu.NextInstructionAddress = 0x08; + return 3; + } + + internal UInt64 HandleIRQ() + { + _cpu.Reg14_irq = _cpu.NextInstructionAddress + 4; + _cpu.SPSR_irq = _cpu.CPSR; + _cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x92u); + _cpu.NextInstructionAddress = 0x18; + return 3; + } + } +} diff --git a/Assets/Iris/Iris.GBA/BIOS.cs.meta b/Assets/Iris/Iris.GBA/BIOS.cs.meta new file mode 100644 index 0000000..f821dd0 --- /dev/null +++ b/Assets/Iris/Iris.GBA/BIOS.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a44ddbc1235a2b9448cedcd04283cb60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/Communication.cs b/Assets/Iris/Iris.GBA/Communication.cs new file mode 100644 index 0000000..480f068 --- /dev/null +++ b/Assets/Iris/Iris.GBA/Communication.cs @@ -0,0 +1,247 @@ +namespace Iris.GBA +{ + internal sealed class Communication + { + internal enum Register + { + SIODATA0, + SIODATA1, + SIODATA2, + SIODATA3, + + SIOCNT, + SIODATA_SEND, + + RCNT, + + JOYCNT, + + JOY_RECV_L, + JOY_RECV_H, + + JOY_TRANS_L, + JOY_TRANS_H, + + JOYSTAT + } + + private UInt16 _SIODATA0; // SIOMULTI0 / SIODATA32_L + private UInt16 _SIODATA1; // SIOMULTI1 / SIODATA32_H + private UInt16 _SIODATA2; // SIOMULTI2 + private UInt16 _SIODATA3; // SIOMULTI3 + + private UInt16 _SIOCNT; + private UInt16 _SIODATA_SEND; // SIOMLT_SEND / SIODATA_8 + + private UInt16 _RCNT; + + private UInt16 _JOYCNT; + + private UInt16 _JOY_RECV_L; + private UInt16 _JOY_RECV_H; + + private UInt16 _JOY_TRANS_L; + private UInt16 _JOY_TRANS_H; + + private UInt16 _JOYSTAT; + + private InterruptControl _interruptControl; + + internal void Initialize(InterruptControl interruptControl) + { + _interruptControl = interruptControl; + } + + internal void ResetState() + { + _SIODATA0 = 0; + _SIODATA1 = 0; + _SIODATA2 = 0; + _SIODATA3 = 0; + + _SIOCNT = 0; + _SIODATA_SEND = 0; + + _RCNT = 0; + + _JOYCNT = 0; + + _JOY_RECV_L = 0; + _JOY_RECV_H = 0; + + _JOY_TRANS_L = 0; + _JOY_TRANS_H = 0; + + _JOYSTAT = 0; + } + + internal void LoadState(BinaryReader reader) + { + _SIODATA0 = reader.ReadUInt16(); + _SIODATA1 = reader.ReadUInt16(); + _SIODATA2 = reader.ReadUInt16(); + _SIODATA3 = reader.ReadUInt16(); + + _SIOCNT = reader.ReadUInt16(); + _SIODATA_SEND = reader.ReadUInt16(); + + _RCNT = reader.ReadUInt16(); + + _JOYCNT = reader.ReadUInt16(); + + _JOY_RECV_L = reader.ReadUInt16(); + _JOY_RECV_H = reader.ReadUInt16(); + + _JOY_TRANS_L = reader.ReadUInt16(); + _JOY_TRANS_H = reader.ReadUInt16(); + + _JOYSTAT = reader.ReadUInt16(); + } + + internal void SaveState(BinaryWriter writer) + { + writer.Write(_SIODATA0); + writer.Write(_SIODATA1); + writer.Write(_SIODATA2); + writer.Write(_SIODATA3); + + writer.Write(_SIOCNT); + writer.Write(_SIODATA_SEND); + + writer.Write(_RCNT); + + writer.Write(_JOYCNT); + + writer.Write(_JOY_RECV_L); + writer.Write(_JOY_RECV_H); + + writer.Write(_JOY_TRANS_L); + writer.Write(_JOY_TRANS_H); + + writer.Write(_JOYSTAT); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.SIODATA0 => _SIODATA0, + Register.SIODATA1 => _SIODATA1, + Register.SIODATA2 => _SIODATA2, + Register.SIODATA3 => _SIODATA3, + + Register.SIOCNT => _SIOCNT, + Register.SIODATA_SEND => _SIODATA_SEND, + + Register.RCNT => _RCNT, + + Register.JOYCNT => _JOYCNT, + + Register.JOY_RECV_L => _JOY_RECV_L, + Register.JOY_RECV_H => _JOY_RECV_H, + + Register.JOY_TRANS_L => _JOY_TRANS_L, + Register.JOY_TRANS_H => _JOY_TRANS_H, + + Register.JOYSTAT => _JOYSTAT, + + // should never happen + _ => throw new Exception("Iris.GBA.Communication: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.SIODATA0: + Memory.WriteRegisterHelper(ref _SIODATA0, value, mode); + break; + case Register.SIODATA1: + Memory.WriteRegisterHelper(ref _SIODATA1, value, mode); + break; + case Register.SIODATA2: + Memory.WriteRegisterHelper(ref _SIODATA2, value, mode); + break; + case Register.SIODATA3: + Memory.WriteRegisterHelper(ref _SIODATA3, value, mode); + break; + + case Register.SIOCNT: + Memory.WriteRegisterHelper(ref _SIOCNT, value, mode); + CheckTransfer(); + break; + case Register.SIODATA_SEND: + Memory.WriteRegisterHelper(ref _SIODATA_SEND, value, mode); + break; + + case Register.RCNT: + Memory.WriteRegisterHelper(ref _RCNT, value, mode); + CheckTransfer(); + break; + + case Register.JOYCNT: + Memory.WriteRegisterHelper(ref _JOYCNT, value, mode); + break; + + case Register.JOY_RECV_L: + Memory.WriteRegisterHelper(ref _JOY_RECV_L, value, mode); + break; + case Register.JOY_RECV_H: + Memory.WriteRegisterHelper(ref _JOY_RECV_H, value, mode); + break; + + case Register.JOY_TRANS_L: + Memory.WriteRegisterHelper(ref _JOY_TRANS_L, value, mode); + break; + case Register.JOY_TRANS_H: + Memory.WriteRegisterHelper(ref _JOY_TRANS_H, value, mode); + break; + + case Register.JOYSTAT: + Memory.WriteRegisterHelper(ref _JOYSTAT, value, mode); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.Communication: Register write error"); + } + } + + private void CheckTransfer() + { + switch ((_RCNT >> 14) & 0b11) + { + case 0b00: + case 0b01: + switch ((_SIOCNT >> 12) & 0b11) + { + case 0b00: // 8 bits normal serial communication + case 0b01: // 32 bits normal serial communication + case 0b10: // 16 bits multiplayer serial communication + if ((_SIOCNT & 0x0080) == 0x0080) + { + _SIOCNT = (UInt16)(_SIOCNT & ~0x0080); + + if ((_SIOCNT & 0x4000) == 0x4000) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.SIO); + } + break; + + case 0b11: + Console.WriteLine("[Iris.GBA.Communication] UART communication not implemented"); + break; + } + break; + + case 0b10: + Console.WriteLine("[Iris.GBA.Communication] General purpose communication not implemented"); + break; + + case 0b11: + Console.WriteLine("[Iris.GBA.Communication] JOY Bus communication not implemented"); + break; + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/Communication.cs.meta b/Assets/Iris/Iris.GBA/Communication.cs.meta new file mode 100644 index 0000000..72e7fd5 --- /dev/null +++ b/Assets/Iris/Iris.GBA/Communication.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cb1b07716db45d45b2681382a0ff8fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/DMA.cs b/Assets/Iris/Iris.GBA/DMA.cs new file mode 100644 index 0000000..f91b7b9 --- /dev/null +++ b/Assets/Iris/Iris.GBA/DMA.cs @@ -0,0 +1,469 @@ +using System.Runtime.CompilerServices; + +namespace Iris.GBA +{ + internal sealed class DMA + { + internal enum Register + { + DMA0SAD_L, + DMA0SAD_H, + + DMA0DAD_L, + DMA0DAD_H, + + DMA0CNT_L, + DMA0CNT_H, + + DMA1SAD_L, + DMA1SAD_H, + + DMA1DAD_L, + DMA1DAD_H, + + DMA1CNT_L, + DMA1CNT_H, + + DMA2SAD_L, + DMA2SAD_H, + + DMA2DAD_L, + DMA2DAD_H, + + DMA2CNT_L, + DMA2CNT_H, + + DMA3SAD_L, + DMA3SAD_H, + + DMA3DAD_L, + DMA3DAD_H, + + DMA3CNT_L, + DMA3CNT_H + } + + internal enum StartTiming + { + Immediate = 0b00, + VBlank = 0b01, + HBlank = 0b10, + Special = 0b11 + } + + private readonly Common.Scheduler _scheduler; + + private InterruptControl _interruptControl; + private Memory _memory; + + private struct Channel + { + internal UInt32 _source; + internal UInt32 _sourceReload; + internal UInt32 _destination; + internal UInt32 _destinationReload; + internal UInt32 _length; + internal UInt16 _lengthReload; + internal UInt16 _control; + internal bool _running; + } + + private Channel _channel0; + private Channel _channel1; + private Channel _channel2; + private Channel _channel3; + + private const UInt32 MaxLengthChannel0 = 0x4000; + private const UInt32 MaxLengthChannel1 = 0x4000; + private const UInt32 MaxLengthChannel2 = 0x4000; + private const UInt32 MaxLengthChannel3 = 0x1_0000; + + internal DMA(Common.Scheduler scheduler) + { + _scheduler = scheduler; + + _scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel0, _ => Start(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0)); + _scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel1, _ => Start(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1)); + _scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel2, _ => Start(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2)); + _scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel3, _ => Start(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3)); + } + + internal void Initialize(InterruptControl interruptControl, Memory memory) + { + _interruptControl = interruptControl; + _memory = memory; + } + + internal void ResetState() + { + _channel0 = default; + _channel1 = default; + _channel2 = default; + _channel3 = default; + } + + internal void LoadState(BinaryReader reader) + { + void LoadChannel(ref Channel channel) + { + channel._source = reader.ReadUInt32(); + channel._sourceReload = reader.ReadUInt32(); + channel._destination = reader.ReadUInt32(); + channel._destinationReload = reader.ReadUInt32(); + channel._length = reader.ReadUInt32(); + channel._lengthReload = reader.ReadUInt16(); + channel._control = reader.ReadUInt16(); + channel._running = reader.ReadBoolean(); + } + + LoadChannel(ref _channel0); + LoadChannel(ref _channel1); + LoadChannel(ref _channel2); + LoadChannel(ref _channel3); + } + + internal void SaveState(BinaryWriter writer) + { + void SaveChannel(Channel channel) + { + writer.Write(channel._source); + writer.Write(channel._sourceReload); + writer.Write(channel._destination); + writer.Write(channel._destinationReload); + writer.Write(channel._length); + writer.Write(channel._lengthReload); + writer.Write(channel._control); + writer.Write(channel._running); + } + + SaveChannel(_channel0); + SaveChannel(_channel1); + SaveChannel(_channel2); + SaveChannel(_channel3); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.DMA0CNT_H => _channel0._control, + Register.DMA1CNT_H => _channel1._control, + Register.DMA2CNT_H => _channel2._control, + Register.DMA3CNT_H => _channel3._control, + + // should never happen + _ => throw new Exception("Iris.GBA.DMA: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + void WriteSourceReload_Low(ref Channel channel) + { + UInt16 low = (UInt16)channel._sourceReload; + Memory.WriteRegisterHelper(ref low, value, mode); + channel._sourceReload = (channel._sourceReload & 0xffff_0000) | low; + } + + void WriteSourceReload_High(ref Channel channel, UInt16 mask) + { + UInt16 high = (UInt16)(channel._sourceReload >> 16); + Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode); + channel._sourceReload = (channel._sourceReload & 0x0000_ffff) | (UInt32)(high << 16); + } + + void WriteDestinationReload_Low(ref Channel channel) + { + UInt16 low = (UInt16)channel._destinationReload; + Memory.WriteRegisterHelper(ref low, value, mode); + channel._destinationReload = (channel._destinationReload & 0xffff_0000) | low; + } + + void WriteDestinationReload_High(ref Channel channel, UInt16 mask) + { + UInt16 high = (UInt16)(channel._destinationReload >> 16); + Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode); + channel._destinationReload = (channel._destinationReload & 0x0000_ffff) | (UInt32)(high << 16); + } + + void WriteLengthReload(ref Channel channel) + { + UInt16 reload = channel._lengthReload; + Memory.WriteRegisterHelper(ref reload, value, mode); + channel._lengthReload = reload; + } + + void WriteControl(ref Channel channel, GBA_System.TaskId startTaskId) + { + UInt16 previousControl = channel._control; + + UInt16 newControl = channel._control; + Memory.WriteRegisterHelper(ref newControl, value, mode); + channel._control = newControl; + + if ((previousControl & 0x8000) == 0) + { + if ((newControl & 0x8000) == 0x8000) + _scheduler.ScheduleTask((int)startTaskId, 2); + } + else + { + if ((newControl & 0x8000) == 0) + { + if (channel._running) + channel._running = false; + else + _scheduler.CancelTask((int)startTaskId); + } + } + } + + switch (register) + { + case Register.DMA0SAD_L: + WriteSourceReload_Low(ref _channel0); + break; + case Register.DMA0SAD_H: + WriteSourceReload_High(ref _channel0, 0x07ff); + break; + + case Register.DMA0DAD_L: + WriteDestinationReload_Low(ref _channel0); + break; + case Register.DMA0DAD_H: + WriteDestinationReload_High(ref _channel0, 0x07ff); + break; + + case Register.DMA0CNT_L: + WriteLengthReload(ref _channel0); + break; + case Register.DMA0CNT_H: + WriteControl(ref _channel0, GBA_System.TaskId.StartDMA_Channel0); + break; + + case Register.DMA1SAD_L: + WriteSourceReload_Low(ref _channel1); + break; + case Register.DMA1SAD_H: + WriteSourceReload_High(ref _channel1, 0x0fff); + break; + + case Register.DMA1DAD_L: + WriteDestinationReload_Low(ref _channel1); + break; + case Register.DMA1DAD_H: + WriteDestinationReload_High(ref _channel1, 0x07ff); + break; + + case Register.DMA1CNT_L: + WriteLengthReload(ref _channel1); + break; + case Register.DMA1CNT_H: + WriteControl(ref _channel1, GBA_System.TaskId.StartDMA_Channel1); + break; + + case Register.DMA2SAD_L: + WriteSourceReload_Low(ref _channel2); + break; + case Register.DMA2SAD_H: + WriteSourceReload_High(ref _channel2, 0x0fff); + break; + + case Register.DMA2DAD_L: + WriteDestinationReload_Low(ref _channel2); + break; + case Register.DMA2DAD_H: + WriteDestinationReload_High(ref _channel2, 0x07ff); + break; + + case Register.DMA2CNT_L: + WriteLengthReload(ref _channel2); + break; + case Register.DMA2CNT_H: + WriteControl(ref _channel2, GBA_System.TaskId.StartDMA_Channel2); + break; + + case Register.DMA3SAD_L: + WriteSourceReload_Low(ref _channel3); + break; + case Register.DMA3SAD_H: + WriteSourceReload_High(ref _channel3, 0x0fff); + break; + + case Register.DMA3DAD_L: + WriteDestinationReload_Low(ref _channel3); + break; + case Register.DMA3DAD_H: + WriteDestinationReload_High(ref _channel3, 0x0fff); + break; + + case Register.DMA3CNT_L: + WriteLengthReload(ref _channel3); + break; + case Register.DMA3CNT_H: + WriteControl(ref _channel3, GBA_System.TaskId.StartDMA_Channel3); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.DMA: Register write error"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PerformVBlankTransfers() + { + if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.VBlank)) + PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0); + + if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.VBlank)) + PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1); + + if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.VBlank)) + PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2); + + if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.VBlank)) + PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void PerformHBlankTransfers() + { + if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.HBlank)) + PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0); + + if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.HBlank)) + PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1); + + if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.HBlank)) + PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2); + + if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.HBlank)) + PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3); + } + + internal void PerformVideoTransfer(bool disable) + { + if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.Special)) + { + PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3); + + if (disable) + { + _channel3._control = (UInt16)(_channel3._control & ~0x8000); + _channel3._running = false; + } + } + } + + private void Start(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength) + { + channel._source = channel._sourceReload; + channel._destination = channel._destinationReload; + channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload; + channel._running = true; + + if (((channel._control >> 12) & 0b11) == (int)StartTiming.Immediate) + PerformTransfer(ref channel, interrupt, maxLength); + } + + private void PerformTransfer(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength) + { + UInt16 sourceAddressControlFlag = (UInt16)((channel._control >> 7) & 0b11); + UInt16 destinationAddressControlFlag = (UInt16)((channel._control >> 5) & 0b11); + + int GetSourceIncrement(int dataUnitSize) + { + return sourceAddressControlFlag switch + { + // increment + 0b00 => dataUnitSize, + // decrement + 0b01 => -dataUnitSize, + // fixed + 0b10 => 0, + // prohibited + 0b11 => 0, + // should never happen + _ => throw new Exception("Iris.GBA.DMA: Wrong source address control flag"), + }; + } + + (int destinationIncrement, bool reloadDestination) GetDestinationIncrement(int dataUnitSize) + { + return destinationAddressControlFlag switch + { + // increment + 0b00 => (dataUnitSize, false), + // decrement + 0b01 => (-dataUnitSize, false), + // fixed + 0b10 => (0, false), + // increment+reload + 0b11 => (dataUnitSize, true), + // should never happen + _ => throw new Exception("Iris.GBA.DMA: Wrong destination address control flag"), + }; + } + + bool reloadDestination; + + // 16 bits + if ((channel._control & 0x0400) == 0) + { + const int DataUnitSize = 2; + + int sourceIncrement = GetSourceIncrement(DataUnitSize); + (int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize); + + for (; channel._length > 0; --channel._length) + { + _memory.Write16(channel._destination, _memory.Read16(channel._source)); + + channel._source = (UInt32)(channel._source + sourceIncrement); + channel._destination = (UInt32)(channel._destination + destinationIncrement); + + _scheduler.AdvanceCycleCounter(2); + } + } + + // 32 bits + else + { + const int DataUnitSize = 4; + + int sourceIncrement = GetSourceIncrement(DataUnitSize); + (int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize); + + for (; channel._length > 0; --channel._length) + { + _memory.Write32(channel._destination, _memory.Read32(channel._source)); + + channel._source = (UInt32)(channel._source + sourceIncrement); + channel._destination = (UInt32)(channel._destination + destinationIncrement); + + _scheduler.AdvanceCycleCounter(2); + } + } + + if ((channel._control & 0x4000) == 0x4000) + _interruptControl.RequestInterrupt(interrupt); + + // Repeat off + if ((channel._control & 0x0200) == 0) + { + channel._control = (UInt16)(channel._control & ~0x8000); + channel._running = false; + } + + // Repeat on + else + { + if (reloadDestination) + channel._destination = channel._destinationReload; + + channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload; + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/DMA.cs.meta b/Assets/Iris/Iris.GBA/DMA.cs.meta new file mode 100644 index 0000000..c39d5d0 --- /dev/null +++ b/Assets/Iris/Iris.GBA/DMA.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0dcd66b296ec4434b836a1075fb8a12a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/GBA_System.cs b/Assets/Iris/Iris.GBA/GBA_System.cs new file mode 100644 index 0000000..17b6b3e --- /dev/null +++ b/Assets/Iris/Iris.GBA/GBA_System.cs @@ -0,0 +1,178 @@ +using System.Security.Cryptography; + +namespace Iris.GBA +{ + public sealed class GBA_System : Common.System + { + internal enum TaskId + { + // ---- Timer ---- + StartTimer_Channel0, + StartTimer_Channel1, + StartTimer_Channel2, + StartTimer_Channel3, + + HandleTimerOverflow_Channel0, + HandleTimerOverflow_Channel1, + HandleTimerOverflow_Channel2, + HandleTimerOverflow_Channel3, + + // ---- DMA ---- + StartDMA_Channel0, + StartDMA_Channel1, + StartDMA_Channel2, + StartDMA_Channel3, + + // ---- KeyInput ---- + CheckKeyInterrupt, + + // ---- Video ---- + StartHBlank, + StartScanline + } + + private static readonly int s_taskIdCount = Enum.GetNames(typeof(TaskId)).Length; + private readonly Common.Scheduler _scheduler = new(s_taskIdCount, 2 * s_taskIdCount); + + private readonly CPU.CPU_Core _cpu; + private readonly Communication _communication = new(); + private readonly Timer _timer; + private readonly Sound _sound = new(); + private readonly DMA _dma; + private readonly KeyInput _keyInput; + private readonly SystemControl _systemControl = new(); + private readonly InterruptControl _interruptControl = new(); + private readonly Memory _memory = new(); + private readonly Video _video; + private readonly BIOS _bios = new(); + + private string _romHash; + private bool _running; + + private const string StateSaveMagic = "IRISGBA"; + private const int StateSaveVersion = 1; + + public GBA_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback) + { + CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(_memory.Read8, _memory.Read16, _memory.Read32, _memory.Write8, _memory.Write16, _memory.Write32, _bios.HandleSWI, _bios.HandleIRQ); + + _cpu = new(CPU.CPU_Core.Model.ARM7TDMI, cpuCallbackInterface); + _timer = new(_scheduler); + _dma = new(_scheduler); + _keyInput = new(_scheduler, pollInputCallback); + _video = new(_scheduler, presentFrameCallback); + + _communication.Initialize(_interruptControl); + _timer.Initialize(_interruptControl); + _dma.Initialize(_interruptControl, _memory); + _keyInput.Initialize(_interruptControl); + _interruptControl.Initialize(_cpu); + _memory.Initialize(_communication, _timer, _sound, _dma, _keyInput, _systemControl, _interruptControl, _video, _bios); + _video.Initialize(_dma, _interruptControl, _memory); + _bios.Initialize(_cpu, _memory); + } + + public override void Dispose() + { + _memory.Dispose(); + _video.Dispose(); + _bios.Dispose(); + } + + public override void ResetState(bool skipIntro) + { + _scheduler.ResetState(); // This has to be done first + + _cpu.ResetState(); + _communication.ResetState(); + _timer.ResetState(); + _sound.ResetState(); + _dma.ResetState(); + _keyInput.ResetState(); + _systemControl.ResetState(); + _interruptControl.ResetState(); + _memory.ResetState(); + _video.ResetState(); + + _bios.Reset(skipIntro); // This has to be done last + } + + public override void LoadState(BinaryReader reader) + { + if (reader.ReadString() != StateSaveMagic) + throw new Exception("Iris.GBA.GBA_System: Wrong state save magic"); + + if (reader.ReadInt32() != StateSaveVersion) + throw new Exception("Iris.GBA.GBA_System: Wrong state save version"); + + if (reader.ReadString() != _romHash) + throw new Exception("Iris.GBA.GBA_System: Wrong ROM hash"); + + _scheduler.LoadState(reader); + _cpu.LoadState(reader); + _communication.LoadState(reader); + _timer.LoadState(reader); + _sound.LoadState(reader); + _dma.LoadState(reader); + _keyInput.LoadState(reader); + _systemControl.LoadState(reader); + _interruptControl.LoadState(reader); + _memory.LoadState(reader); + _video.LoadState(reader); + } + + public override void SaveState(BinaryWriter writer) + { + writer.Write(StateSaveMagic); + writer.Write(StateSaveVersion); + writer.Write(_romHash); + + _scheduler.SaveState(writer); + _cpu.SaveState(writer); + _communication.SaveState(writer); + _timer.SaveState(writer); + _sound.SaveState(writer); + _dma.SaveState(writer); + _keyInput.SaveState(writer); + _systemControl.SaveState(writer); + _interruptControl.SaveState(writer); + _memory.SaveState(writer); + _video.SaveState(writer); + } + + public override void LoadROM(string filename) + { + _memory.LoadROM(filename); + + using HashAlgorithm hashAlgorithm = SHA512.Create(); + using FileStream fileStream = File.OpenRead(filename); + _romHash = BitConverter.ToString(hashAlgorithm.ComputeHash(fileStream)); + } + + public override void SetKeyStatus(Key key, KeyStatus status) + { + _keyInput.SetKeyStatus(key, status); + } + + public override bool IsRunning() + { + return _running; + } + + public override void Run() + { + _running = true; + + while (_running) + { + UInt64 cycleCount = _cpu.Step(); + _scheduler.AdvanceCycleCounter(cycleCount); + } + } + + public override void Pause() + { + _running = false; + } + } +} diff --git a/Assets/Iris/Iris.GBA/GBA_System.cs.meta b/Assets/Iris/Iris.GBA/GBA_System.cs.meta new file mode 100644 index 0000000..3cf64ef --- /dev/null +++ b/Assets/Iris/Iris.GBA/GBA_System.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7bc3a1e87e31e9a4abef36e543dab2c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/InterruptControl.cs b/Assets/Iris/Iris.GBA/InterruptControl.cs new file mode 100644 index 0000000..233898c --- /dev/null +++ b/Assets/Iris/Iris.GBA/InterruptControl.cs @@ -0,0 +1,125 @@ +using System.Runtime.CompilerServices; + +namespace Iris.GBA +{ + internal sealed class InterruptControl + { + internal enum Register + { + IE, + IF, + IME + } + + internal enum Interrupt + { + VBlank = 1 << 0, + HBlank = 1 << 1, + VCountMatch = 1 << 2, + Timer0 = 1 << 3, + Timer1 = 1 << 4, + Timer2 = 1 << 5, + Timer3 = 1 << 6, + SIO = 1 << 7, + DMA0 = 1 << 8, + DMA1 = 1 << 9, + DMA2 = 1 << 10, + DMA3 = 1 << 11, + Key = 1 << 12, + //GamePak = 1 << 13 + } + + private UInt16 _IE; + private UInt16 _IF; + private UInt16 _IME; + + private CPU.CPU_Core _cpu; + + internal void Initialize(CPU.CPU_Core cpu) + { + _cpu = cpu; + } + + internal void ResetState() + { + _IE = 0; + _IF = 0; + _IME = 0; + } + + internal void LoadState(BinaryReader reader) + { + _IE = reader.ReadUInt16(); + _IF = reader.ReadUInt16(); + _IME = reader.ReadUInt16(); + } + + internal void SaveState(BinaryWriter writer) + { + writer.Write(_IE); + writer.Write(_IF); + writer.Write(_IME); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.IE => _IE, + Register.IF => _IF, + Register.IME => _IME, + + // should never happen + _ => throw new Exception("Iris.GBA.InterruptControl: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.IE: + Memory.WriteRegisterHelper(ref _IE, value, mode); + break; + + case Register.IF: + switch (mode) + { + case Memory.RegisterWriteMode.LowByte: + _IF &= (UInt16)~value; + break; + case Memory.RegisterWriteMode.HighByte: + _IF &= (UInt16)~(value << 8); + break; + case Memory.RegisterWriteMode.HalfWord: + _IF &= (UInt16)~value; + break; + } + break; + + case Register.IME: + Memory.WriteRegisterHelper(ref _IME, value, mode); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.InterruptControl: Register write error"); + } + + CheckInterrupts(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RequestInterrupt(Interrupt interrupt) + { + _IF |= (UInt16)interrupt; + CheckInterrupts(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckInterrupts() + { + _cpu.NIRQ = ((_IME == 0) || ((_IE & _IF) == 0)) ? CPU.CPU_Core.Signal.High : CPU.CPU_Core.Signal.Low; + } + } +} diff --git a/Assets/Iris/Iris.GBA/InterruptControl.cs.meta b/Assets/Iris/Iris.GBA/InterruptControl.cs.meta new file mode 100644 index 0000000..6d66972 --- /dev/null +++ b/Assets/Iris/Iris.GBA/InterruptControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c58052e2ebb4549418cb2d58b101d5ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/KeyInput.cs b/Assets/Iris/Iris.GBA/KeyInput.cs new file mode 100644 index 0000000..1073613 --- /dev/null +++ b/Assets/Iris/Iris.GBA/KeyInput.cs @@ -0,0 +1,177 @@ +using System.Runtime.CompilerServices; + +namespace Iris.GBA +{ + internal sealed class KeyInput + { + internal enum Register + { + KEYINPUT, + KEYCNT + } + + private UInt16 _KEYINPUT; + private UInt16 _KEYCNT; + + private readonly Common.Scheduler _scheduler; + private readonly Common.System.PollInput_Delegate _pollInputCallback; + + private InterruptControl _interruptControl; + + private const UInt64 CheckInterruptCycleCount = 280_896; // once per frame + private bool _checkingInterrupt; + + internal KeyInput(Common.Scheduler scheduler, Common.System.PollInput_Delegate pollInputCallback) + { + _scheduler = scheduler; + _pollInputCallback = pollInputCallback; + + _scheduler.RegisterTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterrupt); + } + + internal void Initialize(InterruptControl interruptControl) + { + _interruptControl = interruptControl; + } + + internal void ResetState() + { + _KEYINPUT = 0x03ff; + _KEYCNT = 0; + } + + internal void LoadState(BinaryReader reader) + { + _KEYINPUT = reader.ReadUInt16(); + _KEYCNT = reader.ReadUInt16(); + } + + internal void SaveState(BinaryWriter writer) + { + writer.Write(_KEYINPUT); + writer.Write(_KEYCNT); + } + + internal UInt16 ReadRegister(Register register) + { + switch (register) + { + case Register.KEYINPUT: + _pollInputCallback(); + + if ((_KEYCNT & 0x4000) == 0x4000) + CheckInterrupt(); + + return _KEYINPUT; + + case Register.KEYCNT: + return _KEYCNT; + + // should never happen + default: + throw new Exception("Iris.GBA.KeyInput: Register read error"); + } + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.KEYCNT: + Memory.WriteRegisterHelper(ref _KEYCNT, value, mode); + + if ((_KEYCNT & 0x4000) == 0x4000) + { + _pollInputCallback(); + CheckInterrupt(); + + if (!_checkingInterrupt) + { + _checkingInterrupt = true; + _scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount); + } + } + break; + + // should never happen + default: + throw new Exception("Iris.GBA.KeyInput: Register write error"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetKeyStatus(Common.System.Key key, Common.System.KeyStatus status) + { + int pos; + + switch (key) + { + case Common.System.Key.A: + pos = 0; + break; + case Common.System.Key.B: + pos = 1; + break; + case Common.System.Key.Select: + pos = 2; + break; + case Common.System.Key.Start: + pos = 3; + break; + case Common.System.Key.Right: + pos = 4; + break; + case Common.System.Key.Left: + pos = 5; + break; + case Common.System.Key.Up: + pos = 6; + break; + case Common.System.Key.Down: + pos = 7; + break; + case Common.System.Key.R: + pos = 8; + break; + case Common.System.Key.L: + pos = 9; + break; + default: + return; + } + + _KEYINPUT = (UInt16)((_KEYINPUT & ~(1 << pos)) | ((int)status << pos)); + } + + private void CheckInterrupt(UInt64 cycleCountDelay) + { + if ((_KEYCNT & 0x4000) == 0) + { + _checkingInterrupt = false; + return; + } + + _pollInputCallback(); + CheckInterrupt(); + + _scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount - cycleCountDelay); + } + + private void CheckInterrupt() + { + UInt16 mask = (UInt16)(_KEYCNT & 0x03ff); + + if ((_KEYCNT & 0x8000) == 0) + { + if ((~_KEYINPUT & mask) != 0) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key); + } + else + { + // TODO: figure out what happens when mask == 0 + if ((~_KEYINPUT & mask) == mask) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key); + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/KeyInput.cs.meta b/Assets/Iris/Iris.GBA/KeyInput.cs.meta new file mode 100644 index 0000000..bcc8125 --- /dev/null +++ b/Assets/Iris/Iris.GBA/KeyInput.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee2423bcb9113dc4580cb958b890e91d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/Memory.cs b/Assets/Iris/Iris.GBA/Memory.cs new file mode 100644 index 0000000..4b4a6f9 --- /dev/null +++ b/Assets/Iris/Iris.GBA/Memory.cs @@ -0,0 +1,2215 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Iris.GBA +{ + internal sealed class Memory : IDisposable + { + private Communication _communication; + private Timer _timer; + private Sound _sound; + private DMA _dma; + private KeyInput _keyInput; + private SystemControl _systemControl; + private InterruptControl _interruptControl; + private Video _video; + private BIOS _bios; + + [Flags] + internal enum Flag + { + Read8 = 1 << 0, + Read16 = 1 << 1, + Read32 = 1 << 2, + Write8 = 1 << 3, + Write16 = 1 << 4, + Write32 = 1 << 5, + Mirrored = 1 << 6, + + None = 0, + AllRead = Read8 | Read16 | Read32, + AllWrite = Write8 | Write16 | Write32, + All = AllRead | AllWrite | Mirrored, + } + + private const int KB = 1024; + + private const int EWRAM_Size = 256 * KB; + private const int IWRAM_Size = 32 * KB; + //private const int SRAM_Size = 64 * KB; + + private readonly IntPtr _ewram = Marshal.AllocHGlobal(EWRAM_Size); + private readonly IntPtr _iwram = Marshal.AllocHGlobal(IWRAM_Size); + //private readonly IntPtr _sram = Marshal.AllocHGlobal(SRAM_Size); + + private const UInt32 EWRAM_StartAddress = 0x0200_0000; + private const UInt32 EWRAM_EndAddress = 0x0300_0000; + + private const UInt32 IWRAM_StartAddress = 0x0300_0000; + private const UInt32 IWRAM_EndAddress = 0x0400_0000; + + //private const UInt32 SRAM_StartAddress = 0x0e00_0000; + //private const UInt32 SRAM_EndAddress = 0x1000_0000; + + private int _romSize; + private IntPtr _rom; + + private const UInt32 ROM_WaitState0_StartAddress = 0x0800_0000; + private const UInt32 ROM_WaitState0_EndAddress = 0x0a00_0000; + + private const UInt32 ROM_WaitState1_StartAddress = 0x0a00_0000; + private const UInt32 ROM_WaitState1_EndAddress = 0x0c00_0000; + + private const UInt32 ROM_WaitState2_StartAddress = 0x0c00_0000; + private const UInt32 ROM_WaitState2_EndAddress = 0x0e00_0000; + + private const int PageSize = 1 * KB; + private const int PageTableSize = 1 << 18; + + private readonly IntPtr[] _read8PageTable = new IntPtr[PageTableSize]; + private readonly IntPtr[] _read16PageTable = new IntPtr[PageTableSize]; + private readonly IntPtr[] _read32PageTable = new IntPtr[PageTableSize]; + private readonly IntPtr[] _write8PageTable = new IntPtr[PageTableSize]; + private readonly IntPtr[] _write16PageTable = new IntPtr[PageTableSize]; + private readonly IntPtr[] _write32PageTable = new IntPtr[PageTableSize]; + + private bool _disposed; + + ~Memory() + { + Dispose(); + } + + public void Dispose() + { + if (_disposed) + return; + + Marshal.FreeHGlobal(_ewram); + Marshal.FreeHGlobal(_iwram); + //Marshal.FreeHGlobal(_sram); + + GC.SuppressFinalize(this); + _disposed = true; + } + + internal void Initialize(Communication communication, Timer timer, Sound sound, DMA dma, KeyInput keyInput, SystemControl systemControl, InterruptControl interruptControl, Video video, BIOS bios) + { + _communication = communication; + _timer = timer; + _sound = sound; + _dma = dma; + _keyInput = keyInput; + _systemControl = systemControl; + _interruptControl = interruptControl; + _video = video; + _bios = bios; + + Map(_ewram, EWRAM_Size, EWRAM_StartAddress, EWRAM_EndAddress, Flag.All); + Map(_iwram, IWRAM_Size, IWRAM_StartAddress, IWRAM_EndAddress, Flag.All); + //Map(_sram, SRAM_Size, SRAM_StartAddress, SRAM_EndAddress, Flag.Read8 | Flag.Write8 | Flag.Mirrored); + } + + internal void ResetState() + { + byte[] ewramData = new byte[EWRAM_Size]; + byte[] iwramData = new byte[IWRAM_Size]; + //byte[] sramData = new byte[SRAM_Size]; + + Marshal.Copy(ewramData, 0, _ewram, EWRAM_Size); + Marshal.Copy(iwramData, 0, _iwram, IWRAM_Size); + //Marshal.Copy(sramData, 0, _sram, SRAM_Size); + } + + internal void LoadState(BinaryReader reader) + { + byte[] ewramData = reader.ReadBytes(EWRAM_Size); + byte[] iwramData = reader.ReadBytes(IWRAM_Size); + //byte[] sramData = reader.ReadBytes(SRAM_Size); + + Marshal.Copy(ewramData, 0, _ewram, EWRAM_Size); + Marshal.Copy(iwramData, 0, _iwram, IWRAM_Size); + //Marshal.Copy(sramData, 0, _sram, SRAM_Size); + } + + internal void SaveState(BinaryWriter writer) + { + byte[] ewramData = new byte[EWRAM_Size]; + byte[] iwramData = new byte[IWRAM_Size]; + //byte[] sramData = new byte[SRAM_Size]; + + Marshal.Copy(_ewram, ewramData, 0, EWRAM_Size); + Marshal.Copy(_iwram, iwramData, 0, IWRAM_Size); + //Marshal.Copy(_sram, sramData, 0, SRAM_Size); + + writer.Write(ewramData); + writer.Write(iwramData); + //writer.Write(sramData); + } + + internal void Map(IntPtr data, int size, UInt32 startAddress, UInt32 endAddress, Flag flags) + { + int pageCount = size / PageSize; + int startTablePageIndex = (int)(startAddress >> 10); + int endPageTableIndex = (int)(endAddress >> 10); + + bool readable8 = flags.HasFlag(Flag.Read8); + bool readable16 = flags.HasFlag(Flag.Read16); + bool readable32 = flags.HasFlag(Flag.Read32); + bool writable8 = flags.HasFlag(Flag.Write8); + bool writable16 = flags.HasFlag(Flag.Write16); + bool writable32 = flags.HasFlag(Flag.Write32); + bool mirrored = flags.HasFlag(Flag.Mirrored); + + for (int pageTableIndex = startTablePageIndex, pageIndex = 0; pageTableIndex != endPageTableIndex; ++pageTableIndex, ++pageIndex) + { + if (pageIndex < pageCount) + { + int pageOffset = pageIndex * PageSize; + IntPtr page = data + pageOffset; + _read8PageTable[pageTableIndex] = readable8 ? page : IntPtr.Zero; + _read16PageTable[pageTableIndex] = readable16 ? page : IntPtr.Zero; + _read32PageTable[pageTableIndex] = readable32 ? page : IntPtr.Zero; + _write8PageTable[pageTableIndex] = writable8 ? page : IntPtr.Zero; + _write16PageTable[pageTableIndex] = writable16 ? page : IntPtr.Zero; + _write32PageTable[pageTableIndex] = writable32 ? page : IntPtr.Zero; + } + else if (mirrored) + { + int pageOffset = (pageIndex % pageCount) * PageSize; + IntPtr page = data + pageOffset; + _read8PageTable[pageTableIndex] = readable8 ? page : IntPtr.Zero; + _read16PageTable[pageTableIndex] = readable16 ? page : IntPtr.Zero; + _read32PageTable[pageTableIndex] = readable32 ? page : IntPtr.Zero; + _write8PageTable[pageTableIndex] = writable8 ? page : IntPtr.Zero; + _write16PageTable[pageTableIndex] = writable16 ? page : IntPtr.Zero; + _write32PageTable[pageTableIndex] = writable32 ? page : IntPtr.Zero; + } + else + { + _read8PageTable[pageTableIndex] = IntPtr.Zero; + _read16PageTable[pageTableIndex] = IntPtr.Zero; + _read32PageTable[pageTableIndex] = IntPtr.Zero; + _write8PageTable[pageTableIndex] = IntPtr.Zero; + _write16PageTable[pageTableIndex] = IntPtr.Zero; + _write32PageTable[pageTableIndex] = IntPtr.Zero; + } + } + + if (writable8 || writable16 || writable32) + { + int length = pageCount * PageSize; + + for (int offset = 0; offset < length; ++offset) + Marshal.WriteByte(data, offset, 0); + } + } + + internal void LoadROM(string filename) + { + Byte[] data = File.ReadAllBytes(filename); + + _romSize = data.Length; + + if (_rom != IntPtr.Zero) + Marshal.FreeHGlobal(_rom); + + _rom = Marshal.AllocHGlobal(_romSize); + Marshal.Copy(data, 0, _rom, _romSize); + + Map(_rom, _romSize, ROM_WaitState0_StartAddress, ROM_WaitState0_EndAddress, Flag.AllRead); + Map(_rom, _romSize, ROM_WaitState1_StartAddress, ROM_WaitState1_EndAddress, Flag.AllRead); + Map(_rom, _romSize, ROM_WaitState2_StartAddress, ROM_WaitState2_EndAddress, Flag.AllRead); + } + + internal Byte Read8(UInt32 address) + { + address &= 0x0fff_ffff; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_read8PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.ReadByte + return Unsafe.Read((Byte*)page + (address & 0x3ff)); + } + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + return _bios!.Read8(address); + + // IO and registers + case 0x4: + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Byte GetLowByte(UInt16 value) => (Byte)value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Byte GetHighByte(UInt16 value) => (Byte)(value >> 8); + + UInt32 offset = address - 0x400_0000; + + return offset switch + { + 0x000 => GetLowByte(_video!.ReadRegister(Video.Register.DISPCNT)), + 0x001 => GetHighByte(_video!.ReadRegister(Video.Register.DISPCNT)), + + 0x004 => GetLowByte(_video!.ReadRegister(Video.Register.DISPSTAT)), + 0x005 => GetHighByte(_video!.ReadRegister(Video.Register.DISPSTAT)), + + 0x006 => GetLowByte(_video!.ReadRegister(Video.Register.VCOUNT)), + 0x007 => GetHighByte(_video!.ReadRegister(Video.Register.VCOUNT)), + + 0x008 => GetLowByte(_video!.ReadRegister(Video.Register.BG0CNT)), + 0x009 => GetHighByte(_video!.ReadRegister(Video.Register.BG0CNT)), + + 0x00a => GetLowByte(_video!.ReadRegister(Video.Register.BG1CNT)), + 0x00b => GetHighByte(_video!.ReadRegister(Video.Register.BG1CNT)), + + 0x00c => GetLowByte(_video!.ReadRegister(Video.Register.BG2CNT)), + 0x00d => GetHighByte(_video!.ReadRegister(Video.Register.BG2CNT)), + + 0x00e => GetLowByte(_video!.ReadRegister(Video.Register.BG3CNT)), + 0x00f => GetHighByte(_video!.ReadRegister(Video.Register.BG3CNT)), + + 0x048 => GetLowByte(_video!.ReadRegister(Video.Register.WININ)), + 0x049 => GetHighByte(_video!.ReadRegister(Video.Register.WININ)), + + 0x04a => GetLowByte(_video!.ReadRegister(Video.Register.WINOUT)), + 0x04b => GetHighByte(_video!.ReadRegister(Video.Register.WINOUT)), + + 0x050 => GetLowByte(_video!.ReadRegister(Video.Register.BLDCNT)), + 0x051 => GetHighByte(_video!.ReadRegister(Video.Register.BLDCNT)), + + 0x052 => GetLowByte(_video!.ReadRegister(Video.Register.BLDALPHA)), + 0x053 => GetHighByte(_video!.ReadRegister(Video.Register.BLDALPHA)), + + 0x060 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_L)), + 0x061 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_L)), + + 0x062 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_H)), + 0x063 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_H)), + + 0x064 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_X)), + 0x065 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND1CNT_X)), + + 0x068 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_L)), + 0x069 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_L)), + + 0x06c => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_H)), + 0x06d => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND2CNT_H)), + + 0x070 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_L)), + 0x071 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_L)), + + 0x072 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_H)), + 0x073 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_H)), + + 0x074 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_X)), + 0x075 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND3CNT_X)), + + 0x078 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_L)), + 0x079 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_L)), + + 0x07c => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_H)), + 0x07d => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUND4CNT_H)), + + 0x080 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_L)), + 0x081 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_L)), + + 0x082 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_H)), + 0x083 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_H)), + + 0x084 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_X)), + 0x085 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDCNT_X)), + + 0x088 => GetLowByte(_sound!.ReadRegister(Sound.Register.SOUNDBIAS)), + 0x089 => GetHighByte(_sound!.ReadRegister(Sound.Register.SOUNDBIAS)), + + 0x090 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_L)), + 0x091 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_L)), + + 0x092 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_H)), + 0x093 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM0_H)), + + 0x094 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_L)), + 0x095 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_L)), + + 0x096 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_H)), + 0x097 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM1_H)), + + 0x098 => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_L)), + 0x099 => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_L)), + + 0x09a => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_H)), + 0x09b => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM2_H)), + + 0x09c => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_L)), + 0x09d => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_L)), + + 0x09e => GetLowByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_H)), + 0x09f => GetHighByte(_sound!.ReadRegister(Sound.Register.WAVE_RAM3_H)), + + 0x0ba => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA0CNT_H)), + 0x0bb => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA0CNT_H)), + + 0x0c6 => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA1CNT_H)), + 0x0c7 => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA1CNT_H)), + + 0x0d2 => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA2CNT_H)), + 0x0d3 => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA2CNT_H)), + + 0x0de => GetLowByte(_dma!.ReadRegister(DMA.Register.DMA3CNT_H)), + 0x0df => GetHighByte(_dma!.ReadRegister(DMA.Register.DMA3CNT_H)), + + 0x100 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM0CNT_L)), + 0x101 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM0CNT_L)), + + 0x102 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM0CNT_H)), + 0x103 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM0CNT_H)), + + 0x104 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM1CNT_L)), + 0x105 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM1CNT_L)), + + 0x106 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM1CNT_H)), + 0x107 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM1CNT_H)), + + 0x108 => GetLowByte(_timer!.ReadRegister(Timer.Register.TM2CNT_L)), + 0x109 => GetHighByte(_timer!.ReadRegister(Timer.Register.TM2CNT_L)), + + 0x10a => GetLowByte(_timer!.ReadRegister(Timer.Register.TM2CNT_H)), + 0x10b => GetHighByte(_timer!.ReadRegister(Timer.Register.TM2CNT_H)), + + 0x10c => GetLowByte(_timer!.ReadRegister(Timer.Register.TM3CNT_L)), + 0x10d => GetHighByte(_timer!.ReadRegister(Timer.Register.TM3CNT_L)), + + 0x10e => GetLowByte(_timer!.ReadRegister(Timer.Register.TM3CNT_H)), + 0x10f => GetHighByte(_timer!.ReadRegister(Timer.Register.TM3CNT_H)), + + 0x120 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA0)), + 0x121 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA0)), + + 0x122 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA1)), + 0x123 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA1)), + + 0x124 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA2)), + 0x125 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA2)), + + 0x126 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA3)), + 0x127 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA3)), + + 0x128 => GetLowByte(_communication!.ReadRegister(Communication.Register.SIOCNT)), + 0x129 => GetHighByte(_communication!.ReadRegister(Communication.Register.SIOCNT)), + + 0x12a => GetLowByte(_communication!.ReadRegister(Communication.Register.SIODATA_SEND)), + 0x12b => GetHighByte(_communication!.ReadRegister(Communication.Register.SIODATA_SEND)), + + 0x130 => GetLowByte(_keyInput!.ReadRegister(KeyInput.Register.KEYINPUT)), + 0x131 => GetHighByte(_keyInput!.ReadRegister(KeyInput.Register.KEYINPUT)), + + 0x132 => GetLowByte(_keyInput!.ReadRegister(KeyInput.Register.KEYCNT)), + 0x133 => GetHighByte(_keyInput!.ReadRegister(KeyInput.Register.KEYCNT)), + + 0x134 => GetLowByte(_communication!.ReadRegister(Communication.Register.RCNT)), + 0x135 => GetHighByte(_communication!.ReadRegister(Communication.Register.RCNT)), + + 0x200 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IE)), + 0x201 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IE)), + + 0x202 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IF)), + 0x203 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IF)), + + 0x204 => GetLowByte(_systemControl!.ReadRegister(SystemControl.Register.WAITCNT)), + 0x205 => GetHighByte(_systemControl!.ReadRegister(SystemControl.Register.WAITCNT)), + + 0x208 => GetLowByte(_interruptControl!.ReadRegister(InterruptControl.Register.IME)), + 0x209 => GetHighByte(_interruptControl!.ReadRegister(InterruptControl.Register.IME)), + + 0x300 => GetLowByte(_systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0)), + 0x301 => GetHighByte(_systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0)), + + _ => 0, + }; + } + + // ROM wait state 0 + case 0x8: + case 0x9: + { + UInt32 offset = address - ROM_WaitState0_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadByte + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 1 + case 0xa: + case 0xb: + { + UInt32 offset = address - ROM_WaitState1_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadByte + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 2 + case 0xc: + case 0xd: + { + UInt32 offset = address - ROM_WaitState2_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadByte + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // SRAM/Flash + case 0xe: + return address switch + { + 0xe00_0000 => 0x62, + 0xe00_0001 => 0x13, + _ => 0, + }; + } + + return 0; + } + + internal UInt16 Read16(UInt32 address) + { + address &= 0x0fff_fffe; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_read16PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.ReadInt16 + return Unsafe.Read((Byte*)page + (address & 0x3ff)); + } + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + return _bios!.Read16(address); + + // IO and registers + case 0x4: + { + UInt32 offset = address - 0x400_0000; + + return offset switch + { + 0x000 => _video!.ReadRegister(Video.Register.DISPCNT), + 0x004 => _video!.ReadRegister(Video.Register.DISPSTAT), + 0x006 => _video!.ReadRegister(Video.Register.VCOUNT), + 0x008 => _video!.ReadRegister(Video.Register.BG0CNT), + 0x00a => _video!.ReadRegister(Video.Register.BG1CNT), + 0x00c => _video!.ReadRegister(Video.Register.BG2CNT), + 0x00e => _video!.ReadRegister(Video.Register.BG3CNT), + 0x048 => _video!.ReadRegister(Video.Register.WININ), + 0x04a => _video!.ReadRegister(Video.Register.WINOUT), + 0x050 => _video!.ReadRegister(Video.Register.BLDCNT), + 0x052 => _video!.ReadRegister(Video.Register.BLDALPHA), + 0x060 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_L), + 0x062 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_H), + 0x064 => _sound!.ReadRegister(Sound.Register.SOUND1CNT_X), + 0x068 => _sound!.ReadRegister(Sound.Register.SOUND2CNT_L), + 0x06c => _sound!.ReadRegister(Sound.Register.SOUND2CNT_H), + 0x070 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_L), + 0x072 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_H), + 0x074 => _sound!.ReadRegister(Sound.Register.SOUND3CNT_X), + 0x078 => _sound!.ReadRegister(Sound.Register.SOUND4CNT_L), + 0x07c => _sound!.ReadRegister(Sound.Register.SOUND4CNT_H), + 0x080 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_L), + 0x082 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_H), + 0x084 => _sound!.ReadRegister(Sound.Register.SOUNDCNT_X), + 0x088 => _sound!.ReadRegister(Sound.Register.SOUNDBIAS), + 0x090 => _sound!.ReadRegister(Sound.Register.WAVE_RAM0_L), + 0x092 => _sound!.ReadRegister(Sound.Register.WAVE_RAM0_H), + 0x094 => _sound!.ReadRegister(Sound.Register.WAVE_RAM1_L), + 0x096 => _sound!.ReadRegister(Sound.Register.WAVE_RAM1_H), + 0x098 => _sound!.ReadRegister(Sound.Register.WAVE_RAM2_L), + 0x09a => _sound!.ReadRegister(Sound.Register.WAVE_RAM2_H), + 0x09c => _sound!.ReadRegister(Sound.Register.WAVE_RAM3_L), + 0x09e => _sound!.ReadRegister(Sound.Register.WAVE_RAM3_H), + 0x0ba => _dma!.ReadRegister(DMA.Register.DMA0CNT_H), + 0x0c6 => _dma!.ReadRegister(DMA.Register.DMA1CNT_H), + 0x0d2 => _dma!.ReadRegister(DMA.Register.DMA2CNT_H), + 0x0de => _dma!.ReadRegister(DMA.Register.DMA3CNT_H), + 0x100 => _timer!.ReadRegister(Timer.Register.TM0CNT_L), + 0x102 => _timer!.ReadRegister(Timer.Register.TM0CNT_H), + 0x104 => _timer!.ReadRegister(Timer.Register.TM1CNT_L), + 0x106 => _timer!.ReadRegister(Timer.Register.TM1CNT_H), + 0x108 => _timer!.ReadRegister(Timer.Register.TM2CNT_L), + 0x10a => _timer!.ReadRegister(Timer.Register.TM2CNT_H), + 0x10c => _timer!.ReadRegister(Timer.Register.TM3CNT_L), + 0x10e => _timer!.ReadRegister(Timer.Register.TM3CNT_H), + 0x120 => _communication!.ReadRegister(Communication.Register.SIODATA0), + 0x122 => _communication!.ReadRegister(Communication.Register.SIODATA1), + 0x124 => _communication!.ReadRegister(Communication.Register.SIODATA2), + 0x126 => _communication!.ReadRegister(Communication.Register.SIODATA3), + 0x128 => _communication!.ReadRegister(Communication.Register.SIOCNT), + 0x12a => _communication!.ReadRegister(Communication.Register.SIODATA_SEND), + 0x130 => _keyInput!.ReadRegister(KeyInput.Register.KEYINPUT), + 0x132 => _keyInput!.ReadRegister(KeyInput.Register.KEYCNT), + 0x134 => _communication!.ReadRegister(Communication.Register.RCNT), + 0x200 => _interruptControl!.ReadRegister(InterruptControl.Register.IE), + 0x202 => _interruptControl!.ReadRegister(InterruptControl.Register.IF), + 0x204 => _systemControl!.ReadRegister(SystemControl.Register.WAITCNT), + 0x208 => _interruptControl!.ReadRegister(InterruptControl.Register.IME), + 0x300 => _systemControl!.ReadRegister(SystemControl.Register.SYSCNT_UND0), + _ => 0, + }; + } + + // ROM wait state 0 + case 0x8: + case 0x9: + { + UInt32 offset = address - ROM_WaitState0_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt16 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 1 + case 0xa: + case 0xb: + { + UInt32 offset = address - ROM_WaitState1_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt16 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 2 + case 0xc: + case 0xd: + { + UInt32 offset = address - ROM_WaitState2_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt16 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + } + + return 0; + } + + internal UInt32 Read32(UInt32 address) + { + address &= 0x0fff_fffc; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_read32PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.ReadInt32 + return Unsafe.Read((Byte*)page + (address & 0x3ff)); + } + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + return _bios!.Read32(address); + + // IO and registers + case 0x4: + { + UInt32 offset = address - 0x400_0000; + + return offset switch + { + 0x000 => _video!.ReadRegister(Video.Register.DISPCNT), + 0x004 => (UInt32)((_video!.ReadRegister(Video.Register.VCOUNT) << 16) | _video.ReadRegister(Video.Register.DISPSTAT)), + 0x008 => (UInt32)((_video!.ReadRegister(Video.Register.BG1CNT) << 16) | _video.ReadRegister(Video.Register.BG0CNT)), + 0x00c => (UInt32)((_video!.ReadRegister(Video.Register.BG3CNT) << 16) | _video.ReadRegister(Video.Register.BG2CNT)), + 0x0b8 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA0CNT_H) << 16), + 0x0c4 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA1CNT_H) << 16), + 0x0d0 => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA2CNT_H) << 16), + 0x0dc => (UInt32)(_dma!.ReadRegister(DMA.Register.DMA3CNT_H) << 16), + 0x150 => (UInt32)((_communication.ReadRegister(Communication.Register.JOY_RECV_H) << 16) | _communication.ReadRegister(Communication.Register.JOY_RECV_L)), + 0x200 => (UInt32)((_interruptControl!.ReadRegister(InterruptControl.Register.IF) << 16) | _interruptControl.ReadRegister(InterruptControl.Register.IE)), + 0x208 => _interruptControl!.ReadRegister(InterruptControl.Register.IME), + _ => 0, + }; + } + + // ROM wait state 0 + case 0x8: + case 0x9: + { + UInt32 offset = address - ROM_WaitState0_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt32 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 1 + case 0xa: + case 0xb: + { + UInt32 offset = address - ROM_WaitState1_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt32 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + + // ROM wait state 2 + case 0xc: + case 0xd: + { + UInt32 offset = address - ROM_WaitState2_StartAddress; + + if (offset < _romSize) + { + unsafe + { + // much faster than Marshal.ReadInt32 + return Unsafe.Read((Byte*)_rom + offset); + } + } + } + break; + } + + return 0; + } + + internal enum RegisterWriteMode + { + LowByte, + HighByte, + HalfWord + } + + internal static void WriteRegisterHelper(ref UInt16 registerValue, UInt16 value, RegisterWriteMode mode) + { + switch (mode) + { + case RegisterWriteMode.LowByte: + registerValue = (UInt16)((registerValue & 0xff00) | value); + break; + case RegisterWriteMode.HighByte: + registerValue = (UInt16)((registerValue & 0x00ff) | (value << 8)); + break; + case RegisterWriteMode.HalfWord: + registerValue = value; + break; + } + } + + internal void Write8(UInt32 address, Byte value) + { + address &= 0x0fff_ffff; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_write8PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.WriteByte + Unsafe.Write((Byte*)page + (address & 0x3ff), value); + } + + return; + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + break; + + // IO and registers + case 0x4: + { + UInt32 offset = address - 0x400_0000; + + switch (offset) + { + case 0x000: + _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.LowByte); + break; + case 0x001: + _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x004: + _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.LowByte); + break; + case 0x005: + _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.HighByte); + break; + + case 0x008: + _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.LowByte); + break; + case 0x009: + _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.HighByte); + break; + + case 0x00a: + _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.LowByte); + break; + case 0x00b: + _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.HighByte); + break; + + case 0x00c: + _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.LowByte); + break; + case 0x00d: + _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.HighByte); + break; + + case 0x00e: + _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.LowByte); + break; + case 0x00f: + _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.HighByte); + break; + + case 0x010: + _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.LowByte); + break; + case 0x011: + _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x012: + _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.LowByte); + break; + case 0x013: + _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x014: + _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.LowByte); + break; + case 0x015: + _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x016: + _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.LowByte); + break; + case 0x017: + _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x018: + _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.LowByte); + break; + case 0x019: + _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x01a: + _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.LowByte); + break; + case 0x01b: + _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x01c: + _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.LowByte); + break; + case 0x01d: + _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x01e: + _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.LowByte); + break; + case 0x01f: + _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.HighByte); + break; + + case 0x040: + _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.LowByte); + break; + case 0x041: + _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.HighByte); + break; + + case 0x042: + _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.LowByte); + break; + case 0x043: + _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.HighByte); + break; + + case 0x044: + _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.LowByte); + break; + case 0x045: + _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.HighByte); + break; + + case 0x046: + _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.LowByte); + break; + case 0x047: + _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.HighByte); + break; + + case 0x048: + _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.LowByte); + break; + case 0x049: + _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.HighByte); + break; + + case 0x04a: + _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.LowByte); + break; + case 0x04b: + _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.HighByte); + break; + + case 0x04c: + _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.LowByte); + break; + case 0x04d: + _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.HighByte); + break; + + case 0x050: + _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.LowByte); + break; + case 0x051: + _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x052: + _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.LowByte); + break; + case 0x053: + _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.HighByte); + break; + + case 0x054: + _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.LowByte); + break; + case 0x055: + _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.HighByte); + break; + + case 0x060: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x061: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x062: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x063: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x064: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.LowByte); + break; + case 0x065: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.HighByte); + break; + + case 0x068: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x069: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x06c: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x06d: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x070: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x071: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x072: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x073: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x074: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.LowByte); + break; + case 0x075: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.HighByte); + break; + + case 0x078: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x079: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x07c: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x07d: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x080: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x081: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x082: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x083: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x084: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.LowByte); + break; + case 0x085: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.HighByte); + break; + + case 0x088: + _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.LowByte); + break; + case 0x089: + _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.HighByte); + break; + + case 0x090: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.LowByte); + break; + case 0x091: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.HighByte); + break; + + case 0x092: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.LowByte); + break; + case 0x093: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.HighByte); + break; + + case 0x094: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.LowByte); + break; + case 0x095: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.HighByte); + break; + + case 0x096: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.LowByte); + break; + case 0x097: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.HighByte); + break; + + case 0x098: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.LowByte); + break; + case 0x099: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.HighByte); + break; + + case 0x09a: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.LowByte); + break; + case 0x09b: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.HighByte); + break; + + case 0x09c: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.LowByte); + break; + case 0x09d: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.HighByte); + break; + + case 0x09e: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.LowByte); + break; + case 0x09f: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0b0: + _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0b1: + _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0b2: + _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0b3: + _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0b4: + _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0b5: + _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0b6: + _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0b7: + _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0b8: + _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x0b9: + _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0ba: + _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x0bb: + _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0bc: + _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0bd: + _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0be: + _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0bf: + _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0c0: + _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0c1: + _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0c2: + _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0c3: + _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0c4: + _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x0c5: + _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0c6: + _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x0c7: + _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0c8: + _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0c9: + _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0ca: + _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0cb: + _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0cc: + _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0cd: + _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0ce: + _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0cf: + _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0d0: + _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x0d1: + _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0d2: + _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x0d3: + _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0d4: + _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0d5: + _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0d6: + _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0d7: + _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0d8: + _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.LowByte); + break; + case 0x0d9: + _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0da: + _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.LowByte); + break; + case 0x0db: + _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.HighByte); + break; + + case 0x0dc: + _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x0dd: + _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x0de: + _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x0df: + _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x100: + _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x101: + _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x102: + _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x103: + _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x104: + _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x105: + _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x106: + _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x107: + _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x108: + _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x109: + _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x10a: + _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x10b: + _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x10c: + _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.LowByte); + break; + case 0x10d: + _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.HighByte); + break; + + case 0x10e: + _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.LowByte); + break; + case 0x10f: + _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.HighByte); + break; + + case 0x120: + _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.LowByte); + break; + case 0x121: + _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.HighByte); + break; + + case 0x122: + _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.LowByte); + break; + case 0x123: + _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.HighByte); + break; + + case 0x124: + _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.LowByte); + break; + case 0x125: + _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.HighByte); + break; + + case 0x126: + _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.LowByte); + break; + case 0x127: + _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.HighByte); + break; + + case 0x128: + _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.LowByte); + break; + case 0x129: + _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x12a: + _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.LowByte); + break; + case 0x12b: + _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.HighByte); + break; + + case 0x130: + case 0x131: + // KEYINPUT (read-only) + break; + + case 0x132: + _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.LowByte); + break; + case 0x133: + _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x134: + _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.LowByte); + break; + case 0x135: + _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x140: + _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.LowByte); + break; + case 0x141: + _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x200: + _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.LowByte); + break; + case 0x201: + _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.HighByte); + break; + + case 0x202: + _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.LowByte); + break; + case 0x203: + _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.HighByte); + break; + + case 0x204: + _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.LowByte); + break; + case 0x205: + _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.HighByte); + break; + + case 0x208: + _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.LowByte); + break; + case 0x209: + _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.HighByte); + break; + + case 0x300: + _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.LowByte); + break; + case 0x301: + _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.HighByte); + break; + + case 0x410: + // undocumented + break; + + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + break; + + // Palette RAM + case 0x5: + _video!.Write8_PaletteRAM(address, value); + break; + + // VRAM + case 0x6: + _video!.Write8_VRAM(address, value); + break; + + // OAM + case 0x7: + break; + + // ROM + case 0x8: + case 0x9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + break; + + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + + internal void Write16(UInt32 address, UInt16 value) + { + address &= 0x0fff_fffe; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_write16PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.WriteInt16 + Unsafe.Write((Byte*)page + (address & 0x3ff), value); + } + + return; + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + break; + + // IO and registers + case 0x4: + { + UInt32 offset = address - 0x400_0000; + + switch (offset) + { + case 0x000: + _video!.WriteRegister(Video.Register.DISPCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x004: + _video!.WriteRegister(Video.Register.DISPSTAT, value, RegisterWriteMode.HalfWord); + break; + case 0x008: + _video!.WriteRegister(Video.Register.BG0CNT, value, RegisterWriteMode.HalfWord); + break; + case 0x00a: + _video!.WriteRegister(Video.Register.BG1CNT, value, RegisterWriteMode.HalfWord); + break; + case 0x00c: + _video!.WriteRegister(Video.Register.BG2CNT, value, RegisterWriteMode.HalfWord); + break; + case 0x00e: + _video!.WriteRegister(Video.Register.BG3CNT, value, RegisterWriteMode.HalfWord); + break; + case 0x010: + _video!.WriteRegister(Video.Register.BG0HOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x012: + _video!.WriteRegister(Video.Register.BG0VOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x014: + _video!.WriteRegister(Video.Register.BG1HOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x016: + _video!.WriteRegister(Video.Register.BG1VOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x018: + _video!.WriteRegister(Video.Register.BG2HOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x01a: + _video!.WriteRegister(Video.Register.BG2VOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x01c: + _video!.WriteRegister(Video.Register.BG3HOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x01e: + _video!.WriteRegister(Video.Register.BG3VOFS, value, RegisterWriteMode.HalfWord); + break; + case 0x020: + _video!.WriteRegister(Video.Register.BG2PA, value, RegisterWriteMode.HalfWord); + break; + case 0x022: + _video!.WriteRegister(Video.Register.BG2PB, value, RegisterWriteMode.HalfWord); + break; + case 0x024: + _video!.WriteRegister(Video.Register.BG2PC, value, RegisterWriteMode.HalfWord); + break; + case 0x026: + _video!.WriteRegister(Video.Register.BG2PD, value, RegisterWriteMode.HalfWord); + break; + case 0x028: + _video!.WriteRegister(Video.Register.BG2X_L, value, RegisterWriteMode.HalfWord); + break; + case 0x02a: + _video!.WriteRegister(Video.Register.BG2X_H, value, RegisterWriteMode.HalfWord); + break; + case 0x02c: + _video!.WriteRegister(Video.Register.BG2Y_L, value, RegisterWriteMode.HalfWord); + break; + case 0x02e: + _video!.WriteRegister(Video.Register.BG2Y_H, value, RegisterWriteMode.HalfWord); + break; + case 0x030: + _video!.WriteRegister(Video.Register.BG3PA, value, RegisterWriteMode.HalfWord); + break; + case 0x032: + _video!.WriteRegister(Video.Register.BG3PB, value, RegisterWriteMode.HalfWord); + break; + case 0x034: + _video!.WriteRegister(Video.Register.BG3PC, value, RegisterWriteMode.HalfWord); + break; + case 0x036: + _video!.WriteRegister(Video.Register.BG3PD, value, RegisterWriteMode.HalfWord); + break; + case 0x038: + _video!.WriteRegister(Video.Register.BG3X_L, value, RegisterWriteMode.HalfWord); + break; + case 0x03a: + _video!.WriteRegister(Video.Register.BG3X_H, value, RegisterWriteMode.HalfWord); + break; + case 0x03c: + _video!.WriteRegister(Video.Register.BG3Y_L, value, RegisterWriteMode.HalfWord); + break; + case 0x03e: + _video!.WriteRegister(Video.Register.BG3Y_H, value, RegisterWriteMode.HalfWord); + break; + case 0x040: + _video!.WriteRegister(Video.Register.WIN0H, value, RegisterWriteMode.HalfWord); + break; + case 0x042: + _video!.WriteRegister(Video.Register.WIN1H, value, RegisterWriteMode.HalfWord); + break; + case 0x044: + _video!.WriteRegister(Video.Register.WIN0V, value, RegisterWriteMode.HalfWord); + break; + case 0x046: + _video!.WriteRegister(Video.Register.WIN1V, value, RegisterWriteMode.HalfWord); + break; + case 0x048: + _video!.WriteRegister(Video.Register.WININ, value, RegisterWriteMode.HalfWord); + break; + case 0x04a: + _video!.WriteRegister(Video.Register.WINOUT, value, RegisterWriteMode.HalfWord); + break; + case 0x04c: + _video!.WriteRegister(Video.Register.MOSAIC, value, RegisterWriteMode.HalfWord); + break; + case 0x050: + _video!.WriteRegister(Video.Register.BLDCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x052: + _video!.WriteRegister(Video.Register.BLDALPHA, value, RegisterWriteMode.HalfWord); + break; + case 0x054: + _video!.WriteRegister(Video.Register.BLDY, value, RegisterWriteMode.HalfWord); + break; + case 0x060: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x062: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x064: + _sound!.WriteRegister(Sound.Register.SOUND1CNT_X, value, RegisterWriteMode.HalfWord); + break; + case 0x068: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x06c: + _sound!.WriteRegister(Sound.Register.SOUND2CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x070: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x072: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x074: + _sound!.WriteRegister(Sound.Register.SOUND3CNT_X, value, RegisterWriteMode.HalfWord); + break; + case 0x078: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x07c: + _sound!.WriteRegister(Sound.Register.SOUND4CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x080: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x082: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x084: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_X, value, RegisterWriteMode.HalfWord); + break; + case 0x088: + _sound!.WriteRegister(Sound.Register.SOUNDBIAS, value, RegisterWriteMode.HalfWord); + break; + case 0x090: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, value, RegisterWriteMode.HalfWord); + break; + case 0x092: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_H, value, RegisterWriteMode.HalfWord); + break; + case 0x094: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, value, RegisterWriteMode.HalfWord); + break; + case 0x096: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_H, value, RegisterWriteMode.HalfWord); + break; + case 0x098: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, value, RegisterWriteMode.HalfWord); + break; + case 0x09a: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_H, value, RegisterWriteMode.HalfWord); + break; + case 0x09c: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, value, RegisterWriteMode.HalfWord); + break; + case 0x09e: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0b0: + _dma!.WriteRegister(DMA.Register.DMA0SAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0b2: + _dma!.WriteRegister(DMA.Register.DMA0SAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0b4: + _dma!.WriteRegister(DMA.Register.DMA0DAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0b6: + _dma!.WriteRegister(DMA.Register.DMA0DAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0b8: + _dma!.WriteRegister(DMA.Register.DMA0CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0ba: + _dma!.WriteRegister(DMA.Register.DMA0CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0bc: + _dma!.WriteRegister(DMA.Register.DMA1SAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0be: + _dma!.WriteRegister(DMA.Register.DMA1SAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0c0: + _dma!.WriteRegister(DMA.Register.DMA1DAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0c2: + _dma!.WriteRegister(DMA.Register.DMA1DAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0c4: + _dma!.WriteRegister(DMA.Register.DMA1CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0c6: + _dma!.WriteRegister(DMA.Register.DMA1CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0c8: + _dma!.WriteRegister(DMA.Register.DMA2SAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0ca: + _dma!.WriteRegister(DMA.Register.DMA2SAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0cc: + _dma!.WriteRegister(DMA.Register.DMA2DAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0ce: + _dma!.WriteRegister(DMA.Register.DMA2DAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0d0: + _dma!.WriteRegister(DMA.Register.DMA2CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0d2: + _dma!.WriteRegister(DMA.Register.DMA2CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0d4: + _dma!.WriteRegister(DMA.Register.DMA3SAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0d6: + _dma!.WriteRegister(DMA.Register.DMA3SAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0d8: + _dma!.WriteRegister(DMA.Register.DMA3DAD_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0da: + _dma!.WriteRegister(DMA.Register.DMA3DAD_H, value, RegisterWriteMode.HalfWord); + break; + case 0x0dc: + _dma!.WriteRegister(DMA.Register.DMA3CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x0de: + _dma!.WriteRegister(DMA.Register.DMA3CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x100: + _timer!.WriteRegister(Timer.Register.TM0CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x102: + _timer!.WriteRegister(Timer.Register.TM0CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x104: + _timer!.WriteRegister(Timer.Register.TM1CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x106: + _timer!.WriteRegister(Timer.Register.TM1CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x108: + _timer!.WriteRegister(Timer.Register.TM2CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x10a: + _timer!.WriteRegister(Timer.Register.TM2CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x10c: + _timer!.WriteRegister(Timer.Register.TM3CNT_L, value, RegisterWriteMode.HalfWord); + break; + case 0x10e: + _timer!.WriteRegister(Timer.Register.TM3CNT_H, value, RegisterWriteMode.HalfWord); + break; + case 0x120: + _communication!.WriteRegister(Communication.Register.SIODATA0, value, RegisterWriteMode.HalfWord); + break; + case 0x122: + _communication!.WriteRegister(Communication.Register.SIODATA1, value, RegisterWriteMode.HalfWord); + break; + case 0x124: + _communication!.WriteRegister(Communication.Register.SIODATA2, value, RegisterWriteMode.HalfWord); + break; + case 0x126: + _communication!.WriteRegister(Communication.Register.SIODATA3, value, RegisterWriteMode.HalfWord); + break; + case 0x128: + _communication!.WriteRegister(Communication.Register.SIOCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x12a: + _communication!.WriteRegister(Communication.Register.SIODATA_SEND, value, RegisterWriteMode.HalfWord); + break; + case 0x130: + // KEYINPUT (read-only) + break; + case 0x132: + _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x134: + _communication!.WriteRegister(Communication.Register.RCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x140: + _communication!.WriteRegister(Communication.Register.JOYCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x158: + _communication!.WriteRegister(Communication.Register.JOYSTAT, value, RegisterWriteMode.HalfWord); + break; + case 0x200: + _interruptControl!.WriteRegister(InterruptControl.Register.IE, value, RegisterWriteMode.HalfWord); + break; + case 0x202: + _interruptControl!.WriteRegister(InterruptControl.Register.IF, value, RegisterWriteMode.HalfWord); + break; + case 0x204: + _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, value, RegisterWriteMode.HalfWord); + break; + case 0x208: + _interruptControl!.WriteRegister(InterruptControl.Register.IME, value, RegisterWriteMode.HalfWord); + break; + case 0x300: + _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, value, RegisterWriteMode.HalfWord); + break; + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + break; + + // ROM + case 0x8: + case 0x9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + break; + + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + + internal void Write32(UInt32 address, UInt32 value) + { + address &= 0x0fff_fffc; + + IntPtr page = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_write32PageTable), address >> 10); + + if (page != IntPtr.Zero) + { + unsafe + { + // much faster than Marshal.WriteInt32 + Unsafe.Write((Byte*)page + (address & 0x3ff), value); + } + + return; + } + + // page fault + switch (address >> 24) + { + // BIOS + case 0x0: + case 0x1: + break; + + // IO and registers + case 0x4: + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static UInt16 GetLowHalfword(UInt32 value) => (UInt16)value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static UInt16 GetHighHalfword(UInt32 value) => (UInt16)(value >> 16); + + UInt32 offset = address - 0x400_0000; + + switch (offset) + { + case 0x000: + _video!.WriteRegister(Video.Register.DISPCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are undocumented (green swap register) + break; + case 0x004: + _video!.WriteRegister(Video.Register.DISPSTAT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x008: + _video!.WriteRegister(Video.Register.BG0CNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG1CNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x00c: + _video!.WriteRegister(Video.Register.BG2CNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3CNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x010: + _video!.WriteRegister(Video.Register.BG0HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG0VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x014: + _video!.WriteRegister(Video.Register.BG1HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG1VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x018: + _video!.WriteRegister(Video.Register.BG2HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG2VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x01c: + _video!.WriteRegister(Video.Register.BG3HOFS, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3VOFS, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x020: + _video!.WriteRegister(Video.Register.BG2PA, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG2PB, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x024: + _video!.WriteRegister(Video.Register.BG2PC, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG2PD, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x028: + _video!.WriteRegister(Video.Register.BG2X_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG2X_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x02c: + _video!.WriteRegister(Video.Register.BG2Y_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG2Y_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x030: + _video!.WriteRegister(Video.Register.BG3PA, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3PB, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x034: + _video!.WriteRegister(Video.Register.BG3PC, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3PD, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x038: + _video!.WriteRegister(Video.Register.BG3X_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3X_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x03c: + _video!.WriteRegister(Video.Register.BG3Y_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BG3Y_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x040: + _video!.WriteRegister(Video.Register.WIN0H, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.WIN1H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x044: + _video!.WriteRegister(Video.Register.WIN0V, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.WIN1V, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x048: + _video!.WriteRegister(Video.Register.WININ, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.WINOUT, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x04c: + _video!.WriteRegister(Video.Register.MOSAIC, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x050: + _video!.WriteRegister(Video.Register.BLDCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _video!.WriteRegister(Video.Register.BLDALPHA, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x054: + _video!.WriteRegister(Video.Register.BLDY, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x58: + case 0x5c: + // unused + break; + case 0x080: + _sound!.WriteRegister(Sound.Register.SOUNDCNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.SOUNDCNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x090: + _sound!.WriteRegister(Sound.Register.WAVE_RAM0_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.WAVE_RAM0_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x094: + _sound!.WriteRegister(Sound.Register.WAVE_RAM1_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.WAVE_RAM1_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x098: + _sound!.WriteRegister(Sound.Register.WAVE_RAM2_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.WAVE_RAM2_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x09c: + _sound!.WriteRegister(Sound.Register.WAVE_RAM3_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.WAVE_RAM3_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0a0: + _sound!.WriteRegister(Sound.Register.FIFO_A_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.FIFO_A_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0a4: + _sound!.WriteRegister(Sound.Register.FIFO_B_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _sound.WriteRegister(Sound.Register.FIFO_B_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0a8: + case 0x0ac: + // unused + break; + case 0x0b0: + _dma!.WriteRegister(DMA.Register.DMA0SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA0SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0b4: + _dma!.WriteRegister(DMA.Register.DMA0DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA0DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0b8: + _dma!.WriteRegister(DMA.Register.DMA0CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA0CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0bc: + _dma!.WriteRegister(DMA.Register.DMA1SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA1SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0c0: + _dma!.WriteRegister(DMA.Register.DMA1DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA1DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0c4: + _dma!.WriteRegister(DMA.Register.DMA1CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA1CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0c8: + _dma!.WriteRegister(DMA.Register.DMA2SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA2SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0cc: + _dma!.WriteRegister(DMA.Register.DMA2DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA2DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0d0: + _dma!.WriteRegister(DMA.Register.DMA2CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA2CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0d4: + _dma!.WriteRegister(DMA.Register.DMA3SAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA3SAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0d8: + _dma!.WriteRegister(DMA.Register.DMA3DAD_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA3DAD_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0dc: + _dma!.WriteRegister(DMA.Register.DMA3CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _dma.WriteRegister(DMA.Register.DMA3CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x0e0: + case 0x0e4: + case 0x0e8: + case 0x0ec: + case 0x0f0: + case 0x0f4: + case 0x0f8: + case 0x0fc: + // unused + break; + case 0x100: + _timer!.WriteRegister(Timer.Register.TM0CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _timer!.WriteRegister(Timer.Register.TM0CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x104: + _timer!.WriteRegister(Timer.Register.TM1CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _timer!.WriteRegister(Timer.Register.TM1CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x108: + _timer!.WriteRegister(Timer.Register.TM2CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _timer!.WriteRegister(Timer.Register.TM2CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x10c: + _timer!.WriteRegister(Timer.Register.TM3CNT_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _timer!.WriteRegister(Timer.Register.TM3CNT_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x110: + case 0x114: + case 0x118: + case 0x11c: + // unused + break; + case 0x120: + _communication!.WriteRegister(Communication.Register.SIODATA0, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _communication.WriteRegister(Communication.Register.SIODATA1, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x124: + _communication!.WriteRegister(Communication.Register.SIODATA2, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _communication.WriteRegister(Communication.Register.SIODATA3, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x128: + _communication!.WriteRegister(Communication.Register.SIOCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _communication.WriteRegister(Communication.Register.SIODATA_SEND, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x12c: + // unused + break; + case 0x130: + // 16 lower bits are read-only (KEYINPUT) + _keyInput!.WriteRegister(KeyInput.Register.KEYCNT, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x140: + _communication!.WriteRegister(Communication.Register.JOYCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x144: + case 0x148: + case 0x14c: + // unused + break; + case 0x150: + _communication!.WriteRegister(Communication.Register.JOY_RECV_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _communication.WriteRegister(Communication.Register.JOY_RECV_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x154: + _communication!.WriteRegister(Communication.Register.JOY_TRANS_L, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _communication.WriteRegister(Communication.Register.JOY_TRANS_H, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x158: + _communication!.WriteRegister(Communication.Register.JOYSTAT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x15c: + // unused + break; + case 0x200: + _interruptControl!.WriteRegister(InterruptControl.Register.IE, GetLowHalfword(value), RegisterWriteMode.HalfWord); + _interruptControl!.WriteRegister(InterruptControl.Register.IF, GetHighHalfword(value), RegisterWriteMode.HalfWord); + break; + case 0x204: + _systemControl!.WriteRegister(SystemControl.Register.WAITCNT, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x208: + _interruptControl!.WriteRegister(InterruptControl.Register.IME, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x20c: + case 0x210: + case 0x214: + case 0x218: + case 0x21c: + // unused + break; + case 0x300: + _systemControl!.WriteRegister(SystemControl.Register.SYSCNT_UND0, GetLowHalfword(value), RegisterWriteMode.HalfWord); + // 16 upper bits are unused + break; + case 0x800: + // 16 lower bits are undocumented (internal memory control) + // 16 upper bits are unused + break; + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + break; + + // ROM + case 0x8: + case 0x9: + case 0xa: + case 0xb: + case 0xc: + case 0xd: + break; + + default: + Console.WriteLine($"[Iris.GBA.Memory] Unhandled write to address 0x{address:x8}"); + break; + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/Memory.cs.meta b/Assets/Iris/Iris.GBA/Memory.cs.meta new file mode 100644 index 0000000..640d07a --- /dev/null +++ b/Assets/Iris/Iris.GBA/Memory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c63b68705a3d713459aaacd1a2506ed8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/Sound.cs b/Assets/Iris/Iris.GBA/Sound.cs new file mode 100644 index 0000000..9c8a784 --- /dev/null +++ b/Assets/Iris/Iris.GBA/Sound.cs @@ -0,0 +1,348 @@ +namespace Iris.GBA +{ + internal sealed class Sound + { + internal enum Register + { + SOUND1CNT_L, + SOUND1CNT_H, + SOUND1CNT_X, + + SOUND2CNT_L, + SOUND2CNT_H, + + SOUND3CNT_L, + SOUND3CNT_H, + SOUND3CNT_X, + + SOUND4CNT_L, + SOUND4CNT_H, + + SOUNDCNT_L, + SOUNDCNT_H, + SOUNDCNT_X, + + SOUNDBIAS, + + WAVE_RAM0_L, + WAVE_RAM0_H, + + WAVE_RAM1_L, + WAVE_RAM1_H, + + WAVE_RAM2_L, + WAVE_RAM2_H, + + WAVE_RAM3_L, + WAVE_RAM3_H, + + FIFO_A_L, + FIFO_A_H, + + FIFO_B_L, + FIFO_B_H + } + + private UInt16 _SOUND1CNT_L; + private UInt16 _SOUND1CNT_H; + private UInt16 _SOUND1CNT_X; + + private UInt16 _SOUND2CNT_L; + private UInt16 _SOUND2CNT_H; + + private UInt16 _SOUND3CNT_L; + private UInt16 _SOUND3CNT_H; + private UInt16 _SOUND3CNT_X; + + private UInt16 _SOUND4CNT_L; + private UInt16 _SOUND4CNT_H; + + private UInt16 _SOUNDCNT_L; + private UInt16 _SOUNDCNT_H; + private UInt16 _SOUNDCNT_X; + + private UInt16 _SOUNDBIAS; + + private UInt16 _WAVE_RAM0_L; + private UInt16 _WAVE_RAM0_H; + + private UInt16 _WAVE_RAM1_L; + private UInt16 _WAVE_RAM1_H; + + private UInt16 _WAVE_RAM2_L; + private UInt16 _WAVE_RAM2_H; + + private UInt16 _WAVE_RAM3_L; + private UInt16 _WAVE_RAM3_H; + + private UInt16 _FIFO_A_L; + private UInt16 _FIFO_A_H; + + private UInt16 _FIFO_B_L; + private UInt16 _FIFO_B_H; + + internal void ResetState() + { + _SOUND1CNT_L = 0; + _SOUND1CNT_H = 0; + _SOUND1CNT_X = 0; + + _SOUND2CNT_L = 0; + _SOUND2CNT_H = 0; + + _SOUND3CNT_L = 0; + _SOUND3CNT_H = 0; + _SOUND3CNT_X = 0; + + _SOUND4CNT_L = 0; + _SOUND4CNT_H = 0; + + _SOUNDCNT_L = 0; + _SOUNDCNT_H = 0; + _SOUNDCNT_X = 0; + + _SOUNDBIAS = 0; + + _WAVE_RAM0_L = 0; + _WAVE_RAM0_H = 0; + + _WAVE_RAM1_L = 0; + _WAVE_RAM1_H = 0; + + _WAVE_RAM2_L = 0; + _WAVE_RAM2_H = 0; + + _WAVE_RAM3_L = 0; + _WAVE_RAM3_H = 0; + + _FIFO_A_L = 0; + _FIFO_A_H = 0; + + _FIFO_B_L = 0; + _FIFO_B_H = 0; + } + + internal void LoadState(BinaryReader reader) + { + _SOUND1CNT_L = reader.ReadUInt16(); + _SOUND1CNT_H = reader.ReadUInt16(); + _SOUND1CNT_X = reader.ReadUInt16(); + + _SOUND2CNT_L = reader.ReadUInt16(); + _SOUND2CNT_H = reader.ReadUInt16(); + + _SOUND3CNT_L = reader.ReadUInt16(); + _SOUND3CNT_H = reader.ReadUInt16(); + _SOUND3CNT_X = reader.ReadUInt16(); + + _SOUND4CNT_L = reader.ReadUInt16(); + _SOUND4CNT_H = reader.ReadUInt16(); + + _SOUNDCNT_L = reader.ReadUInt16(); + _SOUNDCNT_H = reader.ReadUInt16(); + _SOUNDCNT_X = reader.ReadUInt16(); + + _SOUNDBIAS = reader.ReadUInt16(); + + _WAVE_RAM0_L = reader.ReadUInt16(); + _WAVE_RAM0_H = reader.ReadUInt16(); + + _WAVE_RAM1_L = reader.ReadUInt16(); + _WAVE_RAM1_H = reader.ReadUInt16(); + + _WAVE_RAM2_L = reader.ReadUInt16(); + _WAVE_RAM2_H = reader.ReadUInt16(); + + _WAVE_RAM3_L = reader.ReadUInt16(); + _WAVE_RAM3_H = reader.ReadUInt16(); + + _FIFO_A_L = reader.ReadUInt16(); + _FIFO_A_H = reader.ReadUInt16(); + + _FIFO_B_L = reader.ReadUInt16(); + _FIFO_B_H = reader.ReadUInt16(); + } + + internal void SaveState(BinaryWriter writer) + { + writer.Write(_SOUND1CNT_L); + writer.Write(_SOUND1CNT_H); + writer.Write(_SOUND1CNT_X); + + writer.Write(_SOUND2CNT_L); + writer.Write(_SOUND2CNT_H); + + writer.Write(_SOUND3CNT_L); + writer.Write(_SOUND3CNT_H); + writer.Write(_SOUND3CNT_X); + + writer.Write(_SOUND4CNT_L); + writer.Write(_SOUND4CNT_H); + + writer.Write(_SOUNDCNT_L); + writer.Write(_SOUNDCNT_H); + writer.Write(_SOUNDCNT_X); + + writer.Write(_SOUNDBIAS); + + writer.Write(_WAVE_RAM0_L); + writer.Write(_WAVE_RAM0_H); + + writer.Write(_WAVE_RAM1_L); + writer.Write(_WAVE_RAM1_H); + + writer.Write(_WAVE_RAM2_L); + writer.Write(_WAVE_RAM2_H); + + writer.Write(_WAVE_RAM3_L); + writer.Write(_WAVE_RAM3_H); + + writer.Write(_FIFO_A_L); + writer.Write(_FIFO_A_H); + + writer.Write(_FIFO_B_L); + writer.Write(_FIFO_B_H); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.SOUND1CNT_L => _SOUND1CNT_L, + Register.SOUND1CNT_H => _SOUND1CNT_H, + Register.SOUND1CNT_X => _SOUND1CNT_X, + + Register.SOUND2CNT_L => _SOUND2CNT_L, + Register.SOUND2CNT_H => _SOUND2CNT_H, + + Register.SOUND3CNT_L => _SOUND3CNT_L, + Register.SOUND3CNT_H => _SOUND3CNT_H, + Register.SOUND3CNT_X => _SOUND3CNT_X, + + Register.SOUND4CNT_L => _SOUND4CNT_L, + Register.SOUND4CNT_H => _SOUND4CNT_H, + + Register.SOUNDCNT_L => _SOUNDCNT_L, + Register.SOUNDCNT_H => _SOUNDCNT_H, + Register.SOUNDCNT_X => _SOUNDCNT_X, + + Register.SOUNDBIAS => _SOUNDBIAS, + + Register.WAVE_RAM0_L => _WAVE_RAM0_L, + Register.WAVE_RAM0_H => _WAVE_RAM0_H, + + Register.WAVE_RAM1_L => _WAVE_RAM1_L, + Register.WAVE_RAM1_H => _WAVE_RAM1_H, + + Register.WAVE_RAM2_L => _WAVE_RAM2_L, + Register.WAVE_RAM2_H => _WAVE_RAM2_H, + + Register.WAVE_RAM3_L => _WAVE_RAM3_L, + Register.WAVE_RAM3_H => _WAVE_RAM3_H, + + // should never happen + _ => throw new Exception("Iris.GBA.Sound: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.SOUND1CNT_L: + Memory.WriteRegisterHelper(ref _SOUND1CNT_L, value, mode); + break; + case Register.SOUND1CNT_H: + Memory.WriteRegisterHelper(ref _SOUND1CNT_H, value, mode); + break; + case Register.SOUND1CNT_X: + Memory.WriteRegisterHelper(ref _SOUND1CNT_X, value, mode); + break; + + case Register.SOUND2CNT_L: + Memory.WriteRegisterHelper(ref _SOUND2CNT_L, value, mode); + break; + case Register.SOUND2CNT_H: + Memory.WriteRegisterHelper(ref _SOUND2CNT_H, value, mode); + break; + + case Register.SOUND3CNT_L: + Memory.WriteRegisterHelper(ref _SOUND3CNT_L, value, mode); + break; + case Register.SOUND3CNT_H: + Memory.WriteRegisterHelper(ref _SOUND3CNT_H, value, mode); + break; + case Register.SOUND3CNT_X: + Memory.WriteRegisterHelper(ref _SOUND3CNT_X, value, mode); + break; + + case Register.SOUND4CNT_L: + Memory.WriteRegisterHelper(ref _SOUND4CNT_L, value, mode); + break; + case Register.SOUND4CNT_H: + Memory.WriteRegisterHelper(ref _SOUND4CNT_H, value, mode); + break; + + case Register.SOUNDCNT_L: + Memory.WriteRegisterHelper(ref _SOUNDCNT_L, value, mode); + break; + case Register.SOUNDCNT_H: + Memory.WriteRegisterHelper(ref _SOUNDCNT_H, value, mode); + break; + case Register.SOUNDCNT_X: + Memory.WriteRegisterHelper(ref _SOUNDCNT_X, value, mode); + break; + + case Register.SOUNDBIAS: + Memory.WriteRegisterHelper(ref _SOUNDBIAS, value, mode); + break; + + case Register.WAVE_RAM0_L: + Memory.WriteRegisterHelper(ref _WAVE_RAM0_L, value, mode); + break; + case Register.WAVE_RAM0_H: + Memory.WriteRegisterHelper(ref _WAVE_RAM0_H, value, mode); + break; + + case Register.WAVE_RAM1_L: + Memory.WriteRegisterHelper(ref _WAVE_RAM1_L, value, mode); + break; + case Register.WAVE_RAM1_H: + Memory.WriteRegisterHelper(ref _WAVE_RAM1_H, value, mode); + break; + + case Register.WAVE_RAM2_L: + Memory.WriteRegisterHelper(ref _WAVE_RAM2_L, value, mode); + break; + case Register.WAVE_RAM2_H: + Memory.WriteRegisterHelper(ref _WAVE_RAM2_H, value, mode); + break; + + case Register.WAVE_RAM3_L: + Memory.WriteRegisterHelper(ref _WAVE_RAM3_L, value, mode); + break; + case Register.WAVE_RAM3_H: + Memory.WriteRegisterHelper(ref _WAVE_RAM3_H, value, mode); + break; + + case Register.FIFO_A_L: + Memory.WriteRegisterHelper(ref _FIFO_A_L, value, mode); + break; + case Register.FIFO_A_H: + Memory.WriteRegisterHelper(ref _FIFO_A_H, value, mode); + break; + + case Register.FIFO_B_L: + Memory.WriteRegisterHelper(ref _FIFO_B_L, value, mode); + break; + case Register.FIFO_B_H: + Memory.WriteRegisterHelper(ref _FIFO_B_H, value, mode); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.Sound: Register write error"); + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/Sound.cs.meta b/Assets/Iris/Iris.GBA/Sound.cs.meta new file mode 100644 index 0000000..392f09a --- /dev/null +++ b/Assets/Iris/Iris.GBA/Sound.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e76b919919204d4db34341937e30bc9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/SystemControl.cs b/Assets/Iris/Iris.GBA/SystemControl.cs new file mode 100644 index 0000000..aed81e8 --- /dev/null +++ b/Assets/Iris/Iris.GBA/SystemControl.cs @@ -0,0 +1,61 @@ +namespace Iris.GBA +{ + internal sealed class SystemControl + { + internal enum Register + { + WAITCNT, + SYSCNT_UND0 // undocumented - Post Boot Flag (POSTFLG) & Power Down Control (HALTCNT) + } + + private UInt16 _WAITCNT; + private UInt16 _SYSCNT_UND0; + + internal void ResetState() + { + _WAITCNT = 0; + _SYSCNT_UND0 = 0; + } + + internal void LoadState(BinaryReader reader) + { + _WAITCNT = reader.ReadUInt16(); + _SYSCNT_UND0 = reader.ReadUInt16(); + } + + internal void SaveState(BinaryWriter writer) + { + writer.Write(_WAITCNT); + writer.Write(_SYSCNT_UND0); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.WAITCNT => _WAITCNT, + Register.SYSCNT_UND0 => _SYSCNT_UND0, + + // should never happen + _ => throw new Exception("Iris.GBA.SystemControl: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.WAITCNT: + Memory.WriteRegisterHelper(ref _WAITCNT, value, mode); + break; + case Register.SYSCNT_UND0: + Memory.WriteRegisterHelper(ref _SYSCNT_UND0, value, mode); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.SystemControl: Register write error"); + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/SystemControl.cs.meta b/Assets/Iris/Iris.GBA/SystemControl.cs.meta new file mode 100644 index 0000000..21ad2dc --- /dev/null +++ b/Assets/Iris/Iris.GBA/SystemControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24d9a1588f50c654691f136b394d3d31 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/Timer.cs b/Assets/Iris/Iris.GBA/Timer.cs new file mode 100644 index 0000000..ba0f96d --- /dev/null +++ b/Assets/Iris/Iris.GBA/Timer.cs @@ -0,0 +1,353 @@ +using System.Runtime.CompilerServices; + +namespace Iris.GBA +{ + internal sealed class Timer + { + internal enum Register + { + TM0CNT_L, + TM0CNT_H, + + TM1CNT_L, + TM1CNT_H, + + TM2CNT_L, + TM2CNT_H, + + TM3CNT_L, + TM3CNT_H + } + + private readonly Common.Scheduler _scheduler; + + private InterruptControl _interruptControl; + + private struct Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt) + { + internal UInt16 _counter; + internal UInt16 _reload; + internal UInt16 _control; + internal UInt64 _cycleCount; // only used in non-cascading mode + internal bool _running; + + internal readonly GBA_System.TaskId _startTaskId = startTaskId; + internal readonly GBA_System.TaskId _handleOverflowTaskId = handleOverflowTaskId; + internal readonly InterruptControl.Interrupt _interrupt = interrupt; + } + + private readonly Channel[] _channels; + + internal Timer(Common.Scheduler scheduler) + { + _scheduler = scheduler; + + _channels = + [ + new(GBA_System.TaskId.StartTimer_Channel0, GBA_System.TaskId.HandleTimerOverflow_Channel0, InterruptControl.Interrupt.Timer0), + new(GBA_System.TaskId.StartTimer_Channel1, GBA_System.TaskId.HandleTimerOverflow_Channel1, InterruptControl.Interrupt.Timer1), + new(GBA_System.TaskId.StartTimer_Channel2, GBA_System.TaskId.HandleTimerOverflow_Channel2, InterruptControl.Interrupt.Timer2), + new(GBA_System.TaskId.StartTimer_Channel3, GBA_System.TaskId.HandleTimerOverflow_Channel3, InterruptControl.Interrupt.Timer3) + ]; + + for (int channelIndex = 0; channelIndex < 4; ++channelIndex) + { + int channelIndexCopy = channelIndex; + _scheduler.RegisterTask((int)_channels[channelIndex]._startTaskId, cycleCountDelay => Start(channelIndexCopy, cycleCountDelay)); + _scheduler.RegisterTask((int)_channels[channelIndex]._handleOverflowTaskId, cycleCountDelay => HandleOverflow(channelIndexCopy, cycleCountDelay)); + } + } + + internal void Initialize(InterruptControl interruptControl) + { + _interruptControl = interruptControl; + } + + internal void ResetState() + { + foreach (ref Channel channel in _channels.AsSpan()) + { + channel._counter = 0; + channel._reload = 0; + channel._control = 0; + channel._cycleCount = 0; + channel._running = false; + } + } + + internal void LoadState(BinaryReader reader) + { + foreach (ref Channel channel in _channels.AsSpan()) + { + channel._counter = reader.ReadUInt16(); + channel._reload = reader.ReadUInt16(); + channel._control = reader.ReadUInt16(); + channel._cycleCount = reader.ReadUInt64(); + channel._running = reader.ReadBoolean(); + } + } + + internal void SaveState(BinaryWriter writer) + { + foreach (Channel channel in _channels) + { + writer.Write(channel._counter); + writer.Write(channel._reload); + writer.Write(channel._control); + writer.Write(channel._cycleCount); + writer.Write(channel._running); + } + } + + internal UInt16 ReadRegister(Register register) + { + UInt16 ReadCounter(int channelIndex) + { + ref Channel channel = ref _channels[channelIndex]; + + if (channel._running && (((channel._control & 0x0004) == 0) || (channelIndex == 0))) + UpdateCounter(ref channel, channel._control); + + return channel._counter; + } + + return register switch + { + Register.TM0CNT_L => ReadCounter(0), + Register.TM0CNT_H => _channels[0]._control, + + Register.TM1CNT_L => ReadCounter(1), + Register.TM1CNT_H => _channels[1]._control, + + Register.TM2CNT_L => ReadCounter(2), + Register.TM2CNT_H => _channels[2]._control, + + Register.TM3CNT_L => ReadCounter(3), + Register.TM3CNT_H => _channels[3]._control, + + // should never happen + _ => throw new Exception("Iris.GBA.Timer: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + void WriteReload(ref Channel channel) + { + UInt16 reload = channel._reload; + Memory.WriteRegisterHelper(ref reload, value, mode); + channel._reload = reload; + } + + void WriteControl(int channelIndex) + { + ref Channel channel = ref _channels[channelIndex]; + + UInt16 previousControl = channel._control; + + UInt16 newControl = channel._control; + Memory.WriteRegisterHelper(ref newControl, value, mode); + channel._control = newControl; + + CheckControl(ref channel, channelIndex, previousControl, newControl); + } + + switch (register) + { + case Register.TM0CNT_L: + WriteReload(ref _channels[0]); + break; + case Register.TM0CNT_H: + WriteControl(0); + break; + + case Register.TM1CNT_L: + WriteReload(ref _channels[1]); + break; + case Register.TM1CNT_H: + WriteControl(1); + break; + + case Register.TM2CNT_L: + WriteReload(ref _channels[2]); + break; + case Register.TM2CNT_H: + WriteControl(2); + break; + + case Register.TM3CNT_L: + WriteReload(ref _channels[3]); + break; + case Register.TM3CNT_H: + WriteControl(3); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.Timer: Register write error"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CheckControl(ref Channel channel, int channelIndex, UInt16 previousControl, UInt16 newControl) + { + if ((previousControl & 0x0080) == 0) + { + if ((newControl & 0x0080) == 0x0080) + _scheduler.ScheduleTask((int)channel._startTaskId, 2); + } + else + { + if ((newControl & 0x0080) == 0) + { + if (channel._running) + { + if (((previousControl & 0x0004) == 0) || (channelIndex == 0)) + { + UpdateCounter(ref channel, previousControl); + + _scheduler.CancelTask((int)channel._handleOverflowTaskId); + } + + channel._running = false; + } + else + { + _scheduler.CancelTask((int)channel._startTaskId); + } + } + else + { + if (channel._running) + { + if (channelIndex == 0) + { + if ((previousControl & 0b11) != (newControl & 0b11)) + { + UpdateCounter(ref channel, previousControl); + + _scheduler.CancelTask((int)channel._handleOverflowTaskId); + _scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel)); + } + } + else + { + if ((previousControl & 0x0004) == 0) + { + if ((newControl & 0x0004) == 0) + { + if ((previousControl & 0b11) != (newControl & 0b11)) + { + UpdateCounter(ref channel, previousControl); + + _scheduler.CancelTask((int)channel._handleOverflowTaskId); + _scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel)); + } + } + else + { + UpdateCounter(ref channel, previousControl); + + _scheduler.CancelTask((int)channel._handleOverflowTaskId); + } + } + else + { + if ((newControl & 0x0004) == 0) + { + channel._cycleCount = _scheduler.GetCycleCounter(); + + _scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel)); + } + } + } + } + } + } + } + + private void UpdateCounter(ref Channel channel, UInt16 control) + { + UInt64 currentCycleCount = _scheduler.GetCycleCounter(); + UInt64 cycleCountDelta = currentCycleCount - channel._cycleCount; + UInt64 prescaler = GetPrescaler(control); + + channel._counter += (UInt16)(cycleCountDelta / prescaler); + channel._cycleCount = currentCycleCount - (UInt16)(cycleCountDelta % prescaler); + } + + private void Start(int channelIndex, UInt64 cycleCountDelay) + { + ref Channel channel = ref _channels[channelIndex]; + + channel._counter = channel._reload; + channel._running = true; + + if (((channel._control & 0x0004) == 0) || (channelIndex == 0)) + { + channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay; + + _scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay); + } + } + + private void HandleOverflow(int channelIndex, UInt64 cycleCountDelay) + { + ref Channel channel = ref _channels[channelIndex]; + + channel._counter = channel._reload; + channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay; + + _scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay); + + if ((channel._control & 0x0040) == 0x0040) + _interruptControl.RequestInterrupt(channel._interrupt); + + CascadeOverflow(channelIndex); + } + + private void CascadeOverflow(int channelIndex) + { + if (channelIndex == 3) + return; + + ++channelIndex; + + ref Channel channel = ref _channels[channelIndex]; + + if (!channel._running || ((channel._control & 0x0004) != 0x0004)) + return; + + if (channel._counter == 0xffff) + { + channel._counter = channel._reload; + + if ((channel._control & 0x0040) == 0x0040) + _interruptControl.RequestInterrupt(channel._interrupt); + + CascadeOverflow(channelIndex); + } + else + { + ++channel._counter; + } + } + + private static UInt64 ComputeCycleCountUntilOverflow(ref readonly Channel channel) + { + return (0x1_0000u - channel._counter) * GetPrescaler(channel._control); + } + + private static UInt64 GetPrescaler(UInt16 control) + { + return (control & 0b11) switch + { + 0b00 => 1, + 0b01 => 64, + 0b10 => 256, + 0b11 => 1024, + _ => 0, // cannot happen + }; + } + } +} diff --git a/Assets/Iris/Iris.GBA/Timer.cs.meta b/Assets/Iris/Iris.GBA/Timer.cs.meta new file mode 100644 index 0000000..447d88b --- /dev/null +++ b/Assets/Iris/Iris.GBA/Timer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1c87077f94b1c547b5ef23240dca9cb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.GBA/Video.cs b/Assets/Iris/Iris.GBA/Video.cs new file mode 100644 index 0000000..64a570d --- /dev/null +++ b/Assets/Iris/Iris.GBA/Video.cs @@ -0,0 +1,1346 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Iris.GBA +{ + internal sealed class Video : IDisposable + { + internal enum Register + { + DISPCNT, + DISPSTAT, + VCOUNT, + + BG0CNT, + BG1CNT, + BG2CNT, + BG3CNT, + + BG0HOFS, + BG0VOFS, + + BG1HOFS, + BG1VOFS, + + BG2HOFS, + BG2VOFS, + + BG3HOFS, + BG3VOFS, + + BG2PA, + BG2PB, + BG2PC, + BG2PD, + BG2X_L, + BG2X_H, + BG2Y_L, + BG2Y_H, + + BG3PA, + BG3PB, + BG3PC, + BG3PD, + BG3X_L, + BG3X_H, + BG3Y_L, + BG3Y_H, + + WIN0H, + WIN1H, + + WIN0V, + WIN1V, + + WININ, + WINOUT, + + MOSAIC, + + BLDCNT, + BLDALPHA, + BLDY + } + + private const int KB = 1024; + + private const int PaletteRAM_Size = 1 * KB; + private const int VRAM_Size = 96 * KB; + private const int OAM_Size = 1 * KB; + + private readonly IntPtr _paletteRAM = Marshal.AllocHGlobal(PaletteRAM_Size); + private readonly IntPtr _vram = Marshal.AllocHGlobal(VRAM_Size); + private readonly IntPtr _oam = Marshal.AllocHGlobal(OAM_Size); + + private const UInt32 PaletteRAM_StartAddress = 0x0500_0000; + private const UInt32 PaletteRAM_EndAddress = 0x0600_0000; + + private const UInt32 VRAM_StartAddress = 0x0600_0000; + private const UInt32 VRAM_EndAddress = 0x0700_0000; + + private const UInt32 OAM_StartAddress = 0x0700_0000; + private const UInt32 OAM_EndAddress = 0x0800_0000; + + private UInt16 _DISPCNT; + private UInt16 _DISPSTAT; + private UInt16 _VCOUNT; + + private UInt16 _BG0CNT; + private UInt16 _BG1CNT; + private UInt16 _BG2CNT; + private UInt16 _BG3CNT; + + private UInt16 _BG0HOFS; + private UInt16 _BG0VOFS; + + private UInt16 _BG1HOFS; + private UInt16 _BG1VOFS; + + private UInt16 _BG2HOFS; + private UInt16 _BG2VOFS; + + private UInt16 _BG3HOFS; + private UInt16 _BG3VOFS; + + private UInt16 _BG2PA; + private UInt16 _BG2PB; + private UInt16 _BG2PC; + private UInt16 _BG2PD; + private UInt16 _BG2X_L; + private UInt16 _BG2X_H; + private UInt16 _BG2Y_L; + private UInt16 _BG2Y_H; + + private UInt16 _BG3PA; + private UInt16 _BG3PB; + private UInt16 _BG3PC; + private UInt16 _BG3PD; + private UInt16 _BG3X_L; + private UInt16 _BG3X_H; + private UInt16 _BG3Y_L; + private UInt16 _BG3Y_H; + + private UInt16 _WIN0H; + private UInt16 _WIN1H; + + private UInt16 _WIN0V; + private UInt16 _WIN1V; + + private UInt16 _WININ; + private UInt16 _WINOUT; + + private UInt16 _MOSAIC; + + private UInt16 _BLDCNT; + private UInt16 _BLDALPHA; + private UInt16 _BLDY; + + private const int DisplayScreenWidth = 240; + private const int DisplayScreenHeight = 160; + private const int DisplayScreenSize = DisplayScreenWidth * DisplayScreenHeight; + + private const int CharacterWidth = 8; + private const int CharacterHeight = 8; + + private const UInt64 HDrawCycleCount = 1006; + private const UInt64 HBlankCycleCount = 226; + + private readonly Common.Scheduler _scheduler; + private readonly Common.System.PresentFrame_Delegate _presentFrameCallback; + + private DMA _dma; + private InterruptControl _interruptControl; + + private bool _disposed; + + private readonly UInt16[] _displayFrameBuffer = new UInt16[DisplayScreenSize]; + + private Int32 _currentBG2X; + private Int32 _currentBG2Y; + + private Int32 _currentBG3X; + private Int32 _currentBG3Y; + + internal Video(Common.Scheduler scheduler, Common.System.PresentFrame_Delegate presentFrameCallback) + { + _scheduler = scheduler; + _presentFrameCallback = presentFrameCallback; + + _scheduler.RegisterTask((int)GBA_System.TaskId.StartHBlank, StartHBlank); + _scheduler.RegisterTask((int)GBA_System.TaskId.StartScanline, StartScanline); + } + + ~Video() + { + Dispose(); + } + + public void Dispose() + { + if (_disposed) + return; + + Marshal.FreeHGlobal(_paletteRAM); + Marshal.FreeHGlobal(_vram); + Marshal.FreeHGlobal(_oam); + + GC.SuppressFinalize(this); + _disposed = true; + } + + internal void Initialize(DMA dma, InterruptControl interruptControl, Memory memory) + { + _dma = dma; + _interruptControl = interruptControl; + + const Memory.Flag flags = Memory.Flag.All & ~Memory.Flag.Write8; + memory.Map(_paletteRAM, PaletteRAM_Size, PaletteRAM_StartAddress, PaletteRAM_EndAddress, flags); + memory.Map(_vram, VRAM_Size, VRAM_StartAddress, VRAM_EndAddress, flags); + memory.Map(_oam, OAM_Size, OAM_StartAddress, OAM_EndAddress, flags); + } + + internal void ResetState() + { + byte[] paletteRamData = new byte[PaletteRAM_Size]; + byte[] vramData = new byte[VRAM_Size]; + byte[] oamData = new byte[OAM_Size]; + + Marshal.Copy(paletteRamData, 0, _paletteRAM, PaletteRAM_Size); + Marshal.Copy(vramData, 0, _vram, VRAM_Size); + Marshal.Copy(oamData, 0, _oam, OAM_Size); + + _DISPCNT = 0; + _DISPSTAT = 0; + _VCOUNT = 0; + + _BG0CNT = 0; + _BG1CNT = 0; + _BG2CNT = 0; + _BG3CNT = 0; + + _BG0HOFS = 0; + _BG0VOFS = 0; + + _BG1HOFS = 0; + _BG1VOFS = 0; + + _BG2HOFS = 0; + _BG2VOFS = 0; + + _BG3HOFS = 0; + _BG3VOFS = 0; + + _BG2PA = 0; + _BG2PB = 0; + _BG2PC = 0; + _BG2PD = 0; + _BG2X_L = 0; + _BG2X_H = 0; + _BG2Y_L = 0; + _BG2Y_H = 0; + + _BG3PA = 0; + _BG3PB = 0; + _BG3PC = 0; + _BG3PD = 0; + _BG3X_L = 0; + _BG3X_H = 0; + _BG3Y_L = 0; + _BG3Y_H = 0; + + _WIN0H = 0; + _WIN1H = 0; + + _WIN0V = 0; + _WIN1V = 0; + + _WININ = 0; + _WINOUT = 0; + + _MOSAIC = 0; + + _BLDCNT = 0; + _BLDALPHA = 0; + _BLDY = 0; + + _scheduler.ScheduleTask((int)GBA_System.TaskId.StartHBlank, HDrawCycleCount); + + Array.Clear(_displayFrameBuffer); + + _currentBG2X = 0; + _currentBG2Y = 0; + + _currentBG3X = 0; + _currentBG3Y = 0; + } + + internal void LoadState(BinaryReader reader) + { + byte[] paletteRamData = reader.ReadBytes(PaletteRAM_Size); + byte[] vramData = reader.ReadBytes(VRAM_Size); + byte[] oamData = reader.ReadBytes(OAM_Size); + + Marshal.Copy(paletteRamData, 0, _paletteRAM, PaletteRAM_Size); + Marshal.Copy(vramData, 0, _vram, VRAM_Size); + Marshal.Copy(oamData, 0, _oam, OAM_Size); + + _DISPCNT = reader.ReadUInt16(); + _DISPSTAT = reader.ReadUInt16(); + _VCOUNT = reader.ReadUInt16(); + + _BG0CNT = reader.ReadUInt16(); + _BG1CNT = reader.ReadUInt16(); + _BG2CNT = reader.ReadUInt16(); + _BG3CNT = reader.ReadUInt16(); + + _BG0HOFS = reader.ReadUInt16(); + _BG0VOFS = reader.ReadUInt16(); + + _BG1HOFS = reader.ReadUInt16(); + _BG1VOFS = reader.ReadUInt16(); + + _BG2HOFS = reader.ReadUInt16(); + _BG2VOFS = reader.ReadUInt16(); + + _BG3HOFS = reader.ReadUInt16(); + _BG3VOFS = reader.ReadUInt16(); + + _BG2PA = reader.ReadUInt16(); + _BG2PB = reader.ReadUInt16(); + _BG2PC = reader.ReadUInt16(); + _BG2PD = reader.ReadUInt16(); + _BG2X_L = reader.ReadUInt16(); + _BG2X_H = reader.ReadUInt16(); + _BG2Y_L = reader.ReadUInt16(); + _BG2Y_H = reader.ReadUInt16(); + + _BG3PA = reader.ReadUInt16(); + _BG3PB = reader.ReadUInt16(); + _BG3PC = reader.ReadUInt16(); + _BG3PD = reader.ReadUInt16(); + _BG3X_L = reader.ReadUInt16(); + _BG3X_H = reader.ReadUInt16(); + _BG3Y_L = reader.ReadUInt16(); + _BG3Y_H = reader.ReadUInt16(); + + _WIN0H = reader.ReadUInt16(); + _WIN1H = reader.ReadUInt16(); + + _WIN0V = reader.ReadUInt16(); + _WIN1V = reader.ReadUInt16(); + + _WININ = reader.ReadUInt16(); + _WINOUT = reader.ReadUInt16(); + + _MOSAIC = reader.ReadUInt16(); + + _BLDCNT = reader.ReadUInt16(); + _BLDALPHA = reader.ReadUInt16(); + _BLDY = reader.ReadUInt16(); + + foreach (ref UInt16 color in _displayFrameBuffer.AsSpan()) + color = reader.ReadUInt16(); + + _currentBG2X = reader.ReadInt32(); + _currentBG2Y = reader.ReadInt32(); + + _currentBG3X = reader.ReadInt32(); + _currentBG3Y = reader.ReadInt32(); + } + + internal void SaveState(BinaryWriter writer) + { + byte[] paletteRamData = new byte[PaletteRAM_Size]; + byte[] vramData = new byte[VRAM_Size]; + byte[] oamData = new byte[OAM_Size]; + + Marshal.Copy(_paletteRAM, paletteRamData, 0, PaletteRAM_Size); + Marshal.Copy(_vram, vramData, 0, VRAM_Size); + Marshal.Copy(_oam, oamData, 0, OAM_Size); + + writer.Write(paletteRamData); + writer.Write(vramData); + writer.Write(oamData); + + writer.Write(_DISPCNT); + writer.Write(_DISPSTAT); + writer.Write(_VCOUNT); + + writer.Write(_BG0CNT); + writer.Write(_BG1CNT); + writer.Write(_BG2CNT); + writer.Write(_BG3CNT); + + writer.Write(_BG0HOFS); + writer.Write(_BG0VOFS); + + writer.Write(_BG1HOFS); + writer.Write(_BG1VOFS); + + writer.Write(_BG2HOFS); + writer.Write(_BG2VOFS); + + writer.Write(_BG3HOFS); + writer.Write(_BG3VOFS); + + writer.Write(_BG2PA); + writer.Write(_BG2PB); + writer.Write(_BG2PC); + writer.Write(_BG2PD); + writer.Write(_BG2X_L); + writer.Write(_BG2X_H); + writer.Write(_BG2Y_L); + writer.Write(_BG2Y_H); + + writer.Write(_BG3PA); + writer.Write(_BG3PB); + writer.Write(_BG3PC); + writer.Write(_BG3PD); + writer.Write(_BG3X_L); + writer.Write(_BG3X_H); + writer.Write(_BG3Y_L); + writer.Write(_BG3Y_H); + + writer.Write(_WIN0H); + writer.Write(_WIN1H); + + writer.Write(_WIN0V); + writer.Write(_WIN1V); + + writer.Write(_WININ); + writer.Write(_WINOUT); + + writer.Write(_MOSAIC); + + writer.Write(_BLDCNT); + writer.Write(_BLDALPHA); + writer.Write(_BLDY); + + foreach (UInt16 color in _displayFrameBuffer) + writer.Write(color); + + writer.Write(_currentBG2X); + writer.Write(_currentBG2Y); + + writer.Write(_currentBG3X); + writer.Write(_currentBG3Y); + } + + internal UInt16 ReadRegister(Register register) + { + return register switch + { + Register.DISPCNT => _DISPCNT, + Register.DISPSTAT => _DISPSTAT, + Register.VCOUNT => _VCOUNT, + + Register.BG0CNT => _BG0CNT, + Register.BG1CNT => _BG1CNT, + Register.BG2CNT => _BG2CNT, + Register.BG3CNT => _BG3CNT, + + Register.WININ => _WININ, + Register.WINOUT => _WINOUT, + + Register.BLDCNT => _BLDCNT, + Register.BLDALPHA => _BLDALPHA, + + // should never happen + _ => throw new Exception("Iris.GBA.Video: Register read error"), + }; + } + + internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode) + { + switch (register) + { + case Register.DISPCNT: + Memory.WriteRegisterHelper(ref _DISPCNT, value, mode); + break; + case Register.DISPSTAT: + Memory.WriteRegisterHelper(ref _DISPSTAT, value, mode); + break; + + case Register.BG0CNT: + Memory.WriteRegisterHelper(ref _BG0CNT, value, mode); + break; + case Register.BG1CNT: + Memory.WriteRegisterHelper(ref _BG1CNT, value, mode); + break; + case Register.BG2CNT: + Memory.WriteRegisterHelper(ref _BG2CNT, value, mode); + break; + case Register.BG3CNT: + Memory.WriteRegisterHelper(ref _BG3CNT, value, mode); + break; + + case Register.BG0HOFS: + Memory.WriteRegisterHelper(ref _BG0HOFS, value, mode); + break; + case Register.BG0VOFS: + Memory.WriteRegisterHelper(ref _BG0VOFS, value, mode); + break; + + case Register.BG1HOFS: + Memory.WriteRegisterHelper(ref _BG1HOFS, value, mode); + break; + case Register.BG1VOFS: + Memory.WriteRegisterHelper(ref _BG1VOFS, value, mode); + break; + + case Register.BG2HOFS: + Memory.WriteRegisterHelper(ref _BG2HOFS, value, mode); + break; + case Register.BG2VOFS: + Memory.WriteRegisterHelper(ref _BG2VOFS, value, mode); + break; + + case Register.BG3HOFS: + Memory.WriteRegisterHelper(ref _BG3HOFS, value, mode); + break; + case Register.BG3VOFS: + Memory.WriteRegisterHelper(ref _BG3VOFS, value, mode); + break; + + case Register.BG2PA: + Memory.WriteRegisterHelper(ref _BG2PA, value, mode); + break; + case Register.BG2PB: + Memory.WriteRegisterHelper(ref _BG2PB, value, mode); + break; + case Register.BG2PC: + Memory.WriteRegisterHelper(ref _BG2PC, value, mode); + break; + case Register.BG2PD: + Memory.WriteRegisterHelper(ref _BG2PD, value, mode); + break; + case Register.BG2X_L: + Memory.WriteRegisterHelper(ref _BG2X_L, value, mode); + _currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4; + break; + case Register.BG2X_H: + Memory.WriteRegisterHelper(ref _BG2X_H, value, mode); + _currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4; + break; + case Register.BG2Y_L: + Memory.WriteRegisterHelper(ref _BG2Y_L, value, mode); + _currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4; + break; + case Register.BG2Y_H: + Memory.WriteRegisterHelper(ref _BG2Y_H, value, mode); + _currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4; + break; + + case Register.BG3PA: + Memory.WriteRegisterHelper(ref _BG3PA, value, mode); + break; + case Register.BG3PB: + Memory.WriteRegisterHelper(ref _BG3PB, value, mode); + break; + case Register.BG3PC: + Memory.WriteRegisterHelper(ref _BG3PC, value, mode); + break; + case Register.BG3PD: + Memory.WriteRegisterHelper(ref _BG3PD, value, mode); + break; + case Register.BG3X_L: + Memory.WriteRegisterHelper(ref _BG3X_L, value, mode); + _currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4; + break; + case Register.BG3X_H: + Memory.WriteRegisterHelper(ref _BG3X_H, value, mode); + _currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4; + break; + case Register.BG3Y_L: + Memory.WriteRegisterHelper(ref _BG3Y_L, value, mode); + _currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4; + break; + case Register.BG3Y_H: + Memory.WriteRegisterHelper(ref _BG3Y_H, value, mode); + _currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4; + break; + + case Register.WIN0H: + Memory.WriteRegisterHelper(ref _WIN0H, value, mode); + break; + case Register.WIN1H: + Memory.WriteRegisterHelper(ref _WIN1H, value, mode); + break; + + case Register.WIN0V: + Memory.WriteRegisterHelper(ref _WIN0V, value, mode); + break; + case Register.WIN1V: + Memory.WriteRegisterHelper(ref _WIN1V, value, mode); + break; + + case Register.WININ: + Memory.WriteRegisterHelper(ref _WININ, value, mode); + break; + case Register.WINOUT: + Memory.WriteRegisterHelper(ref _WINOUT, value, mode); + break; + + case Register.MOSAIC: + Memory.WriteRegisterHelper(ref _MOSAIC, value, mode); + break; + + case Register.BLDCNT: + Memory.WriteRegisterHelper(ref _BLDCNT, value, mode); + break; + case Register.BLDALPHA: + Memory.WriteRegisterHelper(ref _BLDALPHA, value, mode); + break; + case Register.BLDY: + Memory.WriteRegisterHelper(ref _BLDY, value, mode); + break; + + // should never happen + default: + throw new Exception("Iris.GBA.Video: Register write error"); + } + } + + internal void Write8_PaletteRAM(UInt32 address, Byte value) + { + UInt32 offset = (UInt32)((address - PaletteRAM_StartAddress) & ~1) % PaletteRAM_Size; + + unsafe + { + Unsafe.Write((Byte*)_paletteRAM + offset, value); + Unsafe.Write((Byte*)_paletteRAM + offset + 1, value); + } + } + + internal void Write8_VRAM(UInt32 address, Byte value) + { + UInt32 offset = (UInt32)((address - VRAM_StartAddress) & ~1) % VRAM_Size; + + unsafe + { + Unsafe.Write((Byte*)_vram + offset, value); + Unsafe.Write((Byte*)_vram + offset + 1, value); + } + } + + private void StartHBlank(UInt64 cycleCountDelay) + { + _DISPSTAT |= 0x0002; // set HBlank status + + if ((_DISPSTAT & 0x0010) == 0x0010) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.HBlank); + + if (_VCOUNT < DisplayScreenHeight) + _dma.PerformHBlankTransfers(); + + _scheduler.ScheduleTask((int)GBA_System.TaskId.StartScanline, HBlankCycleCount - cycleCountDelay); + } + + private void StartScanline(UInt64 cycleCountDelay) + { + _DISPSTAT = (UInt16)(_DISPSTAT & ~0x0002); // clear HBlank status + + switch (_VCOUNT) + { + // rendering + case 0: + _VCOUNT = 1; + + Render(); + break; + + // rendering + // video transfer DMA + case > 0 and < 159: + ++_VCOUNT; + + _dma.PerformVideoTransfer(false); + + Render(); + break; + + // end rendering + // start vblank + // video transfer DMA + case 159: + _VCOUNT = 160; + + _DISPSTAT |= 0x0001; // set VBlank status + + if ((_DISPSTAT & 0x0008) == 0x0008) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.VBlank); + + _dma.PerformVBlankTransfers(); + _dma.PerformVideoTransfer(false); + + _presentFrameCallback(_displayFrameBuffer); + Array.Clear(_displayFrameBuffer); + + _currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4; + _currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4; + + _currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4; + _currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4; + break; + + // vblank + // end video transfer DMA + case 160: + _VCOUNT = 161; + + _dma.PerformVideoTransfer(true); + break; + + // vblank + case > 160 and < 226: + ++_VCOUNT; + break; + + // end vblank + case 226: + _VCOUNT = 227; + + _DISPSTAT = (UInt16)(_DISPSTAT & ~0x0001); // clear VBlank status + break; + + // start rendering + case 227: + _VCOUNT = 0; + + Render(); + break; + } + + if (_VCOUNT == ((_DISPSTAT >> 8) & 0xff)) + { + _DISPSTAT |= 0x0004; // set VCountMatch status + + if ((_DISPSTAT & 0x0020) == 0x0020) + _interruptControl.RequestInterrupt(InterruptControl.Interrupt.VCountMatch); + } + else + { + _DISPSTAT = (UInt16)(_DISPSTAT & ~0x0004); // clear VCountMatch status + } + + _scheduler.ScheduleTask((int)GBA_System.TaskId.StartHBlank, HDrawCycleCount - cycleCountDelay); + } + + private void Render() + { + UInt16 bgMode = (UInt16)(_DISPCNT & 0b111); + + switch (bgMode) + { + case 0b000: + RenderBackgroundMode0(); + break; + case 0b001: + RenderBackgroundMode1(); + break; + case 0b010: + RenderBackgroundMode2(); + break; + case 0b011: + RenderBackgroundMode3(); + break; + case 0b100: + RenderBackgroundMode4(); + break; + case 0b101: + RenderBackgroundMode5(); + break; + + // TODO: verify + case 0b110: + case 0b111: + throw new Exception($"Iris.GBA.Video: Unknown background mode {bgMode}"); + } + + _currentBG2X += (Int16)_BG2PB; + _currentBG2Y += (Int16)_BG2PD; + + _currentBG3X += (Int16)_BG3PB; + _currentBG3Y += (Int16)_BG3PD; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode0() + { + bool isFirst = true; + + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + { + if (((_DISPCNT & 0x0800) == 0x0800) && ((_BG3CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG3CNT, _BG3HOFS, _BG3VOFS, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG2CNT, _BG2HOFS, _BG2VOFS, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0200) == 0x0200) && ((_BG1CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG1CNT, _BG1HOFS, _BG1VOFS, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0100) == 0x0100) && ((_BG0CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG0CNT, _BG0HOFS, _BG0VOFS, isFirst); + isFirst = false; + } + + if ((_DISPCNT & 0x1000) == 0x1000) + RenderObjects((UInt16)bgPriority); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode1() + { + bool isFirst = true; + + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + { + if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority)) + { + RenderRotationScalingBackground(_BG2CNT, _currentBG2X, _currentBG2Y, _BG2PA, _BG2PC, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0200) == 0x0200) && ((_BG1CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG1CNT, _BG1HOFS, _BG1VOFS, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0100) == 0x0100) && ((_BG0CNT & 0b11) == bgPriority)) + { + RenderTextBackground(_BG0CNT, _BG0HOFS, _BG0VOFS, isFirst); + isFirst = false; + } + + if ((_DISPCNT & 0x1000) == 0x1000) + RenderObjects((UInt16)bgPriority); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode2() + { + bool isFirst = true; + + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + { + if (((_DISPCNT & 0x0800) == 0x0800) && ((_BG3CNT & 0b11) == bgPriority)) + { + RenderRotationScalingBackground(_BG3CNT, _currentBG3X, _currentBG3Y, _BG3PA, _BG3PC, isFirst); + isFirst = false; + } + + if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority)) + { + RenderRotationScalingBackground(_BG2CNT, _currentBG2X, _currentBG2Y, _BG2PA, _BG2PC, isFirst); + isFirst = false; + } + + if ((_DISPCNT & 0x1000) == 0x1000) + RenderObjects((UInt16)bgPriority); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode3() + { + if ((_DISPCNT & 0x0400) == 0x0400) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int pixelNumberBegin = _VCOUNT * DisplayScreenWidth; + int pixelNumberEnd = pixelNumberBegin + DisplayScreenWidth; + + for (int pixelNumber = pixelNumberBegin; pixelNumber < pixelNumberEnd; ++pixelNumber) + { + unsafe + { + UInt16 color = Unsafe.Read((UInt16*)_vram + pixelNumber); + Unsafe.Add(ref displayFrameBufferDataRef, pixelNumber) = color; + } + } + } + + if ((_DISPCNT & 0x1000) == 0x1000) + { + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + RenderObjects((UInt16)bgPriority); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode4() + { + if ((_DISPCNT & 0x0400) == 0x0400) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int pixelNumberBegin = _VCOUNT * DisplayScreenWidth; + int pixelNumberEnd = pixelNumberBegin + DisplayScreenWidth; + + UInt32 vramFrameBufferOffset = ((_DISPCNT & 0x0010) == 0) ? 0x0_0000u : 0x0_a000u; + + for (int pixelNumber = pixelNumberBegin; pixelNumber < pixelNumberEnd; ++pixelNumber) + { + unsafe + { + Byte colorNumber = Unsafe.Read((Byte*)_vram + vramFrameBufferOffset + pixelNumber); + UInt16 color = Unsafe.Read((UInt16*)_paletteRAM + colorNumber); + Unsafe.Add(ref displayFrameBufferDataRef, pixelNumber) = color; + } + } + } + + if ((_DISPCNT & 0x1000) == 0x1000) + { + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + RenderObjects((UInt16)bgPriority); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderBackgroundMode5() + { + if ((_DISPCNT & 0x0400) == 0x0400) + { + const int VRAM_FrameBufferWidth = 160; + const int VRAM_FrameBufferHeight = 128; + + if (_VCOUNT < VRAM_FrameBufferHeight) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int vramPixelNumberBegin = _VCOUNT * VRAM_FrameBufferWidth; + int vramPixelNumberEnd = vramPixelNumberBegin + VRAM_FrameBufferWidth; + + int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth; + + UInt32 vramFrameBufferOffset = ((_DISPCNT & 0x0010) == 0) ? 0x0_0000u : 0x0_a000u; + + for (int vramPixelNumber = vramPixelNumberBegin, displayPixelNumber = displayPixelNumberBegin; vramPixelNumber < vramPixelNumberEnd; ++vramPixelNumber, ++displayPixelNumber) + { + unsafe + { + UInt16 color = Unsafe.Read((Byte*)_vram + vramFrameBufferOffset + (vramPixelNumber * 2)); + Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color; + } + } + } + } + + if ((_DISPCNT & 0x1000) == 0x1000) + { + for (int bgPriority = 3; bgPriority >= 0; --bgPriority) + RenderObjects((UInt16)bgPriority); + } + } + + private void RenderTextBackground(UInt16 cnt, UInt16 hofs, UInt16 vofs, bool isFirst) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth; + + UInt16 virtualScreenSize = (UInt16)((cnt >> 14) & 0b11); + UInt16 screenBaseBlock = (UInt16)((cnt >> 8) & 0b1_1111); + UInt16 colorMode = (UInt16)((cnt >> 7) & 1); + UInt16 characterBaseBlock = (UInt16)((cnt >> 2) & 0b11); + + int virtualScreenWidth = ((virtualScreenSize & 0b01) == 0) ? 256 : 512; + int virtualScreenHeight = ((virtualScreenSize & 0b10) == 0) ? 256 : 512; + + UInt32 screenBaseBlockOffset = (UInt32)(screenBaseBlock * 2 * KB); + UInt32 characterBaseBlockOffset = (UInt32)(characterBaseBlock * 16 * KB); + + int v = (_VCOUNT + vofs) % virtualScreenHeight; + + const int SC_Width = 256; + const int SC_Height = 256; + const int SC_CharacterCount = (SC_Width / CharacterWidth) * (SC_Height / CharacterHeight); + + int scV = v / SC_Height; + int scPixelV = v % SC_Height; + + int scCharacterV = scPixelV / CharacterHeight; + int characterPixelV = scPixelV % CharacterHeight; + + int scNumberBegin = scV * (virtualScreenWidth / SC_Width); + int characterNumberBegin = scCharacterV * (SC_Width / CharacterWidth); + + int characterPixelNumberBegin = characterPixelV * CharacterWidth; + int characterPixelNumberBegin_VerticalFlip = (CharacterHeight - 1 - characterPixelV) * CharacterWidth; + + for (int hcount = 0; hcount < DisplayScreenWidth; ++hcount) + { + int h = (hcount + hofs) % virtualScreenWidth; + + int scH = h / SC_Width; + int scPixelH = h % SC_Width; + + int scCharacterH = scPixelH / CharacterWidth; + int characterPixelH = scPixelH % CharacterWidth; + + int scNumber = scNumberBegin + scH; + int characterNumber = characterNumberBegin + scCharacterH; + + unsafe + { + UInt16 screenData = Unsafe.Read((Byte*)_vram + screenBaseBlockOffset + (scNumber * SC_CharacterCount * 2) + (characterNumber * 2)); + + UInt16 colorPalette = (UInt16)((screenData >> 12) & 0b1111); + UInt16 verticalFlipFlag = (UInt16)((screenData >> 11) & 1); + UInt16 horizontalFlipFlag = (UInt16)((screenData >> 10) & 1); + UInt16 characterName = (UInt16)(screenData & 0x3ff); + + int characterPixelNumber; + + if (verticalFlipFlag == 0) + characterPixelNumber = characterPixelNumberBegin; + else + characterPixelNumber = characterPixelNumberBegin_VerticalFlip; + + if (horizontalFlipFlag == 0) + characterPixelNumber += characterPixelH; + else + characterPixelNumber += CharacterWidth - 1 - characterPixelH; + + const UInt32 ObjectCharacterDataOffset = 0x1_0000; + + UInt16 color; + + // 16 colors x 16 palettes + if (colorMode == 0) + { + const int CharacterSize = 32; + UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize)); + + if (characterOffset >= ObjectCharacterDataOffset) + continue; + + Byte colorNumber = Unsafe.Read((Byte*)_vram + characterOffset + (characterPixelNumber / 2)); + + if ((characterPixelNumber % 2) == 0) + colorNumber &= 0b1111; + else + colorNumber >>= 4; + + if (!isFirst && (colorNumber == 0)) + continue; + + color = Unsafe.Read((UInt16*)_paletteRAM + (colorPalette * 16) + colorNumber); + } + + // 256 colors x 1 palette + else + { + const int CharacterSize = 64; + UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize)); + + if (characterOffset >= ObjectCharacterDataOffset) + continue; + + Byte colorNumber = Unsafe.Read((Byte*)_vram + characterOffset + characterPixelNumber); + + if (!isFirst && (colorNumber == 0)) + continue; + + color = Unsafe.Read((UInt16*)_paletteRAM + colorNumber); + } + + int displayPixelNumber = displayPixelNumberBegin + hcount; + Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color; + } + } + } + + private void RenderRotationScalingBackground(UInt16 cnt, Int32 x, Int32 y, UInt16 pa, UInt16 pc, bool isFirst) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth; + + UInt16 virtualScreenSize = (UInt16)((cnt >> 14) & 0b11); + UInt16 areaOverflow = (UInt16)((cnt >> 13) & 1); + UInt16 screenBaseBlock = (UInt16)((cnt >> 8) & 0b1_1111); + UInt16 characterBaseBlock = (UInt16)((cnt >> 2) & 0b11); + + int virtualScreenWidth = 128 << virtualScreenSize; + int virtualScreenHeight = 128 << virtualScreenSize; + + UInt32 screenBaseBlockOffset = (UInt32)(screenBaseBlock * 2 * KB); + UInt32 characterBaseBlockOffset = (UInt32)(characterBaseBlock * 16 * KB); + + for (int hcount = 0; hcount < DisplayScreenWidth; ++hcount, x += (Int16)pa, y += (Int16)pc) + { + int h = x >> 8; + int v = y >> 8; + + if (areaOverflow == 0) + { + if ((h < 0) || (h >= virtualScreenWidth)) + continue; + + if ((v < 0) || (v >= virtualScreenHeight)) + continue; + } + else + { + h %= virtualScreenWidth; + v %= virtualScreenHeight; + + if (h < 0) + h += virtualScreenWidth; + + if (v < 0) + v += virtualScreenHeight; + } + + int characterH = h / CharacterWidth; + int characterPixelH = h % CharacterWidth; + + int characterV = v / CharacterHeight; + int characterPixelV = v % CharacterHeight; + + int characterNumber = (characterV * (virtualScreenWidth / CharacterWidth)) + characterH; + int characterPixelNumber = (characterPixelV * CharacterWidth) + characterPixelH; + + unsafe + { + Byte characterName = Unsafe.Read((Byte*)_vram + screenBaseBlockOffset + characterNumber); + + const int CharacterSize = 64; + UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize)); + + const UInt32 ObjectCharacterDataOffset = 0x1_0000; + + if (characterOffset >= ObjectCharacterDataOffset) + continue; + + Byte colorNumber = Unsafe.Read((Byte*)_vram + characterOffset + characterPixelNumber); + + if (!isFirst && (colorNumber == 0)) + continue; + + UInt16 color = Unsafe.Read((UInt16*)_paletteRAM + colorNumber); + + int displayPixelNumber = displayPixelNumberBegin + hcount; + Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color; + } + } + } + + private void RenderObjects(UInt16 bgPriority) + { + ref UInt16 displayFrameBufferDataRef = ref MemoryMarshal.GetArrayDataReference(_displayFrameBuffer); + + int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth; + + UInt16 mappingFormat = (UInt16)((_DISPCNT >> 6) & 1); + + for (int objNumber = 127; objNumber >= 0; --objNumber) + { + unsafe + { + UInt16 attribute0 = Unsafe.Read((UInt16*)_oam + (objNumber * 4)); + UInt16 attribute1 = Unsafe.Read((UInt16*)_oam + (objNumber * 4) + 1); + UInt16 attribute2 = Unsafe.Read((UInt16*)_oam + (objNumber * 4) + 2); + + UInt16 shape = (UInt16)((attribute0 >> 14) & 0b11); + UInt16 colorMode = (UInt16)((attribute0 >> 13) & 1); + UInt16 rotationScalingFlag = (UInt16)((attribute0 >> 8) & 1); + UInt16 yCoordinate = (UInt16)(attribute0 & 0xff); + + UInt16 objSize = (UInt16)((attribute1 >> 14) & 0b11); + UInt16 verticalFlipFlag = (UInt16)((attribute1 >> 13) & 1); + UInt16 horizontalFlipFlag = (UInt16)((attribute1 >> 12) & 1); + UInt16 xCoordinate = (UInt16)(attribute1 & 0x1ff); + + UInt16 colorPalette = (UInt16)((attribute2 >> 12) & 0b1111); + UInt16 objPriority = (UInt16)((attribute2 >> 10) & 0b11); + UInt16 characterName = (UInt16)(attribute2 & 0x3ff); + + if (objPriority != bgPriority) + continue; + + (int objWidth, int objHeight) = (shape, objSize) switch + { + // square + (0b00, 0b00) => (8, 8), + (0b00, 0b01) => (16, 16), + (0b00, 0b10) => (32, 32), + (0b00, 0b11) => (64, 64), + + // horizontal rectangle + (0b01, 0b00) => (16, 8), + (0b01, 0b01) => (32, 8), + (0b01, 0b10) => (32, 16), + (0b01, 0b11) => (64, 32), + + // vertical rectangle + (0b10, 0b00) => (8, 16), + (0b10, 0b01) => (8, 32), + (0b10, 0b10) => (16, 32), + (0b10, 0b11) => (32, 64), + + // prohibited + // TODO: verify + _ => (0, 0) + }; + + const int VirtualScreenWidth = 512; + const int VirtualScreenHeight = 256; + + int left = xCoordinate; + int right = (xCoordinate + objWidth) % VirtualScreenWidth; + + int top = yCoordinate; + int bottom = (yCoordinate + objHeight) % VirtualScreenHeight; + + bool leftHidden = left >= DisplayScreenWidth; + bool rightHidden = right >= DisplayScreenWidth; + + bool topHidden = top >= DisplayScreenHeight; + bool bottomHidden = bottom >= DisplayScreenHeight; + + if ((leftHidden && rightHidden) || (topHidden && bottomHidden)) + continue; + + int hBegin; + int vBegin; + + if (leftHidden) + { + hBegin = VirtualScreenWidth - left; + left = 0; + } + else if (rightHidden) + { + hBegin = 0; + right = DisplayScreenWidth; + } + else + { + hBegin = 0; + } + + if (topHidden) + { + vBegin = VirtualScreenHeight - top; + top = 0; + } + else if (bottomHidden) + { + vBegin = 0; + bottom = DisplayScreenHeight; + } + else + { + vBegin = 0; + } + + if ((top > _VCOUNT) || (bottom <= _VCOUNT)) + continue; + + int v = _VCOUNT - top + vBegin; + + int characterV = v / CharacterHeight; + int characterPixelV = v % CharacterHeight; + + int characterNumberBegin; + int characterPixelNumberBegin = CharacterWidth; + + if (verticalFlipFlag == 0) + { + characterNumberBegin = characterV; + characterPixelNumberBegin *= characterPixelV; + } + else + { + characterNumberBegin = (objHeight / CharacterHeight) - 1 - characterV; + characterPixelNumberBegin *= CharacterHeight - 1 - characterPixelV; + } + + // 2D mapping + if (mappingFormat == 0) + characterNumberBegin *= (colorMode == 0) ? 32 : 16; + + // 1D mapping + else + characterNumberBegin *= objWidth / CharacterWidth; + + for (int hcount = left; hcount < right; ++hcount) + { + int h = hcount - left + hBegin; + + int characterH = h / CharacterWidth; + int characterPixelH = h % CharacterWidth; + + int characterNumber = characterNumberBegin; + int characterPixelNumber = characterPixelNumberBegin; + + if (horizontalFlipFlag == 0) + { + characterNumber += characterH; + characterPixelNumber += characterPixelH; + } + else + { + characterNumber += (objWidth / CharacterWidth) - 1 - characterH; + characterPixelNumber += CharacterWidth - 1 - characterPixelH; + } + + const UInt32 CharacterDataOffset = 0x1_0000; + const UInt32 PaletteOffset = 0x200; + + UInt16 color; + + // 16 colors x 16 palettes + if (colorMode == 0) + { + const int CharacterSize = 32; + Byte colorNumber = Unsafe.Read((Byte*)_vram + CharacterDataOffset + (characterName * 32) + (characterNumber * CharacterSize) + (characterPixelNumber / 2)); + + if ((characterPixelNumber % 2) == 0) + colorNumber &= 0b1111; + else + colorNumber >>= 4; + + if (colorNumber == 0) + continue; + + color = Unsafe.Read((Byte*)_paletteRAM + PaletteOffset + (colorPalette * 16 * 2) + (colorNumber * 2)); + } + + // 256 colors x 1 palette + else + { + const int CharacterSize = 64; + Byte colorNumber = Unsafe.Read((Byte*)_vram + CharacterDataOffset + (characterName * 32) + (characterNumber * CharacterSize) + characterPixelNumber); + + if (colorNumber == 0) + continue; + + color = Unsafe.Read((Byte*)_paletteRAM + PaletteOffset + (colorNumber * 2)); + } + + int displayPixelNumber = displayPixelNumberBegin + hcount; + Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color; + } + } + } + } + } +} diff --git a/Assets/Iris/Iris.GBA/Video.cs.meta b/Assets/Iris/Iris.GBA/Video.cs.meta new file mode 100644 index 0000000..956d1a2 --- /dev/null +++ b/Assets/Iris/Iris.GBA/Video.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c10b6bffb02e139458b969dbf72fc69b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.NDS.meta b/Assets/Iris/Iris.NDS.meta new file mode 100644 index 0000000..fc081ff --- /dev/null +++ b/Assets/Iris/Iris.NDS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 89f540181c8a126469d24bee76e92113 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.NDS/BIOS.cs b/Assets/Iris/Iris.NDS/BIOS.cs new file mode 100644 index 0000000..92ddf4d --- /dev/null +++ b/Assets/Iris/Iris.NDS/BIOS.cs @@ -0,0 +1,25 @@ +namespace Iris.NDS +{ + public sealed partial class NDS_System + { + private void BIOS_Reset() + { + const UInt32 ROMAddress = 0x0800_0000; + + // TODO + + _cpu.Reg[CPU.CPU_Core.PC] = ROMAddress; + _cpu.NextInstructionAddress = ROMAddress; + } + + private UInt64 HandleSWI() + { + throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleSWI unimplemented"); + } + + private UInt64 HandleIRQ() + { + throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleIRQ unimplemented"); + } + } +} diff --git a/Assets/Iris/Iris.NDS/BIOS.cs.meta b/Assets/Iris/Iris.NDS/BIOS.cs.meta new file mode 100644 index 0000000..7bd1ac7 --- /dev/null +++ b/Assets/Iris/Iris.NDS/BIOS.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65bfe93914138fb498b88080b1e38337 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.NDS/Memory.cs b/Assets/Iris/Iris.NDS/Memory.cs new file mode 100644 index 0000000..7e952ea --- /dev/null +++ b/Assets/Iris/Iris.NDS/Memory.cs @@ -0,0 +1,44 @@ +namespace Iris.NDS +{ + public sealed partial class NDS_System + { + private const int KB = 1024; + + private Byte[]? _ROM; + + public override void LoadROM(string filename) + { + _ROM = File.ReadAllBytes(filename); + } + + private Byte ReadMemory8(UInt32 address) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory8 unimplemented"); + } + + private UInt16 ReadMemory16(UInt32 address) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory16 unimplemented"); + } + + private UInt32 ReadMemory32(UInt32 address) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented"); + } + + private void WriteMemory8(UInt32 address, Byte value) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented"); + } + + private void WriteMemory16(UInt32 address, UInt16 value) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented"); + } + + private void WriteMemory32(UInt32 address, UInt32 value) + { + throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented"); + } + } +} diff --git a/Assets/Iris/Iris.NDS/Memory.cs.meta b/Assets/Iris/Iris.NDS/Memory.cs.meta new file mode 100644 index 0000000..7942601 --- /dev/null +++ b/Assets/Iris/Iris.NDS/Memory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 901a653d16f65ca4b906d7d0fb660a03 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.NDS/NDS_System.cs b/Assets/Iris/Iris.NDS/NDS_System.cs new file mode 100644 index 0000000..3e905a2 --- /dev/null +++ b/Assets/Iris/Iris.NDS/NDS_System.cs @@ -0,0 +1,73 @@ +namespace Iris.NDS +{ + public sealed partial class NDS_System : Common.System + { + private readonly CPU.CPU_Core _cpu; + private readonly PPU _ppu; + + private bool _running; + private bool _disposed; + + public NDS_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback) + { + CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(ReadMemory8, ReadMemory16, ReadMemory32, WriteMemory8, WriteMemory16, WriteMemory32, HandleSWI, HandleIRQ); + _cpu = new(CPU.CPU_Core.Model.ARM946ES, cpuCallbackInterface); + _ppu = new(presentFrameCallback); + } + + public override void Dispose() + { + if (_disposed) + return; + + // TODO + + _disposed = true; + } + + public override void ResetState(bool skipIntro) + { + BIOS_Reset(); + + _cpu.NIRQ = CPU.CPU_Core.Signal.High; + } + + public override void LoadState(BinaryReader reader) + { + // TODO + } + + public override void SaveState(BinaryWriter writer) + { + // TODO + } + + public override bool IsRunning() + { + return _running; + } + + public override void Run() + { + _running = true; + + while (_running) + { + UInt64 cycles = _cpu.Step(); + + for (UInt64 i = 0; i < cycles; ++i) + _ppu.Step(); + } + } + + public override void Pause() + { + _running = false; + } + + public override void SetKeyStatus(Key key, KeyStatus status) + { + // TODO + } + } +} diff --git a/Assets/Iris/Iris.NDS/NDS_System.cs.meta b/Assets/Iris/Iris.NDS/NDS_System.cs.meta new file mode 100644 index 0000000..7d2af6c --- /dev/null +++ b/Assets/Iris/Iris.NDS/NDS_System.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2233aca5b62bca346b553db0b0fcc482 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.NDS/PPU.cs b/Assets/Iris/Iris.NDS/PPU.cs new file mode 100644 index 0000000..5d92549 --- /dev/null +++ b/Assets/Iris/Iris.NDS/PPU.cs @@ -0,0 +1,19 @@ +namespace Iris.NDS +{ + public sealed class PPU + { + private const int KB = 1024; + + private readonly Common.System.PresentFrame_Delegate _presentFrameCallback; + + internal PPU(Common.System.PresentFrame_Delegate presentFrameCallback) + { + _presentFrameCallback = presentFrameCallback; + } + + internal void Step() + { + // TODO + } + } +} diff --git a/Assets/Iris/Iris.NDS/PPU.cs.meta b/Assets/Iris/Iris.NDS/PPU.cs.meta new file mode 100644 index 0000000..abaf307 --- /dev/null +++ b/Assets/Iris/Iris.NDS/PPU.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd011144397045948876bf23745e4a4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes.meta b/Assets/Scenes.meta new file mode 100644 index 0000000..83c741b --- /dev/null +++ b/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6ea315d0fd7389c41b19996891e99ae3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity new file mode 100644 index 0000000..2221b04 --- /dev/null +++ b/Assets/Scenes/SampleScene.unity @@ -0,0 +1,267 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInternal: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Scenes/SampleScene.unity.meta b/Assets/Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..952bd1e --- /dev/null +++ b/Assets/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: