From 3af171bdc35aceb358509c3bdfb76c878170adbf Mon Sep 17 00:00:00 2001 From: sin365 <353374337@qq.com> Date: Fri, 16 Aug 2024 11:06:40 +0800 Subject: [PATCH] =?UTF-8?q?OptimeGBA=20=E4=B8=80=E8=87=B4=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=BD=92=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Iris/Iris.CPU/ARM_Interpreter.cs | 2041 --------------- Assets/Iris/Iris.CPU/CPU_Core.cs | 600 ----- Assets/Iris/Iris.CPU/THUMB_Interpreter.cs | 1548 ------------ Assets/Iris/Iris.Common/Scheduler.cs | 152 -- Assets/Iris/Iris.Common/System.cs | 46 - Assets/Iris/Iris.GBA.meta | 8 - Assets/Iris/Iris.GBA/BIOS.cs | 119 - Assets/Iris/Iris.GBA/BIOS.cs.meta | 11 - Assets/Iris/Iris.GBA/Communication.cs | 250 -- Assets/Iris/Iris.GBA/Communication.cs.meta | 11 - Assets/Iris/Iris.GBA/DMA.cs | 479 ---- Assets/Iris/Iris.GBA/DMA.cs.meta | 11 - Assets/Iris/Iris.GBA/GBA_System.cs | 180 -- Assets/Iris/Iris.GBA/GBA_System.cs.meta | 11 - Assets/Iris/Iris.GBA/InterruptControl.cs | 127 - Assets/Iris/Iris.GBA/InterruptControl.cs.meta | 11 - Assets/Iris/Iris.GBA/KeyInput.cs | 179 -- Assets/Iris/Iris.GBA/KeyInput.cs.meta | 11 - Assets/Iris/Iris.GBA/Memory.cs | 2218 ----------------- Assets/Iris/Iris.GBA/Sound.cs | 351 --- Assets/Iris/Iris.GBA/Sound.cs.meta | 11 - Assets/Iris/Iris.GBA/SystemControl.cs | 64 - Assets/Iris/Iris.GBA/SystemControl.cs.meta | 11 - Assets/Iris/Iris.GBA/Timer.cs | 383 --- Assets/Iris/Iris.GBA/Timer.cs.meta | 11 - Assets/Iris/Iris.GBA/Video.cs | 1349 ---------- Assets/Iris/Iris.GBA/Video.cs.meta | 11 - Assets/Iris/Iris.NDS.meta | 8 - Assets/Iris/Iris.NDS/BIOS.cs | 27 - Assets/Iris/Iris.NDS/BIOS.cs.meta | 11 - Assets/Iris/Iris.NDS/Memory.cs | 47 - Assets/Iris/Iris.NDS/Memory.cs.meta | 11 - Assets/Iris/Iris.NDS/NDS_System.cs | 76 - Assets/Iris/Iris.NDS/NDS_System.cs.meta | 11 - Assets/Iris/Iris.NDS/PPU.cs | 19 - Assets/Iris/Iris.NDS/PPU.cs.meta | 11 - Assets/MyUnSafeCommon.cs | 253 ++ Assets/Plugins.meta | 2 +- Assets/Plugins/System.Buffers.dll.meta | 2 +- Assets/Plugins/System.Memory.dll.meta | 2 +- .../Plugins/System.Numerics.Vectors.dll.meta | 2 +- ...m.Runtime.CompilerServices.Unsafe.dll.meta | 2 +- Assets/Scenes.meta | 8 - Assets/Scenes/SampleScene.unity | 267 -- Assets/Scenes/SampleScene.unity.meta | 7 - Assets/Util.cs | 51 + ...THUMB_Interpreter.cs.meta => Util.cs.meta} | 2 +- Assets/{Iris.meta => emulator.meta} | 2 +- Assets/emulator/ApplySkipLocalsInit.cs | 1 + .../ApplySkipLocalsInit.cs.meta} | 2 +- Assets/emulator/BlipBuf.cs | 169 ++ .../BlipBuf.cs.meta} | 2 +- Assets/emulator/CartridgeNds.cs | 770 ++++++ .../CartridgeNds.cs.meta} | 2 +- Assets/emulator/CoreUtil.cs | 207 ++ Assets/emulator/CoreUtil.cs.meta | 11 + Assets/emulator/Cp15.cs | 86 + Assets/emulator/Cp15.cs.meta | 11 + Assets/emulator/Dma.cs | 29 + Assets/emulator/Dma.cs.meta | 11 + Assets/emulator/DmaGba.cs | 444 ++++ Assets/emulator/DmaGba.cs.meta | 11 + Assets/emulator/DmaNds.cs | 417 ++++ Assets/emulator/DmaNds.cs.meta | 11 + Assets/emulator/GbAudio.cs | 1186 +++++++++ Assets/emulator/GbAudio.cs.meta | 11 + Assets/emulator/Gba.cs | 218 ++ Assets/emulator/Gba.cs.meta | 11 + Assets/emulator/GbaAudio.cs | 483 ++++ Assets/emulator/GbaAudio.cs.meta | 11 + Assets/emulator/HwControl.cs | 14 + Assets/emulator/HwControl.cs.meta | 11 + Assets/emulator/HwControlGba.cs | 109 + Assets/emulator/HwControlGba.cs.meta | 11 + Assets/emulator/HwControlNds.cs | 150 ++ Assets/emulator/HwControlNds.cs.meta | 11 + Assets/emulator/Ipc.cs | 207 ++ Assets/emulator/Ipc.cs.meta | 11 + Assets/emulator/Keypad.cs | 61 + Assets/emulator/Keypad.cs.meta | 11 + Assets/emulator/Memory.cs | 207 ++ .../Iris.GBA => emulator}/Memory.cs.meta | 2 +- Assets/emulator/MemoryControlNds.cs | 157 ++ Assets/emulator/MemoryControlNds.cs.meta | 11 + Assets/emulator/MemoryGba.cs | 438 ++++ Assets/emulator/MemoryGba.cs.meta | 11 + Assets/emulator/MemoryNds7.cs | 449 ++++ Assets/emulator/MemoryNds7.cs.meta | 11 + Assets/emulator/MemoryNds9.cs | 622 +++++ Assets/emulator/MemoryNds9.cs.meta | 11 + Assets/emulator/MemoryUtil.cs | 179 ++ Assets/emulator/MemoryUtil.cs.meta | 11 + Assets/emulator/Nds.cs | 396 +++ Assets/emulator/Nds.cs.meta | 11 + Assets/emulator/Nds9Math.cs | 276 ++ Assets/emulator/Nds9Math.cs.meta | 11 + Assets/emulator/NdsAudio.cs | 525 ++++ Assets/emulator/NdsAudio.cs.meta | 11 + Assets/emulator/Ppu.cs | 261 ++ Assets/emulator/Ppu.cs.meta | 11 + Assets/emulator/PpuGba.cs | 296 +++ Assets/emulator/PpuGba.cs.meta | 11 + Assets/emulator/PpuNds.cs | 925 +++++++ Assets/emulator/PpuNds.cs.meta | 11 + Assets/emulator/PpuNds3D.cs | 1016 ++++++++ Assets/emulator/PpuNds3D.cs.meta | 11 + Assets/emulator/PpuRenderer.cs | 1707 +++++++++++++ Assets/emulator/PpuRenderer.cs.meta | 11 + Assets/emulator/Provider.cs | 10 + Assets/emulator/Provider.cs.meta | 11 + Assets/emulator/ProviderGba.cs | 28 + Assets/emulator/ProviderGba.cs.meta | 11 + Assets/emulator/ProviderNds.cs | 26 + Assets/emulator/ProviderNds.cs.meta | 11 + Assets/emulator/RtcNds.cs | 162 ++ Assets/emulator/RtcNds.cs.meta | 11 + Assets/emulator/Scheduler.cs | 198 ++ .../Scheduler.cs.meta | 2 +- Assets/emulator/Soundgoodizer.cs | 176 ++ Assets/emulator/Soundgoodizer.cs.meta | 11 + Assets/emulator/Spi.cs | 183 ++ Assets/emulator/Spi.cs.meta | 11 + Assets/emulator/SpiFlash.cs | 110 + Assets/emulator/SpiFlash.cs.meta | 11 + Assets/emulator/Timers.cs | 264 ++ Assets/emulator/Timers.cs.meta | 11 + Assets/emulator/WavWriter.cs | 106 + Assets/emulator/WavWriter.cs.meta | 11 + .../{Iris/Iris.CPU.meta => emulator/cpu.meta} | 2 +- Assets/emulator/cpu/Arm.cs | 1924 ++++++++++++++ Assets/emulator/cpu/Arm.cs.meta | 11 + Assets/emulator/cpu/Arm7.cs | 1265 ++++++++++ Assets/emulator/cpu/Arm7.cs.meta | 11 + Assets/emulator/cpu/Thumb.cs | 1346 ++++++++++ Assets/emulator/cpu/Thumb.cs.meta | 11 + .../Iris.Common.meta => emulator/saving.meta} | 2 +- Assets/emulator/saving/Eeprom.cs | 197 ++ Assets/emulator/saving/Eeprom.cs.meta | 11 + Assets/emulator/saving/Flash.cs | 223 ++ Assets/emulator/saving/Flash.cs.meta | 11 + Assets/emulator/saving/NullSaveProvider.cs | 27 + .../emulator/saving/NullSaveProvider.cs.meta | 11 + Assets/emulator/saving/SaveProvider.cs | 11 + Assets/emulator/saving/SaveProvider.cs.meta | 11 + Assets/emulator/saving/Sram.cs | 39 + Assets/emulator/saving/Sram.cs.meta | 11 + 146 files changed, 19091 insertions(+), 10721 deletions(-) delete mode 100644 Assets/Iris/Iris.CPU/ARM_Interpreter.cs delete mode 100644 Assets/Iris/Iris.CPU/CPU_Core.cs delete mode 100644 Assets/Iris/Iris.CPU/THUMB_Interpreter.cs delete mode 100644 Assets/Iris/Iris.Common/Scheduler.cs delete mode 100644 Assets/Iris/Iris.Common/System.cs delete mode 100644 Assets/Iris/Iris.GBA.meta delete mode 100644 Assets/Iris/Iris.GBA/BIOS.cs delete mode 100644 Assets/Iris/Iris.GBA/BIOS.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/Communication.cs delete mode 100644 Assets/Iris/Iris.GBA/Communication.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/DMA.cs delete mode 100644 Assets/Iris/Iris.GBA/DMA.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/GBA_System.cs delete mode 100644 Assets/Iris/Iris.GBA/GBA_System.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/InterruptControl.cs delete mode 100644 Assets/Iris/Iris.GBA/InterruptControl.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/KeyInput.cs delete mode 100644 Assets/Iris/Iris.GBA/KeyInput.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/Memory.cs delete mode 100644 Assets/Iris/Iris.GBA/Sound.cs delete mode 100644 Assets/Iris/Iris.GBA/Sound.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/SystemControl.cs delete mode 100644 Assets/Iris/Iris.GBA/SystemControl.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/Timer.cs delete mode 100644 Assets/Iris/Iris.GBA/Timer.cs.meta delete mode 100644 Assets/Iris/Iris.GBA/Video.cs delete mode 100644 Assets/Iris/Iris.GBA/Video.cs.meta delete mode 100644 Assets/Iris/Iris.NDS.meta delete mode 100644 Assets/Iris/Iris.NDS/BIOS.cs delete mode 100644 Assets/Iris/Iris.NDS/BIOS.cs.meta delete mode 100644 Assets/Iris/Iris.NDS/Memory.cs delete mode 100644 Assets/Iris/Iris.NDS/Memory.cs.meta delete mode 100644 Assets/Iris/Iris.NDS/NDS_System.cs delete mode 100644 Assets/Iris/Iris.NDS/NDS_System.cs.meta delete mode 100644 Assets/Iris/Iris.NDS/PPU.cs delete mode 100644 Assets/Iris/Iris.NDS/PPU.cs.meta delete mode 100644 Assets/Scenes.meta delete mode 100644 Assets/Scenes/SampleScene.unity delete mode 100644 Assets/Scenes/SampleScene.unity.meta create mode 100644 Assets/Util.cs rename Assets/{Iris/Iris.CPU/THUMB_Interpreter.cs.meta => Util.cs.meta} (83%) rename Assets/{Iris.meta => emulator.meta} (77%) create mode 100644 Assets/emulator/ApplySkipLocalsInit.cs rename Assets/{Iris/Iris.Common/System.cs.meta => emulator/ApplySkipLocalsInit.cs.meta} (83%) create mode 100644 Assets/emulator/BlipBuf.cs rename Assets/{Iris/Iris.CPU/CPU_Core.cs.meta => emulator/BlipBuf.cs.meta} (83%) create mode 100644 Assets/emulator/CartridgeNds.cs rename Assets/{Iris/Iris.CPU/ARM_Interpreter.cs.meta => emulator/CartridgeNds.cs.meta} (83%) create mode 100644 Assets/emulator/CoreUtil.cs create mode 100644 Assets/emulator/CoreUtil.cs.meta create mode 100644 Assets/emulator/Cp15.cs create mode 100644 Assets/emulator/Cp15.cs.meta create mode 100644 Assets/emulator/Dma.cs create mode 100644 Assets/emulator/Dma.cs.meta create mode 100644 Assets/emulator/DmaGba.cs create mode 100644 Assets/emulator/DmaGba.cs.meta create mode 100644 Assets/emulator/DmaNds.cs create mode 100644 Assets/emulator/DmaNds.cs.meta create mode 100644 Assets/emulator/GbAudio.cs create mode 100644 Assets/emulator/GbAudio.cs.meta create mode 100644 Assets/emulator/Gba.cs create mode 100644 Assets/emulator/Gba.cs.meta create mode 100644 Assets/emulator/GbaAudio.cs create mode 100644 Assets/emulator/GbaAudio.cs.meta create mode 100644 Assets/emulator/HwControl.cs create mode 100644 Assets/emulator/HwControl.cs.meta create mode 100644 Assets/emulator/HwControlGba.cs create mode 100644 Assets/emulator/HwControlGba.cs.meta create mode 100644 Assets/emulator/HwControlNds.cs create mode 100644 Assets/emulator/HwControlNds.cs.meta create mode 100644 Assets/emulator/Ipc.cs create mode 100644 Assets/emulator/Ipc.cs.meta create mode 100644 Assets/emulator/Keypad.cs create mode 100644 Assets/emulator/Keypad.cs.meta create mode 100644 Assets/emulator/Memory.cs rename Assets/{Iris/Iris.GBA => emulator}/Memory.cs.meta (83%) create mode 100644 Assets/emulator/MemoryControlNds.cs create mode 100644 Assets/emulator/MemoryControlNds.cs.meta create mode 100644 Assets/emulator/MemoryGba.cs create mode 100644 Assets/emulator/MemoryGba.cs.meta create mode 100644 Assets/emulator/MemoryNds7.cs create mode 100644 Assets/emulator/MemoryNds7.cs.meta create mode 100644 Assets/emulator/MemoryNds9.cs create mode 100644 Assets/emulator/MemoryNds9.cs.meta create mode 100644 Assets/emulator/MemoryUtil.cs create mode 100644 Assets/emulator/MemoryUtil.cs.meta create mode 100644 Assets/emulator/Nds.cs create mode 100644 Assets/emulator/Nds.cs.meta create mode 100644 Assets/emulator/Nds9Math.cs create mode 100644 Assets/emulator/Nds9Math.cs.meta create mode 100644 Assets/emulator/NdsAudio.cs create mode 100644 Assets/emulator/NdsAudio.cs.meta create mode 100644 Assets/emulator/Ppu.cs create mode 100644 Assets/emulator/Ppu.cs.meta create mode 100644 Assets/emulator/PpuGba.cs create mode 100644 Assets/emulator/PpuGba.cs.meta create mode 100644 Assets/emulator/PpuNds.cs create mode 100644 Assets/emulator/PpuNds.cs.meta create mode 100644 Assets/emulator/PpuNds3D.cs create mode 100644 Assets/emulator/PpuNds3D.cs.meta create mode 100644 Assets/emulator/PpuRenderer.cs create mode 100644 Assets/emulator/PpuRenderer.cs.meta create mode 100644 Assets/emulator/Provider.cs create mode 100644 Assets/emulator/Provider.cs.meta create mode 100644 Assets/emulator/ProviderGba.cs create mode 100644 Assets/emulator/ProviderGba.cs.meta create mode 100644 Assets/emulator/ProviderNds.cs create mode 100644 Assets/emulator/ProviderNds.cs.meta create mode 100644 Assets/emulator/RtcNds.cs create mode 100644 Assets/emulator/RtcNds.cs.meta create mode 100644 Assets/emulator/Scheduler.cs rename Assets/{Iris/Iris.Common => emulator}/Scheduler.cs.meta (83%) create mode 100644 Assets/emulator/Soundgoodizer.cs create mode 100644 Assets/emulator/Soundgoodizer.cs.meta create mode 100644 Assets/emulator/Spi.cs create mode 100644 Assets/emulator/Spi.cs.meta create mode 100644 Assets/emulator/SpiFlash.cs create mode 100644 Assets/emulator/SpiFlash.cs.meta create mode 100644 Assets/emulator/Timers.cs create mode 100644 Assets/emulator/Timers.cs.meta create mode 100644 Assets/emulator/WavWriter.cs create mode 100644 Assets/emulator/WavWriter.cs.meta rename Assets/{Iris/Iris.CPU.meta => emulator/cpu.meta} (77%) create mode 100644 Assets/emulator/cpu/Arm.cs create mode 100644 Assets/emulator/cpu/Arm.cs.meta create mode 100644 Assets/emulator/cpu/Arm7.cs create mode 100644 Assets/emulator/cpu/Arm7.cs.meta create mode 100644 Assets/emulator/cpu/Thumb.cs create mode 100644 Assets/emulator/cpu/Thumb.cs.meta rename Assets/{Iris/Iris.Common.meta => emulator/saving.meta} (77%) create mode 100644 Assets/emulator/saving/Eeprom.cs create mode 100644 Assets/emulator/saving/Eeprom.cs.meta create mode 100644 Assets/emulator/saving/Flash.cs create mode 100644 Assets/emulator/saving/Flash.cs.meta create mode 100644 Assets/emulator/saving/NullSaveProvider.cs create mode 100644 Assets/emulator/saving/NullSaveProvider.cs.meta create mode 100644 Assets/emulator/saving/SaveProvider.cs create mode 100644 Assets/emulator/saving/SaveProvider.cs.meta create mode 100644 Assets/emulator/saving/Sram.cs create mode 100644 Assets/emulator/saving/Sram.cs.meta diff --git a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs b/Assets/Iris/Iris.CPU/ARM_Interpreter.cs deleted file mode 100644 index d9b46ce..0000000 --- a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs +++ /dev/null @@ -1,2041 +0,0 @@ -using System; -using System.Collections.Generic; -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 InstructionListEntry(0x0fe0_0000, 0x02a0_0000, &ADC, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x00a0_0000, &ADC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00a0_0080, &ADC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00a0_0010, &ADC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - // ADD - new InstructionListEntry(0x0fe0_0000, 0x0280_0000, &ADD, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0080_0000, &ADD, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0080_0080, &ADD, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0080_0010, &ADD, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // AND - new InstructionListEntry(0x0fe0_0000, 0x0200_0000, &AND, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0000_0000, &AND, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0000_0080, &AND, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0000_0010, &AND, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // B - new InstructionListEntry(0x0f00_0000, 0x0a00_0000, &B, new List(){ Model.ARM7TDMI}), - - // BL - new InstructionListEntry(0x0f00_0000, 0x0b00_0000, &BL, new List(){ Model.ARM7TDMI}), - - // BIC - new InstructionListEntry(0x0fe0_0000, 0x03c0_0000, &BIC, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x01c0_0000, &BIC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x01c0_0080, &BIC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x01c0_0010, &BIC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // BX - new InstructionListEntry(0x0fff_fff0, 0x012f_ff10, &BX, new List(){ Model.ARM7TDMI}), - - // CMN - new InstructionListEntry(0x0ff0_f000, 0x0370_0000, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0170_0000, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0170_0080, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0170_0010, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - new InstructionListEntry(0x0ff0_f000, 0x0370_f000, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0170_f000, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0170_f080, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0170_f010, &CMN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // CMP - new InstructionListEntry(0x0ff0_f000, 0x0350_0000, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0150_0000, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0150_0080, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0150_0010, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - new InstructionListEntry(0x0ff0_f000, 0x0350_f000, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0150_f000, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0150_f080, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0150_f010, &CMP, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // EOR - new InstructionListEntry(0x0fe0_0000, 0x0220_0000, &EOR, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0020_0000, &EOR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0020_0080, &EOR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0020_0010, &EOR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // LDM - new InstructionListEntry(0x0e50_0000, 0x0810_0000, &LDM1, new List(){ Model.ARM7TDMI}), - new InstructionListEntry(0x0e50_8000, 0x0850_0000, &LDM2, new List(){ Model.ARM7TDMI}), - //new InstructionListEntry(0x0e50_8000, 0x0850_8000, &LDM3, new List{ Model.ARM7TDMI }), - - // LDR - new InstructionListEntry(0x0c50_0000, 0x0410_0000, &LDR, new List(){ Model.ARM7TDMI}), - - // LDRB - new InstructionListEntry(0x0c50_0000, 0x0450_0000, &LDRB, new List(){ Model.ARM7TDMI}), - - // LDRH - new InstructionListEntry(0x0e10_00f0, 0x0010_00b0, &LDRH, new List(){ Model.ARM7TDMI}), - - // LDRSB - new InstructionListEntry(0x0e10_00f0, 0x0010_00d0, &LDRSB, new List(){ Model.ARM7TDMI}), - - // LDRSH - new InstructionListEntry(0x0e10_00f0, 0x0010_00f0, &LDRSH, new List(){ Model.ARM7TDMI}), - - // MLA - new InstructionListEntry(0x0fe0_00f0, 0x0020_0090, &MLA, new List(){ Model.ARM7TDMI}), - - // MOV - new InstructionListEntry(0x0fef_0000, 0x03a0_0000, &MOV, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fef_0090, 0x01a0_0000, &MOV, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fef_0090, 0x01a0_0080, &MOV, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fef_0090, 0x01a0_0010, &MOV, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // MRS - new InstructionListEntry(0x0fbf_0fff, 0x010f_0000, &MRS, new List(){ Model.ARM7TDMI}), - - // MSR - new InstructionListEntry(0x0fb0_f000, 0x0320_f000, &MSR, new List(){ Model.ARM7TDMI}), // Immediate operand - new InstructionListEntry(0x0fb0_fff0, 0x0120_f000, &MSR, new List(){ Model.ARM7TDMI}), // Register operand - - // MUL - new InstructionListEntry(0x0fe0_f0f0, 0x0000_0090, &MUL, new List(){ Model.ARM7TDMI}), - - // MVN - new InstructionListEntry(0x0fef_0000, 0x03e0_0000, &MVN, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fef_0090, 0x01e0_0000, &MVN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fef_0090, 0x01e0_0080, &MVN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fef_0090, 0x01e0_0010, &MVN, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // ORR - new InstructionListEntry(0x0fe0_0000, 0x0380_0000, &ORR, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0180_0000, &ORR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0180_0080, &ORR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0180_0010, &ORR, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // RSB - new InstructionListEntry(0x0fe0_0000, 0x0260_0000, &RSB, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0060_0000, &RSB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0060_0080, &RSB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0060_0010, &RSB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // RSC - new InstructionListEntry(0x0fe0_0000, 0x02e0_0000, &RSC, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x00e0_0000, &RSC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00e0_0080, &RSC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00e0_0010, &RSC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // SBC - new InstructionListEntry(0x0fe0_0000, 0x02c0_0000, &SBC, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x00c0_0000, &SBC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00c0_0080, &SBC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x00c0_0010, &SBC, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // SMLAL - new InstructionListEntry(0x0fe0_00f0, 0x00e0_0090, &SMLAL, new List(){ Model.ARM7TDMI}), - - // SMULL - new InstructionListEntry(0x0fe0_00f0, 0x00c0_0090, &SMULL, new List(){ Model.ARM7TDMI}), - - // STM - new InstructionListEntry(0x0e50_0000, 0x0800_0000, &STM1, new List(){ Model.ARM7TDMI}), - new InstructionListEntry(0x0e70_0000, 0x0840_0000, &STM2, new List(){ Model.ARM7TDMI}), - - // STR - new InstructionListEntry(0x0c50_0000, 0x0400_0000, &STR, new List(){ Model.ARM7TDMI}), - - // STRB - new InstructionListEntry(0x0c50_0000, 0x0440_0000, &STRB, new List(){ Model.ARM7TDMI}), - - // STRH - new InstructionListEntry(0x0e10_00f0, 0x0000_00b0, &STRH, new List(){ Model.ARM7TDMI}), - - // SUB - new InstructionListEntry(0x0fe0_0000, 0x0240_0000, &SUB, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0fe0_0090, 0x0040_0000, &SUB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0040_0080, &SUB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0fe0_0090, 0x0040_0010, &SUB, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // SWI - new InstructionListEntry(0x0f00_0000, 0x0f00_0000, &SWI, new List(){ Model.ARM7TDMI}), - - // SWP - new InstructionListEntry(0x0ff0_0ff0, 0x0100_0090, &SWP, new List(){ Model.ARM7TDMI}), - - // SWPB - new InstructionListEntry(0x0ff0_0ff0, 0x0140_0090, &SWPB, new List(){ Model.ARM7TDMI}), - - // TEQ - new InstructionListEntry(0x0ff0_f000, 0x0330_0000, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0130_0000, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0130_0080, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0130_0010, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - new InstructionListEntry(0x0ff0_f000, 0x0330_f000, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0130_f000, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0130_f080, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0130_f010, &TEQ, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // TST - new InstructionListEntry(0x0ff0_f000, 0x0310_0000, &TST, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0110_0000, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0110_0080, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0110_0010, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - new InstructionListEntry(0x0ff0_f000, 0x0310_f000, &TST, new List(){ Model.ARM7TDMI}), // I bit is 1 - new InstructionListEntry(0x0ff0_f090, 0x0110_f000, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0110_f080, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 1 and bit[4] is 0 - new InstructionListEntry(0x0ff0_f090, 0x0110_f010, &TST, new List(){ Model.ARM7TDMI}), // I bit is 0, bit[7] is 0 and bit[4] is 1 - - // UMLAL - new InstructionListEntry(0x0fe0_00f0, 0x00a0_0090, &UMLAL, new List(){ Model.ARM7TDMI}), - - // UMULL - new InstructionListEntry(0x0fe0_00f0, 0x0080_0090, &UMULL, new List(){ 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 MyUnSafeCommon.GetArrayDataReference(_cpu.Reg); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)PC); - - regPC = _cpu.NextInstructionAddress + 4; - - ref InstructionLUTEntry instructionLUTDataRef = ref MyUnSafeCommon.GetArrayDataReference(_instructionLUT); - ref InstructionLUTEntry instructionLUTEntry = ref Unsafe.Add(ref instructionLUTDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.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, (int)rs); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - - UInt32 increment = (registerList == 0) ? 0x40 : ((UInt32)MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, (int)LR); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - - for (int i = 0; i <= 14; ++i) - { - if (((registerList >> i) & 1) == 1) - { - ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, (int)i); - - regRi = cpu._callbackInterface._read32(address); - address += 4; - } - } - - UInt32 n = (UInt32)MyBitOperations.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 MyUnSafeCommon.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, (int)i); - - regRi = value; - break; - } - } - - address += 4; - } - } - - UInt32 n = (UInt32)MyBitOperations.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 = MyBitOperations.RotateRight(cpu._callbackInterface._read32(address), (int)(8 * (address & 0b11))); - - if (rd == PC) - { - SetPC(cpu, data & 0xffff_fffc); - - return 5; - } - else - { - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - - UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); - SetReg(cpu, rd, (regRm * regRs) + regRn); - - if (s == 1) - { - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.RotateRight(imm, (int)(2 * rotateImm)); - } - else - { - UInt32 rm = instruction & 0b1111; - - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - - UInt64 m = ComputeMultiplicationCycleCount(regRm, regRs); - SetReg(cpu, rd, regRm * regRs); - - if (s == 1) - { - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, (int)rdHi); - ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, (int)rdLo); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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, (int)rdHi); - ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - - UInt32 oldRegRn = regRn; - - (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); - - UInt32 address = startAddress; - - if (registerList == 0) - { - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)i); - - cpu._callbackInterface._write32(address, regRi); - } - - address += 4; - } - } - - if (((registerList >> 15) & 1) == 1) - { - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)PC); - - cpu._callbackInterface._write32(address, regPC + 4); - } - - UInt32 n = (UInt32)MyBitOperations.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 MyUnSafeCommon.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, (int)rn), - }; - - (UInt32 startAddress, _) = GetAddress_Multiple(cpu, instruction); - - UInt32 address = startAddress; - - if (registerList == 0) - { - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)PC); - - cpu._callbackInterface._write32(address, regPC + 4); - } - - UInt32 n = (UInt32)MyBitOperations.PopCount(registerList); - return n + 1; - } - } - - private static UInt64 STR(CPU_Core cpu, UInt32 instruction) - { - UInt32 rd = (instruction >> 12) & 0b1111; - - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - - UInt32 temp = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRdHi = ref Unsafe.Add(ref regDataRef, (int)rdHi); - ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, (int)rdLo); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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, (int)rdHi); - ref UInt32 regRdLo = ref Unsafe.Add(ref regDataRef, (int)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/CPU_Core.cs b/Assets/Iris/Iris.CPU/CPU_Core.cs deleted file mode 100644 index 6a76c07..0000000 --- a/Assets/Iris/Iris.CPU/CPU_Core.cs +++ /dev/null @@ -1,600 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.CompilerServices; - -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 InstructionListEntry1(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 InstructionListEntry - { - internal InstructionListEntry(T mask, T expected, delegate* handler, List modelList) - { - _mask = mask; - _expected = expected; - _handler = handler; - _modelList = modelList; - } - - internal readonly T _mask; - internal readonly T _expected; - internal unsafe readonly delegate* _handler; - internal readonly List _modelList; - } - - //internal unsafe readonly struct InstructionLUTEntry(delegate* handler) - //{ - // internal unsafe readonly delegate* _handler = handler; - //} - - - internal unsafe readonly struct InstructionLUTEntry - { - internal InstructionLUTEntry(delegate* handler) - { - _handler = handler; - } - internal unsafe readonly delegate* _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 MyUnSafeCommon.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/THUMB_Interpreter.cs b/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs deleted file mode 100644 index 2660892..0000000 --- a/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs +++ /dev/null @@ -1,1548 +0,0 @@ -using System; -using System.Collections.Generic; -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 InstructionListEntry(0xffc0, 0x4140, &ADC, new List{Model.ARM7TDMI}), - - // ADD - new InstructionListEntry(0xfe00, 0x1c00, &ADD1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0x3000, &ADD2, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x1800, &ADD3, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xff00, 0x4400, &ADD4, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0xa000, &ADD5, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0xa800, &ADD6, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xff80, 0xb000, &ADD7, new List{Model.ARM7TDMI}), - - // AND - new InstructionListEntry(0xffc0, 0x4000, &AND, new List{Model.ARM7TDMI}), - - // ASR - new InstructionListEntry(0xf800, 0x1000, &ASR1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xffc0, 0x4100, &ASR2, new List{Model.ARM7TDMI}), - - // B - new InstructionListEntry(0xff00, 0xd000, &B1, new List{Model.ARM7TDMI}), // condition field 0b0000 - new InstructionListEntry(0xff00, 0xd100, &B1, new List{Model.ARM7TDMI}), // condition field 0b0001 - new InstructionListEntry(0xff00, 0xd200, &B1, new List{Model.ARM7TDMI}), // condition field 0b0010 - new InstructionListEntry(0xff00, 0xd300, &B1, new List{Model.ARM7TDMI}), // condition field 0b0011 - new InstructionListEntry(0xff00, 0xd400, &B1, new List{Model.ARM7TDMI}), // condition field 0b0100 - new InstructionListEntry(0xff00, 0xd500, &B1, new List{Model.ARM7TDMI}), // condition field 0b0101 - new InstructionListEntry(0xff00, 0xd600, &B1, new List{Model.ARM7TDMI}), // condition field 0b0110 - new InstructionListEntry(0xff00, 0xd700, &B1, new List{Model.ARM7TDMI}), // condition field 0b0111 - new InstructionListEntry(0xff00, 0xd800, &B1, new List{Model.ARM7TDMI}), // condition field 0b1000 - new InstructionListEntry(0xff00, 0xd900, &B1, new List{Model.ARM7TDMI}), // condition field 0b1001 - new InstructionListEntry(0xff00, 0xda00, &B1, new List{Model.ARM7TDMI}), // condition field 0b1010 - new InstructionListEntry(0xff00, 0xdb00, &B1, new List{Model.ARM7TDMI}), // condition field 0b1011 - new InstructionListEntry(0xff00, 0xdc00, &B1, new List{Model.ARM7TDMI}), // condition field 0b1100 - new InstructionListEntry(0xff00, 0xdd00, &B1, new List{Model.ARM7TDMI}), // condition field 0b1101 - new InstructionListEntry(0xf800, 0xe000, &B2, new List{Model.ARM7TDMI}), - - // BIC - new InstructionListEntry(0xffc0, 0x4380, &BIC, new List{Model.ARM7TDMI}), - - // BL - new InstructionListEntry(0xf000, 0xf000, &BL, new List{Model.ARM7TDMI}), - - // BX - new InstructionListEntry(0xff80, 0x4700, &BX, new List{Model.ARM7TDMI}), - - // CMN - new InstructionListEntry(0xffc0, 0x42c0, &CMN, new List{Model.ARM7TDMI}), - - // CMP - new InstructionListEntry(0xf800, 0x2800, &CMP1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xffc0, 0x4280, &CMP2, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xff00, 0x4500, &CMP3, new List{Model.ARM7TDMI}), - - // EOR - new InstructionListEntry(0xffc0, 0x4040, &EOR, new List{Model.ARM7TDMI}), - - // LDMIA - new InstructionListEntry(0xf800, 0xc800, &LDMIA, new List{Model.ARM7TDMI}), - - // LDR - new InstructionListEntry(0xf800, 0x6800, &LDR1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5800, &LDR2, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0x4800, &LDR3, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0x9800, &LDR4, new List{Model.ARM7TDMI}), - - // LDRB - new InstructionListEntry(0xf800, 0x7800, &LDRB1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5c00, &LDRB2, new List{Model.ARM7TDMI}), - - // LDRH - new InstructionListEntry(0xf800, 0x8800, &LDRH1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5a00, &LDRH2, new List{Model.ARM7TDMI}), - - // LDRSB - new InstructionListEntry(0xfe00, 0x5600, &LDRSB, new List{Model.ARM7TDMI}), - - // LDRSH - new InstructionListEntry(0xfe00, 0x5e00, &LDRSH, new List{Model.ARM7TDMI}), - - // LSL - new InstructionListEntry(0xf800, 0x0000, &LSL1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xffc0, 0x4080, &LSL2, new List{Model.ARM7TDMI}), - - // LSR - new InstructionListEntry(0xf800, 0x0800, &LSR1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xffc0, 0x40c0, &LSR2, new List{Model.ARM7TDMI}), - - // MOV - new InstructionListEntry(0xf800, 0x2000, &MOV1, new List{Model.ARM7TDMI}), - //new InstructionListEntry(0xffc0, 0x1c00, &MOV2, new List{ Model.ARM7TDMI }), - new InstructionListEntry(0xff00, 0x4600, &MOV3, new List{Model.ARM7TDMI}), - - // MUL - new InstructionListEntry(0xffc0, 0x4340, &MUL, new List{Model.ARM7TDMI}), - - // MVN - new InstructionListEntry(0xffc0, 0x43c0, &MVN, new List{Model.ARM7TDMI}), - - // NEG - new InstructionListEntry(0xffc0, 0x4240, &NEG, new List{Model.ARM7TDMI}), - - // ORR - new InstructionListEntry(0xffc0, 0x4300, &ORR, new List{Model.ARM7TDMI}), - - // POP - new InstructionListEntry(0xfe00, 0xbc00, &POP, new List{Model.ARM7TDMI}), - - // PUSH - new InstructionListEntry(0xfe00, 0xb400, &PUSH, new List{Model.ARM7TDMI}), - - // ROR - new InstructionListEntry(0xffc0, 0x41c0, &ROR, new List{Model.ARM7TDMI}), - - // SBC - new InstructionListEntry(0xffc0, 0x4180, &SBC, new List{Model.ARM7TDMI}), - - // STMIA - new InstructionListEntry(0xf800, 0xc000, &STMIA, new List{Model.ARM7TDMI}), - - // STR - new InstructionListEntry(0xf800, 0x6000, &STR1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5000, &STR2, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0x9000, &STR3, new List{Model.ARM7TDMI}), - - // STRB - new InstructionListEntry(0xf800, 0x7000, &STRB1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5400, &STRB2, new List{Model.ARM7TDMI}), - - // STRH - new InstructionListEntry(0xf800, 0x8000, &STRH1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x5200, &STRH2, new List{Model.ARM7TDMI}), - - // SUB - new InstructionListEntry(0xfe00, 0x1e00, &SUB1, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xf800, 0x3800, &SUB2, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xfe00, 0x1a00, &SUB3, new List{Model.ARM7TDMI}), - new InstructionListEntry(0xff80, 0xb080, &SUB4, new List{Model.ARM7TDMI}), - - // SWI - new InstructionListEntry(0xff00, 0xdf00, &SWI, new List{Model.ARM7TDMI}), - - // TST - new InstructionListEntry(0xffc0, 0x4200, &TST, new List{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 MyUnSafeCommon.GetArrayDataReference(_cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(_cpu.Reg); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)PC); - - regPC = _cpu.NextInstructionAddress + 2; - - //ref InstructionLUTEntry instructionLUTDataRef = ref MyUnSafeCommon.GetArrayDataReference(_instructionLUT); - ref InstructionLUTEntry instructionLUTDataRef = ref MyUnSafeCommon.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRi = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, (int)LR); - - if (h == 0b10) - { - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - - UInt32 address = regRn; - - if (registerList == 0) - { - regRn += 0x40; - SetPC(cpu, cpu._callbackInterface._read32(address)); - - return 5; - } - else - { - UInt32 n = (UInt32)MyBitOperations.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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - - UInt32 address = regRn + (imm * 4u); - UInt32 data = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - - UInt32 address = regRn + regRm; - UInt32 data = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)SP); - - UInt32 address = regSP + (imm * 4u); - UInt32 data = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - - UInt32 address = regRn + (imm * 2u); - UInt32 data = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - - UInt32 address = regRn + regRm; - UInt32 data = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)SP); - - UInt32 n = (UInt32)MyBitOperations.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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)SP); - - UInt32 n = (UInt32)MyBitOperations.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, (int)i); - - cpu._callbackInterface._write32(address, regRi); - address += 4; - } - } - - if (r == 1) - { - ref UInt32 regLR = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRs = ref Unsafe.Add(ref regDataRef, (int)rs); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 = MyBitOperations.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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - - UInt32 address = regRn; - - if (registerList == 0) - { - ref UInt32 regPC = ref Unsafe.Add(ref regDataRef, (int)PC); - - regRn += 0x40; - cpu._callbackInterface._write32(address, regPC + 2); - - return 2; - } - else - { - UInt32 n = (UInt32)MyBitOperations.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, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)rd); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)rn); - ref UInt32 regRd = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regSP = ref Unsafe.Add(ref regDataRef, (int)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 MyUnSafeCommon.GetArrayDataReference(cpu.Reg); - ref UInt32 regRm = ref Unsafe.Add(ref regDataRef, (int)rm); - ref UInt32 regRn = ref Unsafe.Add(ref regDataRef, (int)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.Common/Scheduler.cs b/Assets/Iris/Iris.Common/Scheduler.cs deleted file mode 100644 index e760202..0000000 --- a/Assets/Iris/Iris.Common/Scheduler.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Iris.Common -{ - public sealed class Scheduler - { - public Scheduler(int taskListSize, int scheduledTaskListSize) - { - _taskList = new Task_Delegate[taskListSize]; - _scheduledTaskList = new ScheduledTaskListEntry[scheduledTaskListSize]; - } - public delegate void Task_Delegate(UInt64 cycleCountDelay); - private readonly Task_Delegate[] _taskList; - //private readonly Task_Delegate[] _taskList = new Task_Delegate[taskListSize]; - - private struct ScheduledTaskListEntry - { - internal int _id; - internal UInt64 _cycleCount; - } - - private readonly ScheduledTaskListEntry[] _scheduledTaskList; // 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 UnSafeCommon.GetArrayDataReference(_scheduledTaskList); - //ref Task_Delegate taskListDataRef = ref UnSafeCommon.GetArrayDataReference(_taskList); - - ref readonly ScheduledTaskListEntry firstEntry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList); - ref Task_Delegate taskListDataRef = ref MyUnSafeCommon.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 MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1); - ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MyUnSafeCommon.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 MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList); - ref ScheduledTaskListEntry entry = ref MyUnSafeCommon.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/System.cs b/Assets/Iris/Iris.Common/System.cs deleted file mode 100644 index f39846f..0000000 --- a/Assets/Iris/Iris.Common/System.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.IO; - -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.GBA.meta b/Assets/Iris/Iris.GBA.meta deleted file mode 100644 index 458c459..0000000 --- a/Assets/Iris/Iris.GBA.meta +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 497d086..0000000 --- a/Assets/Iris/Iris.GBA/BIOS.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.IO; -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 deleted file mode 100644 index f821dd0..0000000 --- a/Assets/Iris/Iris.GBA/BIOS.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e565ac6..0000000 --- a/Assets/Iris/Iris.GBA/Communication.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.IO; - -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 deleted file mode 100644 index 72e7fd5..0000000 --- a/Assets/Iris/Iris.GBA/Communication.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 9083018..0000000 --- a/Assets/Iris/Iris.GBA/DMA.cs +++ /dev/null @@ -1,479 +0,0 @@ -using System; -using System.IO; -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); - - var v2 = GetDestinationIncrement(DataUnitSize); - int destinationIncrement = v2.destinationIncrement; - reloadDestination = v2.reloadDestination; - - for (; channel._length > 0; --channel._length) - { - //_memory.Write16(channel._destination, _memory.Read16(channel._source)); - _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); - var v2 = GetDestinationIncrement(DataUnitSize); - int destinationIncrement = v2.destinationIncrement; - reloadDestination = v2.reloadDestination; - - 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 deleted file mode 100644 index c39d5d0..0000000 --- a/Assets/Iris/Iris.GBA/DMA.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 27f7b0a..0000000 --- a/Assets/Iris/Iris.GBA/GBA_System.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.IO; -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 deleted file mode 100644 index 3cf64ef..0000000 --- a/Assets/Iris/Iris.GBA/GBA_System.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 06ec4a9..0000000 --- a/Assets/Iris/Iris.GBA/InterruptControl.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using System.IO; -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 deleted file mode 100644 index 6d66972..0000000 --- a/Assets/Iris/Iris.GBA/InterruptControl.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index ee024bd..0000000 --- a/Assets/Iris/Iris.GBA/KeyInput.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System; -using System.IO; -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 deleted file mode 100644 index bcc8125..0000000 --- a/Assets/Iris/Iris.GBA/KeyInput.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index bb50b7f..0000000 --- a/Assets/Iris/Iris.GBA/Memory.cs +++ /dev/null @@ -1,2218 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -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 MyUnSafeCommon.GetArrayDataReference(_read8PageTable), (int)(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 MyUnSafeCommon.GetArrayDataReference(_read16PageTable), (int)(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 MyUnSafeCommon.GetArrayDataReference(_read32PageTable), (int)(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 MyUnSafeCommon.GetArrayDataReference(_write8PageTable), (int)(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 MyUnSafeCommon.GetArrayDataReference(_write16PageTable), (int)(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 MyUnSafeCommon.GetArrayDataReference(_write32PageTable), (int)(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/Sound.cs b/Assets/Iris/Iris.GBA/Sound.cs deleted file mode 100644 index 6aa8ea2..0000000 --- a/Assets/Iris/Iris.GBA/Sound.cs +++ /dev/null @@ -1,351 +0,0 @@ -using System; -using System.IO; - -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 deleted file mode 100644 index 392f09a..0000000 --- a/Assets/Iris/Iris.GBA/Sound.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 2bf2ac2..0000000 --- a/Assets/Iris/Iris.GBA/SystemControl.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.IO; - -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 deleted file mode 100644 index 21ad2dc..0000000 --- a/Assets/Iris/Iris.GBA/SystemControl.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index abcafb6..0000000 --- a/Assets/Iris/Iris.GBA/Timer.cs +++ /dev/null @@ -1,383 +0,0 @@ -using System; -using System.IO; -using System.Runtime.CompilerServices; -using static UnityEditor.Experimental.AssetDatabaseExperimental.AssetDatabaseCounters; - -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 struct Channel - { - internal Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt) - { - _startTaskId = startTaskId; - _handleOverflowTaskId = handleOverflowTaskId; - _interrupt = interrupt; - - _counter = 0; - _reload = 0; - _control = 0; - _cycleCount = 0; - _running = false; - - } - 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; - internal readonly GBA_System.TaskId _handleOverflowTaskId; - internal readonly InterruptControl.Interrupt _interrupt; - } - - private readonly Channel[] _channels; - - internal Timer(Common.Scheduler scheduler) - { - _scheduler = scheduler; - - _channels = new Channel[] - { - new Channel(GBA_System.TaskId.StartTimer_Channel0, GBA_System.TaskId.HandleTimerOverflow_Channel0, InterruptControl.Interrupt.Timer0), - new Channel(GBA_System.TaskId.StartTimer_Channel1, GBA_System.TaskId.HandleTimerOverflow_Channel1, InterruptControl.Interrupt.Timer1), - new Channel(GBA_System.TaskId.StartTimer_Channel2, GBA_System.TaskId.HandleTimerOverflow_Channel2, InterruptControl.Interrupt.Timer2), - new Channel(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) - private static UInt64 ComputeCycleCountUntilOverflow(ref 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 deleted file mode 100644 index 447d88b..0000000 --- a/Assets/Iris/Iris.GBA/Timer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 53db5f4..0000000 --- a/Assets/Iris/Iris.GBA/Video.cs +++ /dev/null @@ -1,1349 +0,0 @@ -using System; -using System.IO; -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,0, _displayFrameBuffer.Length); - - _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); - Array.Clear(_displayFrameBuffer,0, _displayFrameBuffer.Length); - - _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 MyUnSafeCommon.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 MyUnSafeCommon.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 MyUnSafeCommon.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 MyUnSafeCommon.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 MyUnSafeCommon.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 MyUnSafeCommon.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 deleted file mode 100644 index 956d1a2..0000000 --- a/Assets/Iris/Iris.GBA/Video.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index fc081ff..0000000 --- a/Assets/Iris/Iris.NDS.meta +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 9dd7d4a..0000000 --- a/Assets/Iris/Iris.NDS/BIOS.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -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 deleted file mode 100644 index 7bd1ac7..0000000 --- a/Assets/Iris/Iris.NDS/BIOS.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 5d6daa4..0000000 --- a/Assets/Iris/Iris.NDS/Memory.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.IO; - -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 deleted file mode 100644 index 7942601..0000000 --- a/Assets/Iris/Iris.NDS/Memory.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 14290ec..0000000 --- a/Assets/Iris/Iris.NDS/NDS_System.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.IO; - -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 deleted file mode 100644 index 7d2af6c..0000000 --- a/Assets/Iris/Iris.NDS/NDS_System.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 5d92549..0000000 --- a/Assets/Iris/Iris.NDS/PPU.cs +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index abaf307..0000000 --- a/Assets/Iris/Iris.NDS/PPU.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: fd011144397045948876bf23745e4a4c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/MyUnSafeCommon.cs b/Assets/MyUnSafeCommon.cs index bab1ab4..0aede9e 100644 --- a/Assets/MyUnSafeCommon.cs +++ b/Assets/MyUnSafeCommon.cs @@ -1,4 +1,7 @@ using System; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; public static class MyUnSafeCommon { @@ -88,4 +91,254 @@ public static class MyBitOperations // 组合结果 return rightShifted | leftShifted; } +} + + + + +public static class MyStruct +{ + [StructLayout(LayoutKind.Sequential)] + public struct Vector256 where T : struct, IComparable, IComparable, IEquatable, IFormattable + { + public T[] data; + + // 假设Vector256总是包含8个元素(对于uint来说,总共32字节) + public const int ElementCount = 8; + + // 私有构造函数,用于内部创建实例 + public Vector256(T[] data) + { + if (data == null || data.Length != ElementCount) + throw new ArgumentException("Data array must contain exactly 8 elements."); + + this.data = data; + } + + // 静态Create方法,用于从T类型的数组创建Vector256实例 + public static Vector256 Create(params T[] values) + { + if (values.Length > ElementCount) + throw new ArgumentException("Too many values provided."); + + T[] fullData = new T[ElementCount]; + Array.Copy(values, 0, fullData, 0, values.Length); + + // 对于uint,默认值是0 + if (typeof(T) == typeof(uint)) + { + for (int i = values.Length; i < ElementCount; i++) + { + fullData[i] = (T)(object)0u; // 使用显式类型转换来避免编译时错误 + } + } + + return new Vector256(fullData); + } + + // 静态Zero属性,返回一个所有元素都为0的Vector256实例 + public static Vector256 Zero + { + get + { + if (typeof(T) == typeof(uint)) + { + uint[] zeroData = new uint[ElementCount]; + //Array.Fill(zeroData, 0u); // 使用Array.Fill来填充所有元素为0 + return new Vector256((T[])(object)zeroData); // 显式类型转换以绕过泛型约束 + } + + // 如果T不是uint,这里可能需要抛出一个异常或者返回一个默认构造的Vector256 + // 但由于泛型约束,实际上T必须是uint(或者同时满足其他接口的类型,但在这个上下文中我们只关心uint) + // 因此,这里实际上不会执行到 + throw new InvalidOperationException("Zero property is only valid for Vector256."); + } + } + + // ... 可以根据需要添加更多重载 + + // 为了方便调试,可以重写ToString方法 + public override string ToString() + { + return string.Join(", ", Array.ConvertAll(data, x => x.ToString())); + } + } + + + // 模拟 Avx2.ConvertToVector256Int32 + public static Vector256 ConvertToVector256Int32(byte[] source) + { + if (source.Length < 32) // 32 bytes for 8 uints + throw new ArgumentException("Source array must be at least 32 bytes long."); + + uint[] uints = new uint[8]; + for (int i = 0; i < 8; i++) + { + uints[i] = BitConverter.ToUInt32(source, i * 4); + } + + return new Vector256(uints); + } + + // 模拟 Avx2.ShiftLeftLogical + public static Vector256 ShiftLeftLogical(Vector256 metaVec, int shift) + { + int[] shifted = new int[8]; + for (int i = 0; i < 8; i++) + { + shifted[i] = (int)(metaVec.data[i] << shift); + } + return new Vector256(shifted); + } + // 模拟 Avx2.And + public static Vector256 And(Vector256 data1, Vector256 data2) + { + int[] result = new int[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] & data2.data[i]; + } + return new Vector256(result); + } + // 模拟 Avx2.And + public static Vector256 And(Vector256 data1, Vector256 data2) + { + uint[] result = new uint[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] & data2.data[i]; + } + + return new Vector256(result); + } + + // 模拟 Avx2.Or + public static Vector256 Or(Vector256 data1, Vector256 data2) + { + int[] result = new int[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] | data2.data[i]; + } + + return new Vector256(result); + } + // 模拟 Avx2.Or + public static Vector256 Or(Vector256 data1, Vector256 data2) + { + uint[] result = new uint[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] | data2.data[i]; + } + + return new Vector256(result); + } + + // 模拟 Avx2.Xor + + public static Vector256 Xor(Vector256 data1, Vector256 data2) + { + int[] result = new int[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] ^ data2.data[i]; + } + return new Vector256(result); + } + + public static Vector256 Xor(Vector256 data1, Vector256 data2) + { + uint[] result = new uint[8]; + for (int i = 0; i < 8; i++) + { + result[i] = data1.data[i] ^ data2.data[i]; + } + + return new Vector256(result); + } + + // 模拟 Avx2.Permute2x128 (只模拟上下两半交换) + public static Vector256 Permute2x128(Vector256 data) + { + uint[] swapped = new uint[8]; + Array.Copy(data.data, 4, swapped, 0, 4); + Array.Copy(data.data, 0, swapped, 4, 4); + return new Vector256(swapped); + } + // 模拟 Avx2.Permute2x128 (只模拟上下两半交换) + public static Vector256 Permute2x128(Vector256 data) + { + int[] swapped = new int[8]; + Array.Copy(data.data, 4, swapped, 0, 4); + Array.Copy(data.data, 0, swapped, 4, 4); + return new Vector256(swapped); + } + + // 模拟 Avx2.CompareEqual + public static Vector256 CompareEqual(Vector256 left, Vector256 right) + { + int[] result = new int[8]; + for (int i = 0; i < 8; i++) + { + result[i] = (left.data[i] == right.data[i]) ? -1 : 0; // 通常使用-1或全1表示真,0表示假 + } + return new Vector256(result); + } + // 模拟 MaskedStore 操作 + public static void MaskedStore(Vector256 destination, Vector256 mask, Vector256 source) + { + //if (destination.Length < 8 || mask.Length != 8 || source.data.Length != 8) + // throw new ArgumentException("Arrays must be of length 8 and match in size."); + + for (int i = 0; i < 8; i++) + { + // 如果掩码中的值为非零(表示真),则写入源向量的值 + if (mask.data[i] != 0) + { + destination.data[i] = source.data[i]; + } + // 如果掩码中的值为零(表示假),则不修改destination[i] + } + } + + // 注意:这不是AVX2的gather指令的直接模拟,而是一个简化的示例 + public static Vector256 GatherVector256(T[] source, int[] indices) + { + if (source.Length < 8 || indices.Length < 8) + throw new ArgumentException("Source array and indices array must be large enough."); + + int[] gatheredData = new int[8]; + for (int i = 0; i < 8; i++) + { + int index = indices[i]; + if (index < 0 || index >= source.Length) + gatheredData[i] = source[index]; + } + + return new Vector256(gatheredData); + } + + + // 模拟 ShiftRightLogicalVariable,接受两个 Vector256 参数 + // 第一个参数是要右移的向量,第二个参数是每个元素要右移的位数 + public static Vector256 ShiftRightLogicalVariable(this Vector256 vector, Vector256 shiftCounts) + { + if (vector.data.Length != shiftCounts.data.Length) + { + throw new ArgumentException("Both vectors must have the same number of elements."); + } + + uint[] shiftedData = new uint[Vector256.ElementCount]; + for (int i = 0; i < vector.data.Length; i++) + { + // 注意:这里假设 shiftCounts.data[i] 中的值是有效的右移位数(非负且小于 32) + // 如果需要,可以添加额外的检查来处理无效值 + int shiftCount = (int)shiftCounts.data[i] & 0x1F; // 限制右移位数在 0 到 31 之间 + shiftedData[i] = vector.data[i] >> shiftCount; + } + + return new Vector256(shiftedData); + } + } \ No newline at end of file diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta index 24adc08..d0cbb25 100644 --- a/Assets/Plugins.meta +++ b/Assets/Plugins.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2a9692e254d34f34aa83151367615810 +guid: 06ea5793f83f5ab4e83233d686814f83 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Plugins/System.Buffers.dll.meta b/Assets/Plugins/System.Buffers.dll.meta index bd98f88..efe13f4 100644 --- a/Assets/Plugins/System.Buffers.dll.meta +++ b/Assets/Plugins/System.Buffers.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1cb7f59fb73e5f74cb7d6d787eadcbd1 +guid: a3e5397cebfd55d4d950fc3d7fcafd8f PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Plugins/System.Memory.dll.meta b/Assets/Plugins/System.Memory.dll.meta index 4a6db12..b03e202 100644 --- a/Assets/Plugins/System.Memory.dll.meta +++ b/Assets/Plugins/System.Memory.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b70ee69b176c61f4eb6db50741d13b0b +guid: 7079d9113f8bfda4db8a78fffdd414a9 PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Plugins/System.Numerics.Vectors.dll.meta b/Assets/Plugins/System.Numerics.Vectors.dll.meta index 50e7dd5..6e2fab3 100644 --- a/Assets/Plugins/System.Numerics.Vectors.dll.meta +++ b/Assets/Plugins/System.Numerics.Vectors.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d58c54bd61da7884f97da0f6bea3e4f5 +guid: 677f64bf577a1724699f45841c5bcb04 PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta b/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta index a17cc8e..378070a 100644 --- a/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta +++ b/Assets/Plugins/System.Runtime.CompilerServices.Unsafe.dll.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b26dd54a0d93ba0469d1f9307e0e4b43 +guid: c92fb77cb5b063541bd04cb8a496013f PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scenes.meta b/Assets/Scenes.meta deleted file mode 100644 index 83c741b..0000000 --- a/Assets/Scenes.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6ea315d0fd7389c41b19996891e99ae3 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity deleted file mode 100644 index 2221b04..0000000 --- a/Assets/Scenes/SampleScene.unity +++ /dev/null @@ -1,267 +0,0 @@ -%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 deleted file mode 100644 index 952bd1e..0000000 --- a/Assets/Scenes/SampleScene.unity.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 9fc0d4010bbf28b4594072e72b8655ab -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Util.cs b/Assets/Util.cs new file mode 100644 index 0000000..2b22830 --- /dev/null +++ b/Assets/Util.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections; + +static class Util +{ + public static void Each(this System.Collections.Generic.IEnumerable ie, Action action) + { + var i = 0; + foreach (var e in ie) action(e, i++); + } + + public static void WriteDebug(string text) + { + // Console.WriteLine(text); + } + + public static string Pad(string n, int width, char padChar) + { + return n.Length >= width ? n : string.Join(padChar, new int[width - (n.Length + 1)]) + n; + } + + public static string RightPad(string n, int width, char z) + { + return n.Length >= width ? n : n + string.Join(z, new int[width - (n.Length + 1)]); + } + + public static string Hex(long i, int digits) + { + return $"0x{i.ToString("X").ToUpper().PadLeft(digits, '0')}"; + } + + public static string Hex(ulong i, int digits) + { + return $"0x{i.ToString("X").ToUpper().PadLeft(digits, '0')}"; + } + + public static string HexN(long i, int digits) + { + return i.ToString("X").ToUpper().PadLeft(digits, '0'); + } + + public static string HexN_LC(long i, int digits) + { + return i.ToString("X").ToLower().PadLeft(digits, '0'); + } + + public static string Binary(long i, int digits) + { + return Convert.ToString(i, 2).ToLower().PadLeft(digits, '0'); + } +} diff --git a/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta b/Assets/Util.cs.meta similarity index 83% rename from Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta rename to Assets/Util.cs.meta index 25c53e6..d7fd294 100644 --- a/Assets/Iris/Iris.CPU/THUMB_Interpreter.cs.meta +++ b/Assets/Util.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 300995068a952f743aaddf6f4736c84c +guid: 1a5554b2d514cfb40a6352bbea330fe6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Iris.meta b/Assets/emulator.meta similarity index 77% rename from Assets/Iris.meta rename to Assets/emulator.meta index 3e37f79..a0e5027 100644 --- a/Assets/Iris.meta +++ b/Assets/emulator.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c85535aab73500e4e93bc6a3430ff694 +guid: 3efde08a75dea6141840226c711cab4b folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/emulator/ApplySkipLocalsInit.cs b/Assets/emulator/ApplySkipLocalsInit.cs new file mode 100644 index 0000000..99aa6de --- /dev/null +++ b/Assets/emulator/ApplySkipLocalsInit.cs @@ -0,0 +1 @@ +//[module: System.Runtime.CompilerServices.SkipLocalsInit] \ No newline at end of file diff --git a/Assets/Iris/Iris.Common/System.cs.meta b/Assets/emulator/ApplySkipLocalsInit.cs.meta similarity index 83% rename from Assets/Iris/Iris.Common/System.cs.meta rename to Assets/emulator/ApplySkipLocalsInit.cs.meta index 5eed498..81cabb9 100644 --- a/Assets/Iris/Iris.Common/System.cs.meta +++ b/Assets/emulator/ApplySkipLocalsInit.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d321636c09403e249a26ee8f85908765 +guid: 758bfc6cd0551814abf32fc61d504c02 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/emulator/BlipBuf.cs b/Assets/emulator/BlipBuf.cs new file mode 100644 index 0000000..37828cd --- /dev/null +++ b/Assets/emulator/BlipBuf.cs @@ -0,0 +1,169 @@ +// Inspired by Blargg's blip-buf + +using System; + +namespace OptimeGBA +{ + public class BlipBuf + { + const int KERNEL_RESOLUTION = 1024; + + float[] Kernel; + int KernelSize = 0; + + float[] ChannelValsL; + float[] ChannelValsR; + double[] ChannelSample; + double[] ChannelRealSample; + + float[] BufferL; + float[] BufferR; + + int BufferPos = 0; + int BufferSize = 0; + + public float CurrentValL = 0; + public float CurrentValR = 0; + + double CurrentSampleInPos = 0; + double CurrentSampleOutPos = 0; + + public BlipBuf(int kernelSize, bool normalize, int channels) + { + ChannelValsL = new float[channels]; + ChannelValsR = new float[channels]; + ChannelSample = new double[channels]; + ChannelRealSample = new double[channels]; + + BufferSize = 32768; + BufferL = new float[BufferSize]; + BufferR = new float[BufferSize]; + + SetKernelSize(kernelSize, normalize, true); + } + + public void SetKernelSize(int kernelSize, bool normalize, bool enabled) + { + Kernel = new float[kernelSize * KERNEL_RESOLUTION]; + KernelSize = kernelSize; + + if ((kernelSize & (kernelSize - 1)) != 0) + { + throw new ArgumentException("Kernel size not power of 2:" + kernelSize); + } + + for (int i = 0; i < KERNEL_RESOLUTION; i++) + { + float sum = 0; + for (int j = 0; j < kernelSize; j++) + { + if (enabled) + { + float x = j - kernelSize / 2F; + x += (KERNEL_RESOLUTION - i - 1) / (float)KERNEL_RESOLUTION; + x *= (float)Math.PI; + + float sinc = (float)Math.Sin(x) / x; + float lanzcosWindow = (float)Math.Sin((float)x / kernelSize) / ((float)x / kernelSize); + + if (x == 0) + { + Kernel[i * kernelSize + j] = 1; + } + else + { + Kernel[i * kernelSize + j] = sinc * lanzcosWindow; + } + + sum += Kernel[i * kernelSize + j]; + } + else + { + if (j == kernelSize / 2) + { + Kernel[i * kernelSize + j] = 1; + } + else + { + Kernel[i * kernelSize + j] = 0; + } + } + } + + if (normalize && enabled) + { + for (int j = 0; j < kernelSize; j++) + { + Kernel[i * kernelSize + j] /= sum; + } + } + } + } + + public void Reset() + { + BufferPos = 0; + CurrentValL = 0; + CurrentValR = 0; + for (int i = 0; i < BufferSize; i++) + { + BufferL[i] = 0; + BufferR[i] = 0; + } + } + + public void SetValue(int channel, double sample, float valL, float valR) + { + // Tracking to allow submitting value for different channels out of order + double realSample = sample; + double dist = sample - ChannelRealSample[channel]; + sample = ChannelSample[channel] + dist; + + if (sample >= CurrentSampleInPos) + { + CurrentSampleInPos = sample; + } + + if (sample < CurrentSampleOutPos) + { + Console.Error.WriteLine("Tried to set amplitude backward in time!"); + Console.WriteLine(System.Environment.StackTrace); + } + + ChannelSample[channel] = sample; + ChannelRealSample[channel] = realSample; + + if (valL != ChannelValsL[channel] || valR != ChannelValsR[channel]) + { + float diffL = valL - ChannelValsL[channel]; + float diffR = valR - ChannelValsR[channel]; + + int subsamplePos = (int)Math.Floor((sample % 1) * KERNEL_RESOLUTION); + + + // Add our bandlimited impulse to the difference buffer + int kBufPos = (BufferPos + (int)(Math.Floor(sample) - CurrentSampleOutPos)) % BufferSize; + for (int i = 0; i < KernelSize; i++) + { + float kernelVal = Kernel[KernelSize * subsamplePos + i]; + BufferL[kBufPos] += kernelVal * diffL; + BufferR[kBufPos] += kernelVal * diffR; + kBufPos = (kBufPos + 1) % BufferSize; + } + } + + ChannelValsL[channel] = valL; + ChannelValsR[channel] = valR; + } + + public void ReadOutSample() + { + CurrentValL += BufferL[BufferPos]; + CurrentValR += BufferR[BufferPos]; + BufferL[BufferPos] = 0; + BufferR[BufferPos] = 0; + BufferPos = (BufferPos + 1) % BufferSize; + CurrentSampleOutPos++; + } + } +} \ No newline at end of file diff --git a/Assets/Iris/Iris.CPU/CPU_Core.cs.meta b/Assets/emulator/BlipBuf.cs.meta similarity index 83% rename from Assets/Iris/Iris.CPU/CPU_Core.cs.meta rename to Assets/emulator/BlipBuf.cs.meta index 6630d24..5bd5397 100644 --- a/Assets/Iris/Iris.CPU/CPU_Core.cs.meta +++ b/Assets/emulator/BlipBuf.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8ad832993e3b0bc4695925ea366d4d9e +guid: d2f0e6f7505a3d04bb84e704d1ed8d33 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/emulator/CartridgeNds.cs b/Assets/emulator/CartridgeNds.cs new file mode 100644 index 0000000..6e1e35f --- /dev/null +++ b/Assets/emulator/CartridgeNds.cs @@ -0,0 +1,770 @@ +using static Util; +using static OptimeGBA.Bits; +using static OptimeGBA.MemoryUtil; +using System; +using System.Text; +namespace OptimeGBA +{ + public enum SpiEepromState + { + Ready, + ReadStatus, + WriteStatus, + SetReadAddress, + SetWriteAddress, + ReadData, + WriteData, + Done, + } + + public enum ExternalMemoryType + { + None, + Eeprom, + Flash, + FlashWithInfrared + } + + public enum CartridgeState + { + Dummy, + ReadCartridgeHeader, + ReadRomChipId1, + Dummy2, + ReadRomChipId2, + Key2DataRead, + SecureAreaRead, + ReadRomChipId3, + } + + public class CartridgeNds + { + Nds Nds; + byte[] Rom; + byte[] SecureArea = new byte[0x4000]; + + public uint[] EncLutKeycodeLevel1 = new uint[0x412]; + public uint[] EncLutKeycodeLevel2 = new uint[0x412]; + public uint[] EncLutKeycodeLevel3 = new uint[0x412]; + + public uint IdCode; + public string IdString; + + public CartridgeNds(Nds nds) + { + Nds = nds; + Rom = Nds.Provider.Rom; + + for (uint i = 0; i < 0x412; i++) + { + uint val = GetUint(Nds.Provider.Bios7, 0x30 + i * 4); + EncLutKeycodeLevel1[i] = val; + EncLutKeycodeLevel2[i] = val; + EncLutKeycodeLevel3[i] = val; + } + + if (Rom.Length >= 0x10) + { + IdCode = GetUint(Rom, 0x0C); + + Span gameIdSpan = stackalloc byte[4]; + for (int i = 0; i < 4; i++) + { + gameIdSpan[i] = GetByte(Rom, 0x0C + (uint)i); + } + + IdString = Encoding.ASCII.GetString(gameIdSpan); + Console.WriteLine("Game ID: " + IdString); + } + + InitKeycode(EncLutKeycodeLevel1, 1); + InitKeycode(EncLutKeycodeLevel2, 2); + InitKeycode(EncLutKeycodeLevel3, 3); + + if (!Nds.Provider.DirectBoot && Rom.Length >= 0x8000 && GetUint(Rom, 0x4000) == 0xE7FFDEFF) + { + for (uint i = 0; i < 0x4000; i++) + { + SecureArea[i] = Rom[0x4000 + i]; + } + Console.WriteLine("Encrypting first 2KB of secure area"); + SetUlong(SecureArea, 0x0000, 0x6A624F7972636E65); // Write in "encryObj" + + // Encrypt first 2K of the secure area with KEY1 + for (uint i = 0x0000; i < 0x0800; i += 8) + { + // Console.WriteLine("Encrypted ulong at " + Hex(i, 16)); + ulong raw = GetUlong(SecureArea, i); + ulong encrypted = Encrypt64(EncLutKeycodeLevel3, raw); + SetUlong(SecureArea, i, encrypted); + // Console.WriteLine("Before:" + Hex(raw, 16)); + // Console.WriteLine("After :" + Hex(encrypted, 16)); + } + + Console.WriteLine(Hex(GetUint(SecureArea, 0x0010), 8)); + + // Double-encrypt KEY1 + SetUlong(SecureArea, 0x0000, Encrypt64(EncLutKeycodeLevel2, GetUlong(SecureArea, 0x0000))); + } + + for (uint i = 0; i < ExternalMemory.Length; i++) + { + ExternalMemory[i] = 0xFF; + } + } + + ulong PendingCommand; + + // some GBATek example + // TODO: Replace this with something more realistic, maybe from a game DB + public uint RomChipId = 0x00001FC2; + + // State + public CartridgeState State; + public uint DataPos; + public uint BytesTransferred; + public bool Key1Encryption; + public bool Key2Encryption; + + public uint TransferLength; + public uint PendingDummyWrites; + + public bool ReadyBit23; + public byte BlockSize; + public bool SlowTransferClock; + public bool BusyBit31; + + // AUXSPICNT + public byte SpiBaudRate; + public bool SpiChipSelHold = false; + public bool SpiBusy = false; + public bool Slot1SpiMode = false; + public bool TransferReadyIrq = false; + public bool Slot1Enable = false; + + // Shared External Memory State + public SpiEepromState SpiEepromState; + public byte SpiOutData; + public uint SpiAddress; + public uint SpiBytesWritten; + public bool ExternalMemoryWriteEnable; + // only one game called art academy uses more than 1MB + public byte[] ExternalMemory = new byte[1048576]; + + // EEPROM state + public byte EepromWriteProtect; + + // Flash state + // From Nocash's original DS + // TODO: use more realistic flash ID + byte[] FlashId = new byte[] { 0x20, 0x40, 0x12 }; + public SpiFlashState SpiFlashState; + public byte FlashIdIndex; + + // ROMCTRL + byte ROMCTRLB0; + byte ROMCTRLB1; + bool ReleaseReset; + + // cart input + uint InData; + + public byte ReadHwio8(bool fromArm7, uint addr) + { + byte val = 0; + if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights) + { + switch (addr) + { + case 0x40001A0: // AUXSPICNT B0 + val |= SpiBaudRate; + if (SpiChipSelHold) val = BitSet(val, 6); + if (SpiBusy) val = BitSet(val, 7); + // Console.WriteLine("AUXSPICNT B0 read"); + break; + case 0x40001A1: // AUXSPICNT B1 + if (Slot1SpiMode) val = BitSet(val, 5); + if (TransferReadyIrq) val = BitSet(val, 6); + if (Slot1Enable) val = BitSet(val, 7); + break; + + case 0x40001A2: // AUXSPIDATA + return SpiOutData; + + case 0x40001A4: // ROMCTRL B0 + return ROMCTRLB0; + case 0x40001A5: // ROMCTRL B1 + return ROMCTRLB1; + case 0x40001A6: // ROMCTRL B2 + if (ReleaseReset) val = BitSet(val, 5); + if (ReadyBit23) val = BitSet(val, 7); + break; + case 0x40001A7: // ROMCTRL B3 + val |= BlockSize; + if (SlowTransferClock) val = BitSet(val, 3); + if (BusyBit31) val = BitSet(val, 7); + break; + + case 0x4100010: // From cartridge + if (Slot1Enable) + { + ReadData(fromArm7); + } + return (byte)(InData >> 0); + case 0x4100011: + return (byte)(InData >> 8); + case 0x4100012: + return (byte)(InData >> 16); + case 0x4100013: + return (byte)(InData >> 24); + } + } + else + { + Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8)); + } + + return val; + } + + public void WriteHwio8(bool fromArm7, uint addr, byte val) + { + if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights) + { + switch (addr) + { + case 0x40001A0: // AUXSPICNT B0 + SpiBaudRate = (byte)(val & 0b11); + SpiChipSelHold = BitTest(val, 6); + SpiBusy = BitTest(val, 7); + return; + case 0x40001A1: // AUXSPICNT B1 + Slot1SpiMode = BitTest(val, 5); + TransferReadyIrq = BitTest(val, 6); + Slot1Enable = BitTest(val, 7); + return; + + case 0x40001A2: // AUXSPIDATA + SpiTransferTo(val); + break; + + case 0x40001A4: // ROMCTRL B0 + ROMCTRLB0 = val; + break; + case 0x40001A5: // ROMCTRL B1 + ROMCTRLB1 = val; + break; + case 0x40001A6: // ROMCTRL B2 + if (BitTest(val, 5)) ReleaseReset = true; + break; + case 0x40001A7: // ROMCTRL B3 + BlockSize = (byte)(val & 0b111); + SlowTransferClock = BitTest(val, 3); + + if (BitTest(val, 7) && !BusyBit31 && Slot1Enable) + { + ProcessCommand(fromArm7); + } + break; + } + + if (Slot1Enable) + { + switch (addr) + { + case 0x40001A8: // Slot 1 Command out + case 0x40001A9: + case 0x40001AA: + case 0x40001AB: + case 0x40001AC: + case 0x40001AD: + case 0x40001AE: + case 0x40001AF: + if (Slot1Enable) + { + int shiftBy = (int)((7 - (addr & 7)) * 8); + PendingCommand &= (ulong)(~(0xFFUL << shiftBy)); + PendingCommand |= (ulong)val << shiftBy; + } + return; + } + } + } + else + { + Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8)); + } + } + + public void ProcessCommand(bool fromArm7) + { + ulong cmd = PendingCommand; + if (Key1Encryption) + { + cmd = Decrypt64(EncLutKeycodeLevel2, cmd); + } + + // Console.WriteLine("Slot 1 CMD: " + Hex(cmd, 16)); + + if (BlockSize == 0) + { + TransferLength = 0; + } + else if (BlockSize == 7) + { + TransferLength = 4; + } + else + { + TransferLength = 0x100U << BlockSize; + } + + if (TransferLength != 0) + { + DataPos = 0; + BytesTransferred = 0; + } + + BusyBit31 = true; + + if (cmd == 0x9F00000000000000) + { + State = CartridgeState.Dummy; + } + else if (cmd == 0x0000000000000000) + { + // Console.WriteLine("Slot 1: Putting up cartridge header"); + State = CartridgeState.ReadCartridgeHeader; + } + else if (cmd == 0x9000000000000000) + { + // Console.WriteLine("Slot 1: Putting up ROM chip ID 1"); + State = CartridgeState.ReadRomChipId1; + } + else if ((cmd & 0xFF00000000000000) == 0x3C00000000000000) + { + // Console.WriteLine("Slot 1: Enabled KEY1 encryption"); + State = CartridgeState.Dummy2; + Key1Encryption = true; + } + else if ((cmd & 0xF000000000000000) == 0x2000000000000000) + { + // Console.WriteLine("Slot 1: Get Secure Area Block"); + State = CartridgeState.SecureAreaRead; + DataPos = (uint)(((cmd >> 44) & 0xFFFF) * 0x1000); + // Console.WriteLine("Secure area read pos: " + Hex(DataPos, 8)); + } + else if ((cmd & 0xF000000000000000) == 0x4000000000000000) + { + // Console.WriteLine("Slot 1: Enable KEY2"); + State = CartridgeState.Dummy2; + } + else if ((cmd & 0xF000000000000000) == 0x1000000000000000) + { + // Console.WriteLine("Slot 1: Putting up ROM chip ID 2"); + State = CartridgeState.ReadRomChipId2; + } + else if ((cmd & 0xF000000000000000) == 0xA000000000000000) + { + // Console.WriteLine("Slot 1: Enter main data mode"); + State = CartridgeState.Dummy2; + Key1Encryption = false; + } + else if ((cmd & 0xFF00000000FFFFFF) == 0xB700000000000000) + { + // On a real DS, KEY2 encryption is transparent to software, + // as it is all handled in the hardware cartridge interface. + // Plus, DS ROM dumps are usually KEY2 decrypted, so in most cases + // there's actually no need to actually handle KEY2 encryption in + // an emulator. + // Console.WriteLine("KEY2 data read"); + State = CartridgeState.Key2DataRead; + + DataPos = (uint)((cmd >> 24) & 0xFFFFFFFF); + // Console.WriteLine("Addr: " + Hex(DataPos, 8)); + } + else if (cmd == 0xB800000000000000) + { + // Console.WriteLine("Slot 1: Putting up ROM chip ID 3"); + State = CartridgeState.ReadRomChipId3; + } + else + { + // throw new NotImplementedException("Slot 1: unimplemented command " + Hex(cmd, 16)); + } + // If block size is zero, no transfer will take place, signal end. + if (TransferLength == 0) + { + FinishTransfer(); + } + else + { + ReadyBit23 = true; + + // Trigger Slot 1 DMA + Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer); + // Console.WriteLine("Trigger slot 1 DMA, Dest: " + Hex(Nds.Dma7.Ch[3].DmaDest, 8)); + } + } + + public void ReadData(bool fromArm7) + { + if (!ReadyBit23) + { + InData = 0; + return; + } + + uint val = 0xFFFFFFFF; + + switch (State) + { + case CartridgeState.Dummy: // returns all 1s + break; + case CartridgeState.ReadCartridgeHeader: + val = GetUint(Rom, DataPos & 0xFFF); + break; + case CartridgeState.ReadRomChipId1: + case CartridgeState.ReadRomChipId2: + case CartridgeState.ReadRomChipId3: + val = RomChipId; + break; + case CartridgeState.Key2DataRead: + // Console.WriteLine("Key2 data read"); + if (DataPos < Rom.Length) + { + if (DataPos < 0x8000) + { + DataPos = 0x8000 + (DataPos & 0x1FF); + } + val = GetUint(Rom, DataPos); + } + break; + case CartridgeState.SecureAreaRead: + val = GetUint(SecureArea, DataPos - 0x4000); + // Console.WriteLine("Secure area read: Pos: " + Hex(DataPos, 8) + " Val: " + Hex(val, 4)); + break; + + default: + throw new NotImplementedException("Slot 1: bad state"); + } + + + DataPos += 4; + BytesTransferred += 4; + if (BytesTransferred >= TransferLength) + { + FinishTransfer(); + } + else + { + // TODO: Slot 1 DMA transfers + Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer); + } + + InData = val; + } + + public void RepeatCartridgeTransfer(long cyclesLate) + { + // Console.WriteLine(Hex(Nds.Dma7.Ch[3].DmaDest, 8)); + if (Nds.MemoryControl.Nds7Slot1AccessRights) + { + Nds.Dma7.Repeat((byte)DmaStartTimingNds7.Slot1); + } + else + { + Nds.Dma9.Repeat((byte)DmaStartTimingNds9.Slot1); + } + } + + public void FinishTransfer() + { + ReadyBit23 = false; + BusyBit31 = false; + + if (TransferReadyIrq) + { + if (Nds.MemoryControl.Nds7Slot1AccessRights) + { + Nds.HwControl7.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete); + } + else + { + Nds.HwControl9.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete); + + } + } + } + + // From the Key1 Encryption section of GBATek. + // Thanks Martin Korth. + public static ulong Encrypt64(uint[] encLut, ulong val) + { + uint y = (uint)val; + uint x = (uint)(val >> 32); + for (uint i = 0; i < 0x10; i++) + { + uint z = encLut[i] ^ x; + x = encLut[0x012 + (byte)(z >> 24)]; + x = encLut[0x112 + (byte)(z >> 16)] + x; + x = encLut[0x212 + (byte)(z >> 8)] ^ x; + x = encLut[0x312 + (byte)(z >> 0)] + x; + x ^= y; + y = z; + } + uint outLower = x ^ encLut[0x10]; + uint outUpper = y ^ encLut[0x11]; + + return ((ulong)outUpper << 32) | outLower; + } + + public static ulong Decrypt64(uint[] encLut, ulong val) + { + uint y = (uint)val; + uint x = (uint)(val >> 32); + for (uint i = 0x11; i >= 0x02; i--) + { + uint z = encLut[i] ^ x; + x = encLut[0x012 + (byte)(z >> 24)]; + x = encLut[0x112 + (byte)(z >> 16)] + x; + x = encLut[0x212 + (byte)(z >> 8)] ^ x; + x = encLut[0x312 + (byte)(z >> 0)] + x; + x ^= y; + y = z; + } + uint outLower = x ^ encLut[0x1]; + uint outUpper = y ^ encLut[0x0]; + + return ((ulong)outUpper << 32) | outLower; + } + + // modulo is always 0x08 + public void ApplyKeycode(uint[] encLut, Span keyCode, uint modulo) + { + ulong encrypted1 = Encrypt64(encLut, ((ulong)keyCode[2] << 32) | keyCode[1]); + keyCode[1] = (uint)encrypted1; + keyCode[2] = (uint)(encrypted1 >> 32); + ulong encrypted0 = Encrypt64(encLut, ((ulong)keyCode[1] << 32) | keyCode[0]); + keyCode[0] = (uint)encrypted0; + keyCode[1] = (uint)(encrypted0 >> 32); + + ulong scratch = 0; + + for (uint i = 0; i < 0x12; i++) + { + encLut[i] ^= BSwap32(keyCode[(int)(i % modulo)]); + } + + // EncLut is stored in uint for convenience so iterate in uints as well + for (uint i = 0; i < 0x412; i += 2) + { + scratch = Encrypt64(encLut, scratch); + encLut[i + 0] = (uint)(scratch >> 32); + encLut[i + 1] = (uint)scratch; + } + } + + public void InitKeycode(uint[] encLut, uint level) + { + Span keyCode = stackalloc uint[3]; + keyCode[0] = IdCode; + keyCode[1] = IdCode / 2; + keyCode[2] = IdCode * 2; + + // For game cartridge KEY1 decryption, modulo is always 2 (says 8 in GBATek) + // but is 2 when divided by four to convert from byte to uint + if (level >= 1) ApplyKeycode(encLut, keyCode, 2); + if (level >= 2) ApplyKeycode(encLut, keyCode, 2); + + keyCode[1] *= 2; + keyCode[2] /= 2; + + if (level >= 3) ApplyKeycode(encLut, keyCode, 2); // + } + + public static uint BSwap32(uint val) + { + return + ((val >> 24) & 0x000000FF) | + ((val >> 8) & 0x0000FF00) | + ((val << 8) & 0x00FF0000) | + ((val << 24) & 0xFF000000); + } + + public void SpiTransferTo(byte val) + { + // currently only EEPROM support + if (Slot1Enable) + { + var saveType = ExternalMemoryType.Eeprom; + // TODO: use a game DB to get memory type + switch (saveType) + { + case ExternalMemoryType.None: + break; + case ExternalMemoryType.Eeprom: + switch (SpiEepromState) + { + case SpiEepromState.Ready: + switch (val) + { + case 0x06: // Write Enable + ExternalMemoryWriteEnable = true; + SpiEepromState = SpiEepromState.Ready; + break; + case 0x04: // Write Disable + ExternalMemoryWriteEnable = false; + SpiEepromState = SpiEepromState.Ready; + break; + case 0x5: // Read Status Register + SpiEepromState = SpiEepromState.ReadStatus; + break; + case 0x1: // Write Status Register + SpiEepromState = SpiEepromState.WriteStatus; + break; + case 0x9F: // Read JEDEC ID (returns 0xFF on EEPROM/FLASH) + SpiOutData = 0xFF; + break; + case 0x3: // Read + SpiEepromState = SpiEepromState.SetReadAddress; + SpiAddress = 0; + SpiBytesWritten = 0; + break; + case 0x2: // Write + SpiEepromState = SpiEepromState.SetWriteAddress; + SpiAddress = 0; + SpiBytesWritten = 0; + break; + } + break; + case SpiEepromState.ReadStatus: + byte status = 0; + if (ExternalMemoryWriteEnable) status = BitSet(status, 1); + status |= (byte)(EepromWriteProtect << 2); + SpiOutData = status; + break; + case SpiEepromState.WriteStatus: + ExternalMemoryWriteEnable = BitTest(val, 1); + EepromWriteProtect = (byte)((val >> 2) & 0b11); + break; + case SpiEepromState.SetReadAddress: + SpiAddress <<= 8; + SpiAddress |= val; + if (++SpiBytesWritten == 2) + { + SpiEepromState = SpiEepromState.ReadData; + } + break; + case SpiEepromState.ReadData: + SpiOutData = ExternalMemory[SpiAddress]; + SpiAddress++; + break; + case SpiEepromState.SetWriteAddress: + SpiAddress <<= 8; + SpiAddress |= val; + if (++SpiBytesWritten == 2) + { + SpiEepromState = SpiEepromState.WriteData; + } + break; + case SpiEepromState.WriteData: + ExternalMemory[SpiAddress] = val; + SpiOutData = 0; + SpiAddress++; + break; + } + break; + case ExternalMemoryType.FlashWithInfrared: + switch (SpiFlashState) + { + case SpiFlashState.TakePrefix: + if (val == 0) + { + SpiFlashState = SpiFlashState.Ready; + Console.WriteLine("Flash with IR command"); + } + break; + case SpiFlashState.Ready: + // Console.WriteLine("SPI: Receive command! " + Hex(val, 2)); + SpiOutData = 0x00; + switch (val) + { + case 0x06: + ExternalMemoryWriteEnable = true; + break; + case 0x04: + ExternalMemoryWriteEnable = false; + break; + case 0x9F: + SpiFlashState = SpiFlashState.Identification; + SpiAddress = 0; + break; + case 0x03: + SpiFlashState = SpiFlashState.ReceiveAddress; + SpiAddress = 0; + SpiBytesWritten = 0; + break; + case 0x0B: + throw new NotImplementedException("slot1 flash fast read"); + case 0x0A: + throw new NotImplementedException("slot1 flash write"); + case 0x02: + throw new NotImplementedException("slot1 flash program"); + case 0x05: // Identification + // Console.WriteLine("SPI ID"); + SpiAddress = 0; + SpiOutData = 0x00; + break; + case 0x00: + break; + // default: + // throw new NotImplementedException("SPI: Unimplemented command: " + Hex(val, 2)); + } + break; + case SpiFlashState.ReceiveAddress: + // Console.WriteLine("SPI: Address byte write: " + Hex(val, 2)); + SpiAddress <<= 8; + SpiAddress |= val; + if (++SpiBytesWritten == 3) + { + SpiBytesWritten = 0; + SpiFlashState = SpiFlashState.Reading; + // Console.WriteLine("SPI: Address written: " + Hex(Address, 6)); + } + break; + case SpiFlashState.Reading: + // Console.WriteLine("SPI: Read from address: " + Hex(Address, 6)); + // Nds7.Cpu.Error("SPI"); + SpiOutData = ExternalMemory[SpiAddress]; + SpiAddress++; + SpiAddress &= 0xFFFFFF; + break; + case SpiFlashState.Identification: + SpiOutData = FlashId[SpiAddress]; + SpiAddress++; + SpiAddress %= 3; + break; + } + break; + } + + if (!SpiChipSelHold) + { + SpiEepromState = SpiEepromState.Ready; + SpiFlashState = SpiFlashState.TakePrefix; + } + } + } + + public byte[] GetSave() + { + return ExternalMemory; + } + + public void LoadSave(byte[] sav) + { + sav.CopyTo(ExternalMemory, 0); + } + } +} \ No newline at end of file diff --git a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta b/Assets/emulator/CartridgeNds.cs.meta similarity index 83% rename from Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta rename to Assets/emulator/CartridgeNds.cs.meta index 6e3e21b..05c40aa 100644 --- a/Assets/Iris/Iris.CPU/ARM_Interpreter.cs.meta +++ b/Assets/emulator/CartridgeNds.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4c4deba1667702f49b08a4ccab53688e +guid: 0aa73f4dabd6dcd44b7d5eaa5cbf4c68 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/emulator/CoreUtil.cs b/Assets/emulator/CoreUtil.cs new file mode 100644 index 0000000..1418727 --- /dev/null +++ b/Assets/emulator/CoreUtil.cs @@ -0,0 +1,207 @@ +using System.Runtime.CompilerServices; + +namespace OptimeGBA +{ + sealed class Bits + { + public const uint BIT_0 = (1 << 0); + public const uint BIT_1 = (1 << 1); + public const uint BIT_2 = (1 << 2); + public const uint BIT_3 = (1 << 3); + public const uint BIT_4 = (1 << 4); + public const uint BIT_5 = (1 << 5); + public const uint BIT_6 = (1 << 6); + public const uint BIT_7 = (1 << 7); + public const uint BIT_8 = (1 << 8); + public const uint BIT_9 = (1 << 9); + public const uint BIT_10 = (1 << 10); + public const uint BIT_11 = (1 << 11); + public const uint BIT_12 = (1 << 12); + public const uint BIT_13 = (1 << 13); + public const uint BIT_14 = (1 << 14); + public const uint BIT_15 = (1 << 15); + public const uint BIT_16 = (1 << 16); + public const uint BIT_17 = (1 << 17); + public const uint BIT_18 = (1 << 18); + public const uint BIT_19 = (1 << 19); + public const uint BIT_20 = (1 << 20); + public const uint BIT_21 = (1 << 21); + public const uint BIT_22 = (1 << 22); + public const uint BIT_23 = (1 << 23); + public const uint BIT_24 = (1 << 24); + public const uint BIT_25 = (1 << 25); + public const uint BIT_26 = (1 << 26); + public const uint BIT_27 = (1 << 27); + public const uint BIT_28 = (1 << 28); + public const uint BIT_29 = (1 << 29); + public const uint BIT_30 = (1 << 30); + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool BitTest(uint i, byte bit) + { + return (i & (1 << bit)) != 0; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool BitTest(ulong i, byte bit) + { + return (i & (1u << bit)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool BitTest(long i, byte bit) + { + return (i & (1u << bit)) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint BitSet(uint i, byte bit) + { + return (uint)(i | (uint)(1 << bit)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte BitSet(byte i, byte bit) + { + return (byte)(i | (byte)(1 << bit)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte BitClear(byte i, byte bit) + { + return (byte)(i & ~(byte)(1 << bit)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint BitRange(uint i, byte start, byte end) + { + return (i >> start) & (0xFFFFFFFF >> (31 - (end - start))); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte BitReverse8(byte i) + { + return (byte)((i * 0x0202020202U & 0x010884422010U) % 1023); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint LogicalShiftLeft32(uint n, byte bits) + { + return n << bits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint LogicalShiftRight32(uint n, byte bits) + { + return n >> bits; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ArithmeticShiftRight32(uint n, byte bits) + { + return (uint)((int)n >> bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRight32(uint n, byte bits) + { + return (n >> bits) | (n << -bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateRight64(ulong n, byte bits) + { + return (n >> bits) | (n << -bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByteIn(long n, int pos) + { + return (byte)(n >> (pos * 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByteIn(ulong n, int pos) + { + return (byte)(n >> (pos * 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByteIn(long n, uint pos) + { + return (byte)(n >> (int)(pos * 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByteIn(ulong n, uint pos) + { + return (byte)(n >> (int)(pos * 8)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint SetByteIn(uint n, byte val, uint pos) + { + uint mask = ~(0xFFU << (int)(pos * 8)); + uint or = (uint)(val << (int)(pos * 8)); + return (n & mask) | or; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong SetByteIn(ulong n, byte val, uint pos) + { + ulong mask = ~(0xFFUL << (int)(pos * 8)); + ulong or = (ulong)((ulong)val << (int)(pos * 8)); + return (n & mask) | or; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long SetByteIn(long n, byte val, uint pos) + { + long mask = ~(0xFFL << (int)(pos * 8)); + long or = (long)((long)val << (int)(pos * 8)); + return (n & mask) | or; + } + + // public bool bitReset(uint i, uint bit) + // { + // return i & (~(1 << bit)); + // } + + // public bool bitSetValue(uint i, uint bit, bool value) + // { + // if (value) + // { + // return i | (1 << bit); + // } + // else + // { + // return i & (~(1 << bit)); + // } + // } + } + + public class CoreUtil + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Swap(ref T one, ref T two) + { + T temp = one; + one = two; + two = temp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static sbyte SignExtend8(byte val, int pos) + { + return (sbyte)(((sbyte)val << (7 - pos)) >> (7 - pos)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short SignExtend16(ushort val, int pos) + { + return (short)(((short)val << (15 - pos)) >> (15 - pos)); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/CoreUtil.cs.meta b/Assets/emulator/CoreUtil.cs.meta new file mode 100644 index 0000000..5a11454 --- /dev/null +++ b/Assets/emulator/CoreUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc5bdea9f2094294da4dcefeebda8f63 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Cp15.cs b/Assets/emulator/Cp15.cs new file mode 100644 index 0000000..bdef11b --- /dev/null +++ b/Assets/emulator/Cp15.cs @@ -0,0 +1,86 @@ +using System; +using static Util; + +namespace OptimeGBA +{ + public class Cp15 + { + Nds Nds; + + public Cp15(Nds nds) + { + Nds = nds; + } + + public uint ControlRegister; + + public uint DataTcmSettings; + public uint InstTcmSettings; + + public void TransferTo(uint opcode1, uint rdVal, uint cRn, uint cRm, uint opcode2) + { + uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7); + + switch (reg) + { + case 0x100: + ControlRegister = rdVal; + ControlRegister |= 0b00000000000000000000000001111000; + ControlRegister &= 0b00000000000011111111000010000101; + Nds.Mem9.UpdateTcmSettings(); + break; + + case 0x704: + case 0x782: + Nds.Cpu9.Halted = true; + break; + + case 0x910: + DataTcmSettings = rdVal; + Nds.Mem9.UpdateTcmSettings(); + break; + case 0x911: + InstTcmSettings = rdVal; + Nds.Mem9.UpdateTcmSettings(); + break; + + default: + // Console.WriteLine($"UNIMPLEMENTED TO CP15 {opcode1},C{cRn},C{cRm},{opcode2}: {HexN(rdVal, 8)}"); + break; + + } + } + + public uint TransferFrom(uint opcode1, uint cRn, uint cRm, uint opcode2) + { + uint val = 0; + + uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7); + switch (reg) + { + case 0x000: // ID register + val = 0x41059461; + break; + case 0x001: + val = 0x0F0D2112; + break; + case 0x100: + val = ControlRegister; + break; + case 0x910: + val = DataTcmSettings; + break; + case 0x911: + val = InstTcmSettings; + break; + + default: + Console.WriteLine($"UNIMPLEMENTED FROM CP15 {opcode1},C{cRn},C{cRm},{opcode2}"); + break; + } + + + return val; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Cp15.cs.meta b/Assets/emulator/Cp15.cs.meta new file mode 100644 index 0000000..db2ad36 --- /dev/null +++ b/Assets/emulator/Cp15.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 930c82bbb70e8d54d8bed296233fb78a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Dma.cs b/Assets/emulator/Dma.cs new file mode 100644 index 0000000..dc55fb1 --- /dev/null +++ b/Assets/emulator/Dma.cs @@ -0,0 +1,29 @@ +using static OptimeGBA.Bits; +using System; + +namespace OptimeGBA +{ + public enum DmaStartTiming + { + Immediately = 0, + VBlank = 1, + HBlank = 2, + Special = 3, + } + + public enum DmaDestAddrCtrl + { + Increment = 0, + Decrement = 1, + Fixed = 2, + IncrementReload = 3, + } + + public enum DmaSrcAddrCtrl + { + Increment = 0, + Decrement = 1, + Fixed = 2, + PROHIBITED = 3, + } +} \ No newline at end of file diff --git a/Assets/emulator/Dma.cs.meta b/Assets/emulator/Dma.cs.meta new file mode 100644 index 0000000..1c6420d --- /dev/null +++ b/Assets/emulator/Dma.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d57248ed47e4cf4a922bbb7f923b6e9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/DmaGba.cs b/Assets/emulator/DmaGba.cs new file mode 100644 index 0000000..04f8b94 --- /dev/null +++ b/Assets/emulator/DmaGba.cs @@ -0,0 +1,444 @@ +using static OptimeGBA.Bits; +using System; + +namespace OptimeGBA +{ + public enum DmaStartTimingGba + { + Immediately = 0, + VBlank = 1, + HBlank = 2, + Special = 3, + } + + public sealed class DmaChannelGba + { + public uint DMASAD; + public uint DMADAD; + public uint DMACNT_L; + + public uint DmaSource; + public uint DmaDest; + public uint DmaLength; + + // DMACNT_H + public DmaDestAddrCtrl DestAddrCtrl; + public DmaSrcAddrCtrl SrcAddrCtrl; + public bool Repeat; + public bool TransferType; + public bool GamePakDRQ; + public DmaStartTimingGba StartTiming; + public bool FinishedIRQ; + public bool Enabled; // Don't directly set to false, use Disable() + + public uint DMACNT_H; + + public byte ReadHwio8(uint addr) + { + // DMASAD, DMADAD, and DMACNT_L are write-only + byte val = 0; + switch (addr) + { + case 0x0A: // DMACNT_H B0 + case 0x0B: // DMACNT_H B1 + val = GetByteIn(GetControl(), addr & 1); + break; + } + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x00: // DMASAD B0 + case 0x01: // DMASAD B1 + case 0x02: // DMASAD B2 + case 0x03: // DMASAD B3 + DMASAD = SetByteIn(DMASAD, val, addr & 3); + break; + + case 0x04: // DMADAD B0 + case 0x05: // DMADAD B1 + case 0x06: // DMADAD B2 + case 0x07: // DMADAD B3 + DMADAD = SetByteIn(DMADAD, val, addr & 3); + break; + + case 0x08: // DMACNT_L B0 + case 0x09: // DMACNT_L B1 + DMACNT_L = SetByteIn(DMACNT_L, val, addr & 1); + break; + case 0x0A: // DMACNT_H B0 + case 0x0B: // DMACNT_H B1 + DMACNT_H = SetByteIn(DMACNT_H, val, addr & 1); + UpdateControl(); + break; + } + } + + public void UpdateControl() + { + DestAddrCtrl = (DmaDestAddrCtrl)BitRange(DMACNT_H, 5, 6); + SrcAddrCtrl = (DmaSrcAddrCtrl)BitRange(DMACNT_H, 7, 8); + Repeat = BitTest(DMACNT_H, 9); + TransferType = BitTest(DMACNT_H, 10); + GamePakDRQ = BitTest(DMACNT_H, 11); + StartTiming = (DmaStartTimingGba)BitRange(DMACNT_H, 12, 13); + FinishedIRQ = BitTest(DMACNT_H, 14); + if (BitTest(DMACNT_H, 15)) + { + Enable(); + } + else + { + Disable(); + } + } + + public uint GetControl() + { + uint val = 0; + val |= ((uint)DestAddrCtrl & 0b11) << 5; + val |= ((uint)SrcAddrCtrl & 0b11) << 7; + if (Repeat) val = BitSet(val, 9); + if (TransferType) val = BitSet(val, 10); + if (GamePakDRQ) val = BitSet(val, 11); + val |= ((uint)StartTiming & 0b11) << 12; + if (FinishedIRQ) val = BitSet(val, 14); + if (Enabled) val = BitSet(val, 15); + + DMACNT_H = val; + + return val; + } + + public void Enable() + { + if (!Enabled) + { + DmaSource = DMASAD; + DmaDest = DMADAD; + DmaLength = DMACNT_L; + } + + Enabled = true; + GetControl(); + } + + public void Disable() + { + Enabled = false; + GetControl(); + } + } + + public unsafe sealed class DmaGba + { + Gba Gba; + + public DmaChannelGba[] Ch = new DmaChannelGba[4] { + new DmaChannelGba(), + new DmaChannelGba(), + new DmaChannelGba(), + new DmaChannelGba(), + }; + + static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF }; + static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF }; + + public bool DmaLock; + + public DmaGba(Gba gba) + { + Gba = gba; + } + + public byte ReadHwio8(uint addr) + { + if (addr >= 0x40000B0 && addr <= 0x40000BB) + { + return Ch[0].ReadHwio8(addr - 0x40000B0); + } + else if (addr >= 0x40000BC && addr <= 0x40000C7) + { + return Ch[1].ReadHwio8(addr - 0x40000BC); + } + else if (addr >= 0x40000C8 && addr <= 0x40000D3) + { + return Ch[2].ReadHwio8(addr - 0x40000C8); + } + else if (addr >= 0x40000D4 && addr <= 0x40000DF) + { + return Ch[3].ReadHwio8(addr - 0x40000D4); + } + throw new Exception("This shouldn't happen."); + } + + public void WriteHwio8(uint addr, byte val) + { + if (addr >= 0x40000B0 && addr <= 0x40000BB) + { + bool oldEnabled = Ch[0].Enabled; + Ch[0].WriteHwio8(addr - 0x40000B0, val); + if (!oldEnabled && Ch[0].Enabled) ExecuteImmediate(0); + return; + } + else if (addr >= 0x40000BC && addr <= 0x40000C7) + { + bool oldEnabled = Ch[1].Enabled; + Ch[1].WriteHwio8(addr - 0x40000BC, val); + if (!oldEnabled && Ch[1].Enabled) ExecuteImmediate(1); + return; + } + else if (addr >= 0x40000C8 && addr <= 0x40000D3) + { + bool oldEnabled = Ch[2].Enabled; + Ch[2].WriteHwio8(addr - 0x40000C8, val); + if (!oldEnabled && Ch[2].Enabled) ExecuteImmediate(2); + return; + } + else if (addr >= 0x40000D4 && addr <= 0x40000DF) + { + bool oldEnabled = Ch[3].Enabled; + Ch[3].WriteHwio8(addr - 0x40000D4, val); + if (!oldEnabled && Ch[3].Enabled) ExecuteImmediate(3); + return; + } + throw new Exception("This shouldn't happen."); + } + + public void ExecuteDma(DmaChannelGba c, uint ci) + { + DmaLock = true; + + // Least significant 28 (or 27????) bits + c.DmaSource &= DmaSourceMask[ci]; + c.DmaDest &= DmaDestMask[ci]; + + if (ci == 3) + { + // DMA 3 is 16-bit length + c.DmaLength &= 0b1111111111111111; + // Value of zero is treated as maximum length + if (c.DmaLength == 0) c.DmaLength = 0x10000; + } + else + { + // DMA 0-2 are 14-bit length + c.DmaLength &= 0b11111111111111; + // Value of zero is treated as maximum length + if (c.DmaLength == 0) c.DmaLength = 0x4000; + } + + // Console.WriteLine($"Starting DMA {ci}"); + // Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}"); + // Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}"); + // Console.WriteLine($"LENGTH: {Util.HexN(c.DmaLength, 4)}"); + + int destOffsPerUnit; + int sourceOffsPerUnit; + if (c.TransferType) + { + switch (c.DestAddrCtrl) + { + case DmaDestAddrCtrl.Increment: destOffsPerUnit = +4; break; + case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -4; break; + case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +4; break; + default: destOffsPerUnit = 0; break; + } + switch (c.SrcAddrCtrl) + { + case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +4; break; + case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -4; break; + default: sourceOffsPerUnit = 0; break; + } + } + else + { + switch (c.DestAddrCtrl) + { + case DmaDestAddrCtrl.Increment: destOffsPerUnit = +2; break; + case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -2; break; + case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +2; break; + default: destOffsPerUnit = 0; break; + } + switch (c.SrcAddrCtrl) + { + case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +2; break; + case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -2; break; + default: sourceOffsPerUnit = 0; break; + } + } + + uint origLength = c.DmaLength; + + + if (c.TransferType) + { + for (; c.DmaLength > 0; c.DmaLength--) + { + Gba.Mem.Write32(c.DmaDest & ~3u, Gba.Mem.Read32(c.DmaSource & ~3u)); + Gba.Tick(Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]); + Gba.Tick(Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]); + + c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest); + c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource); + } + } + else + { + for (; c.DmaLength > 0; c.DmaLength--) + { + Gba.Mem.Write16(c.DmaDest & ~1u, Gba.Mem.Read16(c.DmaSource & ~1u)); + Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaSource >> 24) & 0xF]); + Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaDest >> 24) & 0xF]); + + c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest); + c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource); + } + } + + if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload) + { + c.DmaLength = origLength; + + if (c.Repeat) + { + c.DmaDest = c.DMADAD; + } + } + + if (c.FinishedIRQ) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci); + } + + DmaLock = false; + } + + public void ExecuteSoundDma(DmaChannelGba c, uint ci) + { + DmaLock = true; + + // Least significant 28 (or 27????) bits + uint srcAddr = c.DmaSource & 0b1111111111111111111111111111; + uint destAddr = c.DmaDest & 0b111111111111111111111111111; + + // 4 units of 32bits (16 bytes) are transferred to FIFO_A or FIFO_B + for (uint i = 0; i < 4; i++) + { + uint val = Gba.Mem.Read32(srcAddr + 0); + if (destAddr == 0x40000A0) + { + Gba.GbaAudio.A.Insert((byte)val); + Gba.GbaAudio.A.Insert((byte)(val >>= 8)); + Gba.GbaAudio.A.Insert((byte)(val >>= 8)); + Gba.GbaAudio.A.Insert((byte)(val >>= 8)); + } + else if (destAddr == 0x40000A4) + { + Gba.GbaAudio.B.Insert((byte)val); + Gba.GbaAudio.B.Insert((byte)(val >>= 8)); + Gba.GbaAudio.B.Insert((byte)(val >>= 8)); + Gba.GbaAudio.B.Insert((byte)(val >>= 8)); + } + else + { + Gba.Mem.Write8(destAddr + 0, (byte)val); + Gba.Mem.Write8(destAddr + 1, (byte)(val >>= 8)); + Gba.Mem.Write8(destAddr + 2, (byte)(val >>= 8)); + Gba.Mem.Write8(destAddr + 3, (byte)(val >>= 8)); + } + + switch (c.SrcAddrCtrl) + { + case DmaSrcAddrCtrl.Increment: srcAddr += 4; break; + case DmaSrcAddrCtrl.Decrement: srcAddr -= 4; break; + case DmaSrcAddrCtrl.Fixed: break; + } + + // Applying proper timing to sound DMAs causes crackling in certain games including PMD. + // This only happens with scheduled timers, which leads me to believe the real problem is in there. + // PROBLEM SOLVED.... my timers were 1 cycle too slow to reload + Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]); + Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]); + } + + c.DmaSource = srcAddr; + + if (c.FinishedIRQ) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci); + } + + DmaLock = false; + } + + + public void ExecuteImmediate(uint ci) + { + DmaChannelGba c = Ch[ci]; + + if (c.Enabled && c.StartTiming == DmaStartTimingGba.Immediately) + { + c.Disable(); + + ExecuteDma(c, ci); + } + } + + public void RepeatFifoA() + { + if (!DmaLock) + { + if (Ch[1].StartTiming == DmaStartTimingGba.Special) + { + ExecuteSoundDma(Ch[1], 1); + } + } + } + public void RepeatFifoB() + { + if (!DmaLock) + { + if (Ch[2].StartTiming == DmaStartTimingGba.Special) + { + ExecuteSoundDma(Ch[2], 2); + } + } + } + + public void RepeatHblank() + { + if (!DmaLock) + { + for (uint ci = 0; ci < 4; ci++) + { + DmaChannelGba c = Ch[ci]; + if (c.StartTiming == DmaStartTimingGba.HBlank) + { + c.DmaLength = c.DMACNT_L; + ExecuteDma(c, ci); + } + } + } + } + + public void RepeatVblank() + { + if (!DmaLock) + { + for (uint ci = 0; ci < 4; ci++) + { + DmaChannelGba c = Ch[ci]; + if (c.StartTiming == DmaStartTimingGba.VBlank) + { + c.DmaLength = c.DMACNT_L; + ExecuteDma(c, ci); + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/DmaGba.cs.meta b/Assets/emulator/DmaGba.cs.meta new file mode 100644 index 0000000..d9b22c4 --- /dev/null +++ b/Assets/emulator/DmaGba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5c4ffd474f58e654b873b37f5ca89d16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/DmaNds.cs b/Assets/emulator/DmaNds.cs new file mode 100644 index 0000000..6b784c4 --- /dev/null +++ b/Assets/emulator/DmaNds.cs @@ -0,0 +1,417 @@ +using static OptimeGBA.Bits; +using static Util; +using System; + + +namespace OptimeGBA +{ + public enum DmaStartTimingNds9 : byte + { + Immediately = 0, + VBlank = 1, + HBlank = 2, + UponRenderBegin = 3, + MainMemoryDisplay = 4, + Slot1 = 5, + Slot2 = 6, + GeometryCommandFifo = 7, + } + + public enum DmaStartTimingNds7 : byte + { + Immediately = 0, + VBlank = 1, + Slot1 = 2, + Misc = 3, + } + + public sealed class DmaChannelNds + { + public bool Nds7; + + public DmaChannelNds(bool nds7) + { + Nds7 = nds7; + } + + public uint DMASAD; + public uint DMADAD; + public uint DMACNT_L; + + public uint DmaSource; + public uint DmaDest; + public uint DmaLength; + + // DMACNT_H + public DmaDestAddrCtrl DestAddrCtrl; + public DmaSrcAddrCtrl SrcAddrCtrl; + public bool Repeat; + public bool TransferType; + public byte StartTiming; + public bool FinishedIRQ; + public bool Enabled; // Don't directly set to false, use Disable() + + public uint DMACNT_H; + + public byte ReadHwio8(uint addr) + { + // DMASAD, DMADAD, and DMACNT_L are write-only + byte val = 0; + switch (addr) + { + case 0x0A: // DMACNT_H B0 + case 0x0B: // DMACNT_H B1 + val = GetByteIn(GetControl(), addr & 1); + break; + } + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x00: // DMASAD B0 + case 0x01: // DMASAD B1 + case 0x02: // DMASAD B2 + case 0x03: // DMASAD B3 + DMASAD = SetByteIn(DMASAD, val, addr & 3); + break; + + case 0x04: // DMADAD B0 + case 0x05: // DMADAD B1 + case 0x06: // DMADAD B2 + case 0x07: // DMADAD B3 + DMADAD = SetByteIn(DMADAD, val, addr & 3); + break; + + case 0x08: // DMACNT_L B0 + case 0x09: // DMACNT_L B1 + DMACNT_L = SetByteIn(DMACNT_L, val, addr & 1); + break; + case 0x0A: // DMACNT_H B0 + case 0x0B: // DMACNT_H B1 + DMACNT_H = SetByteIn(DMACNT_H, val, addr & 1); + UpdateControl(); + break; + } + } + + public void UpdateControl() + { + DestAddrCtrl = (DmaDestAddrCtrl)BitRange(DMACNT_H, 5, 6); + SrcAddrCtrl = (DmaSrcAddrCtrl)BitRange(DMACNT_H, 7, 8); + Repeat = BitTest(DMACNT_H, 9); + TransferType = BitTest(DMACNT_H, 10); + if (!Nds7) + { + StartTiming = (byte)BitRange(DMACNT_H, 11, 13); + } + else + { + StartTiming = (byte)BitRange(DMACNT_H, 12, 13); + } + FinishedIRQ = BitTest(DMACNT_H, 14); + if (BitTest(DMACNT_H, 15)) + { + Enable(); + } + else + { + Disable(); + } + } + + public uint GetControl() + { + uint val = 0; + val |= ((uint)DestAddrCtrl & 0b11) << 5; + val |= ((uint)SrcAddrCtrl & 0b11) << 7; + if (Repeat) val = BitSet(val, 9); + if (TransferType) val = BitSet(val, 10); + val |= ((uint)StartTiming & 0b111) << 11; + if (FinishedIRQ) val = BitSet(val, 14); + if (Enabled) val = BitSet(val, 15); + + DMACNT_H = val; + + return val; + } + + public void Enable() + { + if (!Enabled) + { + DmaSource = DMASAD; + DmaDest = DMADAD; + DmaLength = DMACNT_L; + } + + Enabled = true; + GetControl(); + } + + public void Disable() + { + Enabled = false; + GetControl(); + } + } + + public unsafe sealed class DmaNds + { + bool Nds7; + Memory Mem; + HwControl HwControl; + + public DmaChannelNds[] Ch; + static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF }; + static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF }; + + public byte[] DmaFill = new byte[16]; + + public bool DmaLock; + + public DmaNds(bool nds7, Memory mem, HwControlNds hwControl) + { + Nds7 = nds7; + Mem = mem; + HwControl = hwControl; + + Ch = new DmaChannelNds[4] { + new DmaChannelNds(Nds7), + new DmaChannelNds(Nds7), + new DmaChannelNds(Nds7), + new DmaChannelNds(Nds7), + }; + } + + public byte ReadHwio8(uint addr) + { + if (addr >= 0x40000B0 && addr <= 0x40000BB) + { + return Ch[0].ReadHwio8(addr - 0x40000B0); + } + else if (addr >= 0x40000BC && addr <= 0x40000C7) + { + return Ch[1].ReadHwio8(addr - 0x40000BC); + } + else if (addr >= 0x40000C8 && addr <= 0x40000D3) + { + return Ch[2].ReadHwio8(addr - 0x40000C8); + } + else if (addr >= 0x40000D4 && addr <= 0x40000DF) + { + return Ch[3].ReadHwio8(addr - 0x40000D4); + } + else if (addr >= 0x40000E0 && addr <= 0x40000EF) + { + return DmaFill[addr & 0xF]; + } + throw new Exception("This shouldn't happen."); + } + + public void WriteHwio8(uint addr, byte val) + { + if (addr >= 0x40000B0 && addr <= 0x40000BB) + { + bool oldEnabled = Ch[0].Enabled; + Ch[0].WriteHwio8(addr - 0x40000B0, val); + if (!oldEnabled && Ch[0].Enabled) ExecuteImmediate(0); + return; + } + else if (addr >= 0x40000BC && addr <= 0x40000C7) + { + bool oldEnabled = Ch[1].Enabled; + Ch[1].WriteHwio8(addr - 0x40000BC, val); + if (!oldEnabled && Ch[1].Enabled) ExecuteImmediate(1); + return; + } + else if (addr >= 0x40000C8 && addr <= 0x40000D3) + { + bool oldEnabled = Ch[2].Enabled; + Ch[2].WriteHwio8(addr - 0x40000C8, val); + if (!oldEnabled && Ch[2].Enabled) ExecuteImmediate(2); + return; + } + else if (addr >= 0x40000D4 && addr <= 0x40000DF) + { + bool oldEnabled = Ch[3].Enabled; + Ch[3].WriteHwio8(addr - 0x40000D4, val); + if (!oldEnabled && Ch[3].Enabled) ExecuteImmediate(3); + return; + } + else if (addr >= 0x40000E0 && addr <= 0x40000EF) + { + DmaFill[addr & 0xF] = val; + return; + } + throw new Exception("This shouldn't happen."); + } + + public void ExecuteDma(DmaChannelNds c, uint ci) + { + + DmaLock = true; + + // Console.WriteLine("NDS: Executing DMA"); + // Console.WriteLine("Source: " + Util.Hex(c.DmaSource, 8)); + // Console.WriteLine("Dest: " + Util.Hex(c.DmaDest, 8)); + // Console.WriteLine("Length: " + c.DmaLength); + + if (!Nds7) + { + c.DmaSource &= 0x0FFFFFFF; + c.DmaDest &= 0x0FFFFFFF; + + // All NDS9 DMAs use 21-bit length + c.DmaLength &= 0x1FFFFF; + // Value of zero is treated as maximum length + if (c.DmaLength == 0) c.DmaLength = 0x200000; + } + else + { + // Least significant 28 (or 27????) bits + c.DmaSource &= DmaSourceMask[ci]; + c.DmaDest &= DmaDestMask[ci]; + + if (ci == 3) + { + // DMA 3 is 16-bit length + c.DmaLength &= 0xFFFF; + // Value of zero is treated as maximum length + if (c.DmaLength == 0) c.DmaLength = 0x10000; + } + else + { + // DMA 0-2 are 14-bit length + c.DmaLength &= 0x3FFF; + // Value of zero is treated as maximum length + if (c.DmaLength == 0) c.DmaLength = 0x4000; + } + } + + // if (c.DmaLength != 1 && ci == 3) + // { + // Console.WriteLine(((DmaStartTimingNds7)c.StartTiming).ToString()); + // Console.WriteLine("DMA length " + c.DmaLength); + // } + + // Console.WriteLine($"Starting DMA {ci}"); + // Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}"); + // Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}"); + // Console.WriteLine($"LENGTH: {Util.HexN(c.DmaLength, 4)}"); + + int destOffsPerUnit; + int sourceOffsPerUnit; + if (c.TransferType) + { + switch (c.DestAddrCtrl) + { + case DmaDestAddrCtrl.Increment: destOffsPerUnit = +4; break; + case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -4; break; + case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +4; break; + default: destOffsPerUnit = 0; break; + } + switch (c.SrcAddrCtrl) + { + case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +4; break; + case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -4; break; + default: sourceOffsPerUnit = 0; break; + } + } + else + { + switch (c.DestAddrCtrl) + { + case DmaDestAddrCtrl.Increment: destOffsPerUnit = +2; break; + case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -2; break; + case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +2; break; + default: destOffsPerUnit = 0; break; + } + switch (c.SrcAddrCtrl) + { + case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +2; break; + case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -2; break; + default: sourceOffsPerUnit = 0; break; + } + } + + uint origLength = c.DmaLength; + + // TODO: NDS DMA timings + if (c.TransferType) + { + for (; c.DmaLength > 0; c.DmaLength--) + { + Mem.Write32(c.DmaDest & ~3u, Mem.Read32(c.DmaSource & ~3u)); + // Gba.Tick(Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]); + // Gba.Tick(Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]); + + c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest); + c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource); + } + } + else + { + for (; c.DmaLength > 0; c.DmaLength--) + { + Mem.Write16(c.DmaDest & ~1u, Mem.Read16(c.DmaSource & ~1u)); + // Gba.Tick(Nds.Timing8And16[(c.DmaSource >> 24) & 0xF]); + // Gba.Tick(Nds.Timing8And16[(c.DmaDest >> 24) & 0xF]); + + c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest); + c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource); + } + } + + if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload) + { + if (c.Repeat) + { + c.DmaDest = c.DMADAD; + } + } + + + if (c.FinishedIRQ) + { + HwControl.FlagInterrupt((uint)InterruptNds.Dma0 + ci); + } + + DmaLock = false; + } + + public void ExecuteImmediate(uint ci) + { + DmaChannelNds c = Ch[ci]; + // Console.WriteLine($"NDS{(Nds9 ? "9" : "7")}: Ch{ci} immediate DMA from:{Hex(c.DMASAD, 8)} to:{Hex(c.DMADAD, 8)}"); + + if (c.Enabled && c.StartTiming == (byte)DmaStartTimingNds9.Immediately) + { + c.Disable(); + + ExecuteDma(c, ci); + } + } + + public bool Repeat(byte val) + { + bool executed = false; + if (!DmaLock) + { + for (uint ci = 0; ci < 4; ci++) + { + DmaChannelNds c = Ch[ci]; + if (c.StartTiming == val) + { + executed = true; + c.DmaLength = c.DMACNT_L; + ExecuteDma(c, ci); + } + } + } + return executed; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/DmaNds.cs.meta b/Assets/emulator/DmaNds.cs.meta new file mode 100644 index 0000000..513c504 --- /dev/null +++ b/Assets/emulator/DmaNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de4ad34ffe4c2a5458453cc7c40a4065 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/GbAudio.cs b/Assets/emulator/GbAudio.cs new file mode 100644 index 0000000..3d9d705 --- /dev/null +++ b/Assets/emulator/GbAudio.cs @@ -0,0 +1,1186 @@ +using System; +using static OptimeGBA.Bits; +namespace OptimeGBA +{ + public sealed class GbAudio + { + public static byte[] SEVEN_BIT_NOISE = GenerateNoiseBuffer(true); + public static byte[] FIFTEEN_BIT_NOISE = GenerateNoiseBuffer(false); + public static float[] DAC_TABLE = GenerateDACTable(); + public static int SAMPLE_RATE = 32768; // Digital mixing rate is 32768 Hz on GBA + public static int SAMPLE_TIME_MAX = 4194304 / SAMPLE_RATE; + + readonly double CAPACITOR_FACTOR = Math.Pow(0.999958, (4194304 / SAMPLE_RATE)); // DMG + + public static float[] GenerateDACTable() + { + float[] table = new float[32]; + for (int i = 0; i < 16; i++) + { + table[i] = ((i / 15) * 2) - 1; + } + return table; + } + + public static byte[] GenerateNoiseBuffer(bool putBitBack) + { + uint seed = 0xFF; + + byte[] waveTable = new byte[32768]; + for (int i = 0; i < 32768; i++) + { + waveTable[i] = (byte)((seed & 1) ^ 1); + + int bit = (int)((seed) ^ (seed >> 1)); + bit &= 1; + + seed = (uint)(seed >> 1) | (uint)(bit << 14); + + if (putBitBack == true) + { + seed &= ~BIT_7; + seed |= (uint)(bit << 6); + } + } + return waveTable; + } + + + public short Out1; + public short Out2; + + readonly static uint[][] PULSE_DUTY = new uint[][] { + new uint[] {1, 1, 1, 1, 1, 1, 1, 0}, + new uint[] {0, 1, 1, 1, 1, 1, 1, 0}, + new uint[] {0, 1, 1, 1, 1, 0, 0, 0}, + new uint[] {1, 0, 0, 0, 0, 0, 0, 1}, + }; + + uint Pulse1_pos; + uint Pulse1_width = 2; + uint Pulse1_period = 8192; + int Pulse1_timer; + + public bool enabled = false; + + int ticksEnvelopePulse1 = 0; + int ticksEnvelopePulse2 = 0; + int ticksEnvelopeNoise = 0; + + int clockPulse1FreqSweep = 0; + bool freqSweepEnabled = false; + + int frameSequencerStep = 0; + + byte[] soundRegisters = new byte[65536]; + + void advanceFrameSequencer() + { + if (this.enabled) + { + // 512Hz Frame Sequencer + switch (this.frameSequencerStep) + { + case 0: + case 4: + this.frameSequencerLength(); + this.update(); + break; + case 2: + case 6: + this.frameSequencerLength(); + this.frameSequencerFrequencySweep(); + this.update(); + break; + case 7: + this.frameSequencerVolumeEnvelope(); + this.update(); + break; + default: + break; + } + + this.frameSequencerStep++; this.frameSequencerStep &= 0b111; + } + } + + // #region Channel params + public bool pulse1_enabled = false; + public int pulse1_width = 3; + public bool pulse1_dacEnabled = false; + public bool pulse1_lengthEnable = false; + public int pulse1_lengthCounter = 0; + public int pulse1_frequencyUpper = 0; + public int pulse1_frequencyLower = 0; + public int pulse1_volume = 0; + public bool pulse1_volumeEnvelopeUp = false; + public int pulse1_volumeEnvelopeSweep = 4; + public int pulse1_volumeEnvelopeStart = 0; + public bool pulse1_outputLeft = false; + public bool pulse1_outputRight = false; + public int pulse1_freqSweepPeriod = 0; + public bool pulse1_freqSweepUp = false; + public int pulse1_freqSweepShift = 0; + public bool pulse1_updated = true; + void pulse1_trigger() + { + if (this.pulse1_lengthCounter == 0 || this.pulse1_lengthEnable == false) + { + this.pulse1_lengthCounter = 64; + } + this.pulse1_volume = this.pulse1_volumeEnvelopeStart; + if (this.pulse1_dacEnabled) + { + this.pulse1_enabled = true; + } + this.clockPulse1FreqSweep = 0; + + this.freqSweepEnabled = this.pulse1_freqSweepShift != 0 || this.pulse1_freqSweepPeriod != 0; + this.reloadPulse1Period(); + this.updatePulse1Val(); + } + public float pulse1_getFrequencyHz() + { + float frequency = (this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower; + return 131072 / (2048 - frequency); + } + + public bool pulse2_enabled = false; + public int pulse2_width = 3; + public bool pulse2_dacEnabled = false; + public bool pulse2_lengthEnable = false; + public int pulse2_lengthCounter = 0; + public int pulse2_frequencyUpper = 0; + public int pulse2_frequencyLower = 0; + public int pulse2_volume = 0; + public bool pulse2_volumeEnvelopeUp = false; + public int pulse2_volumeEnvelopeSweep = 4; + public int pulse2_volumeEnvelopeStart = 0; + public bool pulse2_outputLeft = false; + public bool pulse2_outputRight = false; + public bool pulse2_updated = true; + void pulse2_trigger() + { + if (this.pulse2_lengthCounter == 0 || this.pulse2_lengthEnable == false) + { + this.pulse2_lengthCounter = 64; + } + this.pulse2_volume = this.pulse2_volumeEnvelopeStart; + if (this.pulse2_dacEnabled) + { + this.pulse2_enabled = true; + } + this.reloadPulse2Period(); + this.updatePulse2Val(); + } + public float pulse2_getFrequencyHz() + { + float frequency = (this.pulse2_frequencyUpper << 8) | this.pulse2_frequencyLower; + return 131072 / (2048 - frequency); + } + + public bool wave_bank = false; + public bool wave_dimension = true; + public bool wave_enabled = false; + public bool wave_dacEnabled = false; + public bool wave_lengthEnable = true; + public int wave_lengthCounter = 0; + public int wave_frequencyUpper = 0; + public int wave_frequencyLower = 0; + public int wave_volume = 0; + public int wave_oldVolume = 0; + public byte[] wave_waveTable0 = new byte[32]; + public byte[] wave_waveTable1 = new byte[32]; + public bool wave_outputLeft = false; + public bool wave_outputRight = false; + public float wave_getFrequencyHz() + { + float frequency = (this.wave_frequencyUpper << 8) | this.wave_frequencyLower; + return 65536 / (2048 - frequency); + } + + public bool noise_enabled = false; + public int noise_divisorCode = 0; + public bool noise_lengthEnable = false; + public int noise_lengthCounter = 0; + public bool noise_dacEnabled = false; + public int noise_volume = 0; + public bool noise_volumeEnvelopeUp = false; + public int noise_volumeEnvelopeSweep = 4; + public int noise_volumeEnvelopeStart = 0; + public bool noise_outputLeft = false; + public bool noise_outputRight = false; + public int noise_shiftClockFrequency = 0; + public bool noise_counterStep = false; + public int noise_envelopeSweep = 0; + + // #endregion + + + bool nightcoreMode = false; + + bool vinLeftEnable = false; + bool vinRightEnable = false; + public int leftMasterVol = 0; + public int rightMasterVol = 0; + public int leftMasterVolMul = 0; + public int rightMasterVolMul = 0; + + public byte pulse1Val = 0; + public byte pulse2Val = 0; + public byte waveVal = 0; + public byte noiseVal = 0; + + int pulse1Pos = 0; + int pulse2Pos = 0; + int wavePos = 0; + int noisePos = 0; + + int pulse1FreqTimer = 0; + int pulse2FreqTimer = 0; + int waveFreqTimer = 0; + int noiseFreqTimer = 0; + + int pulse1Period = 0; + int pulse2Period = 0; + int wavePeriod = 0; + int noisePeriod = 0; + + float capacitor1 = 0; + float capacitor2 = 0; + + void calcPulse1Period() + { + this.pulse1Period = (2048 - ((this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower)) * 4; + if (this.nightcoreMode) this.pulse1Period = (int)(this.pulse1Period * 0.5); + } + void calcPulse2Period() + { + this.pulse2Period = (2048 - ((this.pulse2_frequencyUpper << 8) | this.pulse2_frequencyLower)) * 4; + if (this.nightcoreMode) this.pulse2Period = (int)(this.pulse2Period * 0.5); + } + void calcWavePeriod() + { + this.wavePeriod = (2048 - ((this.wave_frequencyUpper << 8) | this.wave_frequencyLower)) * 2; + if (this.nightcoreMode) this.wavePeriod = (int)(this.wavePeriod * 0.5); + } + void calcNoisePeriod() + { + int[] noiseDivider = new int[] { 8, 16, 32, 48, 64, 80, 96, 112 }; + this.noisePeriod = (noiseDivider[this.noise_divisorCode] << this.noise_shiftClockFrequency); + } + + void reloadPulse1Period() { this.pulse1FreqTimer = this.pulse1Period; } + void reloadPulse2Period() { this.pulse2FreqTimer = this.pulse2Period; } + void reloadWavePeriod() { this.waveFreqTimer = this.wavePeriod; } + void reloadNoisePeriod() { this.noiseFreqTimer = this.noisePeriod; } + + void noise_trigger() + { + if (this.noise_dacEnabled) + this.noise_enabled = true; + + if (this.noise_lengthCounter == 0 || this.noise_lengthEnable == false) + { + this.noise_lengthCounter = 64; + } + this.noise_volume = this.noise_volumeEnvelopeStart; + this.updateNoiseVal(); + } + + void wave_trigger() + { + if (this.wave_lengthCounter == 0 || this.wave_lengthEnable == false) + { + this.wave_lengthCounter = 256; + } + if (this.wave_dacEnabled) + { + this.wave_enabled = true; + } + this.updateWaveVal(); + } + + public bool enable1Out = true; + public bool enable2Out = true; + public bool enable3Out = true; + public bool enable4Out = true; + + void updatePulse1Val() + { + this.pulse1Val = (byte)(this.pulse1_enabled ? PULSE_DUTY[this.pulse1_width][this.pulse1Pos] * this.pulse1_volume : 0); + } + void updatePulse2Val() + { + this.pulse2Val = (byte)(this.pulse2_enabled ? PULSE_DUTY[this.pulse2_width][this.pulse2Pos] * this.pulse2_volume : 0); + } + void updateWaveVal() + { + int[] waveShift = new int[] { 4, 0, 1, 2 }; + if (this.wave_dimension) + { + if (this.wavePos < 32) + { + int pos = this.wavePos - 0; + this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable0[this.wavePos] >> waveShift[this.wave_volume] : 0); + } + else + { + int pos = this.wavePos - 32; + this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable1[this.wavePos] >> waveShift[this.wave_volume] : 0); + } + } + else + { + if (this.wave_bank) + { + this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable1[this.wavePos] >> waveShift[this.wave_volume] : 0); + } + else + { + this.waveVal = (byte)(this.wave_enabled ? this.wave_waveTable0[this.wavePos] >> waveShift[this.wave_volume] : 0); + } + } + } + void updateNoiseVal() + { + this.noiseVal = (byte)(this.noise_enabled ? (this.noise_counterStep ? SEVEN_BIT_NOISE[this.noisePos] : FIFTEEN_BIT_NOISE[this.noisePos]) * this.noise_volume : 0); + } + + uint pendingCycles = 0; + + const uint FrameSequencerMax = 8192; + uint FrameSequencerTimer = 0; + + public void Tick(uint cycles) + { + this.pendingCycles += cycles; + + this.FrameSequencerTimer += cycles; + if (this.FrameSequencerTimer >= FrameSequencerMax) + { + this.FrameSequencerTimer -= FrameSequencerMax; + + this.advanceFrameSequencer(); + } + + if (this.enabled) + { + if (this.pulse1_enabled) this.pulse1FreqTimer -= (int)cycles; + if (this.pulse2_enabled) this.pulse2FreqTimer -= (int)cycles; + if (this.wave_enabled) this.waveFreqTimer -= (int)cycles; + if (this.noise_enabled) this.noiseFreqTimer -= (int)cycles; + + float in1 = 0; + float in2 = 0; + + // Note: -1 value when disabled is the DAC DC offset + + if (this.pulse1_dacEnabled) + { + if (this.pulse1Period > 0) + { + while (this.pulse1FreqTimer < 0) + { + this.pulse1FreqTimer += this.pulse1Period; + + this.pulse1Pos++; + this.pulse1Pos &= 7; + + this.updatePulse1Val(); + } + } + if (this.enable1Out) + { + if (this.pulse1_outputLeft) in1 += this.pulse1Val; + if (this.pulse1_outputRight) in2 += this.pulse1Val; + } + } + if (this.pulse2_dacEnabled) + { + if (this.pulse2Period > 0) + { + while (this.pulse2FreqTimer < 0) + { + this.pulse2FreqTimer += this.pulse2Period; + + this.pulse2Pos++; + this.pulse2Pos &= 7; + + this.updatePulse2Val(); + } + } + if (this.enable2Out) + { + if (this.pulse2_outputLeft) in1 += this.pulse2Val; + if (this.pulse2_outputRight) in2 += this.pulse2Val; + } + } + if (this.wave_dacEnabled) + { + if (this.wavePeriod > 0) + { + while (this.waveFreqTimer < 0) + { + this.waveFreqTimer += this.wavePeriod; + + this.wavePos++; + + if (this.wave_dimension) + { + this.wavePos &= 63; + } + else + { + this.wavePos &= 31; + } + + this.updateWaveVal(); + } + } + if (this.enable3Out) + { + if (this.wave_outputLeft) in1 += this.waveVal; + if (this.wave_outputRight) in2 += this.waveVal; + } + } + if (this.noise_dacEnabled) + { + if (this.noisePeriod > 0) + { + while (this.noiseFreqTimer < 0) + { + this.noiseFreqTimer += this.noisePeriod; + + this.noisePos++; + this.noisePos &= 32767; + + this.updateNoiseVal(); + } + } + if (this.enable4Out) + { + if (this.noise_outputLeft) in1 += this.noiseVal; + if (this.noise_outputRight) in2 += this.noiseVal; + } + } + + in1 *= this.leftMasterVolMul; + in2 *= this.rightMasterVolMul; + + Out1 = (short)(in1 * PsgFactor); + Out2 = (short)(in2 * PsgFactor); + } + + // The wave sample is visible to the CPU, so it's gotta be updated immediately + } + + public uint PsgFactor = 8; + + void update() + { + // this.fastForwardSound((int)this.pendingCycles); + this.pendingCycles = 0; + } + + private void frameSequencerFrequencySweep() + { + // writeDebug("Frequency sweep") + int actualPeriod = this.pulse1_freqSweepPeriod; + if (actualPeriod == 0) actualPeriod = 8; + if (this.clockPulse1FreqSweep >= actualPeriod) + { + this.clockPulse1FreqSweep = 0; + if (this.freqSweepEnabled == true) + { + this.applyFrequencySweep(); + } + + // writeDebug("abs(Range): " + diff); + // writeDebug("Resulting frequency: " + this.pulse1_frequencyHz); + + } + + this.clockPulse1FreqSweep++; + } + + private void applyFrequencySweep() + { + uint freq = (uint)((this.pulse1_frequencyUpper << 8) | this.pulse1_frequencyLower); + uint diff = freq >> this.pulse1_freqSweepShift; + uint newFreq = this.pulse1_freqSweepUp ? freq + diff : freq - diff; + freq = newFreq; + if (newFreq > 2047) + { + this.pulse1_enabled = false; + } + if (this.pulse1_freqSweepPeriod != 0 && this.pulse1_freqSweepShift != 0) + { + + this.pulse1_frequencyLower = (int)(freq & 0xFF); + this.pulse1_frequencyUpper = (int)((freq >> 8) & 0xFF); + } + + this.calcPulse1Period(); + this.updatePulse1Val(); + } + + private void frameSequencerVolumeEnvelope() + { + this.ticksEnvelopePulse1--; + if (this.ticksEnvelopePulse1 <= 0) + { + if (this.pulse1_volumeEnvelopeSweep != 0) + { + if (this.pulse1_volumeEnvelopeUp == true) + { + if (this.pulse1_volume < 15) + { + this.pulse1_volume++; + } + } + else + { + if (this.pulse1_volume > 0) + { + this.pulse1_volume--; + } + } + this.updatePulse1Val(); + } + this.ticksEnvelopePulse1 = this.pulse1_volumeEnvelopeSweep; + } + + this.ticksEnvelopePulse2--; + if (this.ticksEnvelopePulse2 <= 0) + { + if (this.pulse2_volumeEnvelopeSweep != 0) + { + if (this.pulse2_volumeEnvelopeUp == true) + { + if (this.pulse2_volume < 15) + { + this.pulse2_volume++; + } + } + else + { + if (this.pulse2_volume > 0) + { + this.pulse2_volume--; + } + } + this.updatePulse2Val(); + } + this.ticksEnvelopePulse2 = this.pulse2_volumeEnvelopeSweep; + } + + this.ticksEnvelopeNoise--; + if (this.ticksEnvelopeNoise <= 0) + { + if (this.noise_volumeEnvelopeSweep != 0) + { + if (this.noise_volumeEnvelopeUp == true) + { + if (this.noise_volume < 15) + { + this.noise_volume++; + } + } + else + { + if (this.noise_volume > 0) + { + this.noise_volume--; + } + } + this.updateNoiseVal(); + } + this.ticksEnvelopeNoise = this.noise_volumeEnvelopeSweep; + } + } + + private void frameSequencerLength() + { + this.clockPulse1Length(); + this.clockPulse2Length(); + this.clockWaveLength(); + this.clockNoiseLength(); + } + + private void clockPulse1Length() + { + if (this.pulse1_lengthEnable == true && this.pulse1_lengthCounter > 0) + { + this.pulse1_lengthCounter--; + if (this.pulse1_lengthCounter == 0) + { + this.pulse1_enabled = false; + this.updatePulse1Val(); + } + } + } + + private void clockPulse2Length() + { + if (this.pulse2_lengthEnable == true && this.pulse2_lengthCounter > 0) + { + this.pulse2_lengthCounter--; + if (this.pulse2_lengthCounter == 0) + { + this.pulse2_enabled = false; + this.updatePulse2Val(); + } + } + } + + private void clockWaveLength() + { + if (this.wave_lengthEnable == true && this.wave_lengthCounter > 0) + { + this.wave_lengthCounter--; + if (this.wave_lengthCounter == 0) + { + this.wave_enabled = false; + this.updateWaveVal(); + } + } + } + + private void clockNoiseLength() + { + if (this.noise_lengthEnable == true && this.noise_lengthCounter > 0) + { + this.noise_lengthCounter--; + if (this.noise_lengthCounter == 0) + { + this.noise_enabled = false; + this.updateNoiseVal(); + } + } + } + + public void WriteHwio8(uint addr, byte value) + { + if (this.enabled) + { + switch (addr) + { + // Pulse 1 + case 0x60: // NR10 + this.pulse1_freqSweepPeriod = (value & 0b01110000) >> 4; // in 128ths of a second (0-7) + this.pulse1_freqSweepUp = ((value >> 3) & 1) == 0; // 0 == Add, 1 = Sub + this.pulse1_freqSweepShift = (value & 0b111); // 0-7; + this.updatePulse1Val(); + break; + case 0x62: // NR11 + this.pulse1_width = value >> 6; + this.pulse1_lengthCounter = 64 - (value & 0b111111); + this.pulse1Val = (byte)PULSE_DUTY[this.pulse1_width][this.pulse1Pos]; + this.updatePulse1Val(); + break; + case 0x63: // NR12 + { + bool newUp = ((value >> 3) & 1) == 1; + if (this.pulse1_enabled) + { + if (this.pulse1_volumeEnvelopeSweep == 0) + { + if (this.pulse1_volumeEnvelopeUp) + { + this.pulse1_volume += 1; + this.pulse1_volume &= 0xF; + } + else + { + this.pulse1_volume += 2; + this.pulse1_volume &= 0xF; + } + } + + if (this.pulse1_volumeEnvelopeUp != newUp) + this.pulse1_volume = 0; + } + + this.pulse1_volumeEnvelopeStart = (value >> 4) & 0xF; + this.pulse1_volumeEnvelopeUp = newUp; + this.pulse1_volumeEnvelopeSweep = value & 0b111; + this.pulse1_dacEnabled = (value & 0b11111000) != 0; + if (!this.pulse1_dacEnabled) this.pulse1_enabled = false; + this.updatePulse1Val(); + } + break; + case 0x64: // NR13 Low bits + this.pulse1_frequencyLower = value; + this.calcPulse1Period(); + this.updatePulse1Val(); + break; + case 0x65: // NR14 + this.pulse1_frequencyUpper = value & 0b111; + this.pulse1_lengthEnable = ((value >> 6) & 1) != 0; + // If the next step does not clock the length counter + if ( + ((this.frameSequencerStep + 1) & 7) == 1 || + ((this.frameSequencerStep + 1) & 7) == 3 || + ((this.frameSequencerStep + 1) & 7) == 5 || + ((this.frameSequencerStep + 1) & 7) == 7 + ) + { + this.clockPulse1Length(); + } + if (((value >> 7) & 1) != 0) + { + this.pulse1_trigger(); + } + this.calcPulse1Period(); + this.updatePulse1Val(); + break; + + // Pulse 2 + case 0x68: // NR21 + this.pulse2_width = value >> 6; + this.pulse2_lengthCounter = 64 - (value & 0b111111); + this.pulse2Val = (byte)PULSE_DUTY[this.pulse2_width][this.pulse2Pos]; + this.updatePulse2Val(); + break; + case 0x69: // NR22 + { + bool newUp = ((value >> 3) & 1) == 1; + + if (this.pulse2_enabled) + { + if (this.pulse2_volumeEnvelopeSweep == 0) + { + if (this.pulse2_volumeEnvelopeUp) + { + this.pulse2_volume += 1; + this.pulse2_volume &= 0xF; + } + else + { + this.pulse2_volume += 2; + this.pulse2_volume &= 0xF; + } + } + + if (this.pulse2_volumeEnvelopeUp != newUp) + this.pulse2_volume = 0; + } + + this.pulse2_volumeEnvelopeStart = (value >> 4) & 0xF; + this.pulse2_volumeEnvelopeUp = newUp; + this.pulse2_volumeEnvelopeSweep = value & 0b111; + this.pulse2_dacEnabled = (value & 0b11111000) != 0; + if (!this.pulse2_dacEnabled) this.pulse2_enabled = false; + } + this.updatePulse2Val(); + break; + case 0x6C: // NR23 + this.pulse2_frequencyLower = value; + this.calcPulse2Period(); + this.updatePulse2Val(); + break; + case 0x6D: // NR24 + this.pulse2_frequencyUpper = value & 0b111; + this.pulse2_lengthEnable = ((value >> 6) & 1) != 0; + // If the next step does not clock the length counter + if ( + ((this.frameSequencerStep + 1) & 7) == 1 || + ((this.frameSequencerStep + 1) & 7) == 3 || + ((this.frameSequencerStep + 1) & 7) == 5 || + ((this.frameSequencerStep + 1) & 7) == 7 + ) + { + this.clockPulse2Length(); + } + if (((value >> 7) & 1) != 0) + { + this.pulse2_trigger(); + } + this.calcPulse2Period(); + this.updatePulse2Val(); + break; + + // Wave + case 0x70: // NR30 + this.wave_dimension = BitTest(value, 5); + this.wave_bank = BitTest(value, 6); + this.wave_dacEnabled = BitTest(value, 7); + if (!this.wave_dacEnabled) this.wave_enabled = false; + this.updateWaveVal(); + break; + case 0x72: // NR31 + this.wave_lengthCounter = 256 - value; + this.updateWaveVal(); + break; + case 0x73: // NR32 + this.wave_volume = (value >> 5) & 0b11; + this.updateWaveVal(); + break; + case 0x74: // NR33 + this.wave_frequencyLower = value; + this.calcWavePeriod(); + this.updateWaveVal(); + break; + case 0x75: // NR34 + this.wave_frequencyUpper = value & 0b111; + // If the next step does not clock the length counter + if ( + ((this.frameSequencerStep + 1) & 7) == 1 || + ((this.frameSequencerStep + 1) & 7) == 3 || + ((this.frameSequencerStep + 1) & 7) == 5 || + ((this.frameSequencerStep + 1) & 7) == 7 + ) + { + this.clockWaveLength(); + } + if (((value >> 7) & 1) != 0) + { + this.wave_trigger(); + this.wavePos = 0; + this.reloadWavePeriod(); + } + this.wave_lengthEnable = ((value >> 6) & 1) != 0; + this.calcWavePeriod(); + this.updateWaveVal(); + break; + + // Noise + case 0x78: // NR41 + this.noise_lengthCounter = 64 - (value & 0b111111); // 6 bits + this.updateNoiseVal(); + break; + case 0x79: // NR42 + this.noise_volume = (value >> 4) & 0xF; + this.noise_volumeEnvelopeStart = (value >> 4) & 0xF; + this.noise_volumeEnvelopeUp = ((value >> 3) & 1) == 1; + this.noise_volumeEnvelopeSweep = value & 0b111; + this.noise_dacEnabled = (value & 0b11111000) != 0; + if (!this.noise_dacEnabled) this.noise_enabled = false; + this.updateNoiseVal(); + break; + case 0x7C: // NR43 + this.noise_shiftClockFrequency = (value >> 4) & 0xF; + this.noise_counterStep = ((value >> 3) & 1) != 0; + this.noise_divisorCode = (value & 0b111); + this.calcNoisePeriod(); + this.updateNoiseVal(); + break; + case 0x7D: // NR44 + // If the next step does not clock the length counter + if ( + ((this.frameSequencerStep + 1) & 7) == 1 || + ((this.frameSequencerStep + 1) & 7) == 3 || + ((this.frameSequencerStep + 1) & 7) == 5 || + ((this.frameSequencerStep + 1) & 7) == 7 + ) + { + this.clockWaveLength(); + } + if (((value >> 7) & 1) != 0) + { + this.noise_trigger(); + this.noisePos = 0; + this.reloadNoisePeriod(); + } + this.noise_lengthEnable = ((value >> 6) & 1) != 0; + this.updateNoiseVal(); + break; + + case 0x80: // NR50 + this.vinLeftEnable = (value & BIT_7) != 0; + this.vinRightEnable = (value & BIT_3) != 0; + this.leftMasterVol = (value >> 4) & 0b111; + this.rightMasterVol = (value >> 0) & 0b111; + this.leftMasterVolMul = this.leftMasterVol / 7; + this.rightMasterVolMul = this.rightMasterVol / 7; + break; + + // Panning + case 0x81: // NR51 + this.noise_outputRight = (value & BIT_7) != 0; + this.wave_outputRight = (value & BIT_6) != 0; + this.pulse2_outputRight = (value & BIT_5) != 0; + this.pulse1_outputRight = (value & BIT_4) != 0; + + this.noise_outputLeft = (value & BIT_3) != 0; + this.wave_outputLeft = (value & BIT_2) != 0; + this.pulse2_outputLeft = (value & BIT_1) != 0; + this.pulse1_outputLeft = (value & BIT_0) != 0; + break; + } + + if (addr >= 0x90 && addr <= 0x9F) + { + addr -= 0x90; + + if (!this.wave_bank) // Non-selected wave bank will be written to + { + this.wave_waveTable1[(addr * 2) + 0] = (byte)(value >> 4); + this.wave_waveTable1[(addr * 2) + 1] = (byte)(value & 0xF); + } + else + { + this.wave_waveTable0[(addr * 2) + 0] = (byte)(value >> 4); + this.wave_waveTable0[(addr * 2) + 1] = (byte)(value & 0xF); + } + } + else + { + this.soundRegisters[addr - 0x60] = value; + } + } + + if (addr == 0x84) + { + // Control + if (((value >> 7) & 1) != 0) + { + // writeDebug("Enabled sound"); + this.enabled = true; + this.frameSequencerStep = 0; + + // Console.WriteLine("Enabled PSGs!"); + } + else + { + // Console.WriteLine("Disabled PSGs..."); + + // Disable and write zeros on everything upon main disabling + this.noise_enabled = false; + this.wave_enabled = false; + this.pulse2_enabled = false; + this.pulse1_enabled = false; + + this.noise_dacEnabled = false; + this.wave_dacEnabled = false; + this.pulse2_dacEnabled = false; + this.pulse1_dacEnabled = false; + + this.WriteHwio8(0xFF10, 0); + this.WriteHwio8(0xFF11, 0); + this.WriteHwio8(0xFF12, 0); + this.WriteHwio8(0xFF13, 0); + this.WriteHwio8(0xFF14, 0); + + this.WriteHwio8(0xFF16, 0); + this.WriteHwio8(0xFF17, 0); + this.WriteHwio8(0xFF18, 0); + this.WriteHwio8(0xFF19, 0); + + this.WriteHwio8(0xFF1A, 0); + this.WriteHwio8(0xFF1B, 0); + this.WriteHwio8(0xFF1C, 0); + this.WriteHwio8(0xFF1D, 0); + this.WriteHwio8(0xFF1D, 0); + + this.WriteHwio8(0xFF20, 0); + this.WriteHwio8(0xFF21, 0); + this.WriteHwio8(0xFF22, 0); + this.WriteHwio8(0xFF23, 0); + + this.pulse1Pos = 0; + this.pulse2Pos = 0; + this.wavePos = 0; + this.noisePos = 0; + + this.pulse1FreqTimer = 0; + this.pulse2FreqTimer = 0; + this.waveFreqTimer = 0; + this.noiseFreqTimer = 0; + + for (int i = 0xFF10; i <= 0xFF25; i++) + { + this.soundRegisters[i - 0xFF10] = 0; + } + this.enabled = false; + } + } + + this.update(); + } + + public byte ReadHwio8(uint addr) + { + if (addr >= 0x60 && addr <= 0x9F) + { + byte i = this.soundRegisters[addr - 0x60]; + + if (addr >= 0xFF27 && addr <= 0xFF2F) return 0xFF; + + if (addr >= 0x90 && addr <= 0x9F) + { + uint tableAddr = addr - 0x90; + if (!this.wave_bank) // Non-selected wave bank will be written to + { + byte upper = this.wave_waveTable1[(tableAddr * 2) + 0]; + byte lower = this.wave_waveTable1[(tableAddr * 2) + 1]; + return (byte)((upper << 4) | lower); + } + else + { + byte upper = this.wave_waveTable0[(tableAddr * 2) + 0]; + byte lower = this.wave_waveTable0[(tableAddr * 2) + 1]; + return (byte)((upper << 4) | lower); + } + } + + if (addr == 0x84) + { // NR52 + i = 0; + if (this.enabled) i |= (byte)BIT_7; + i |= 0b01110000; + if (this.noise_enabled && this.noise_dacEnabled) i |= (byte)BIT_3; + if (this.wave_enabled && this.wave_dacEnabled) i |= (byte)BIT_2; + if (this.pulse2_enabled && this.pulse2_dacEnabled) i |= (byte)BIT_1; + if (this.pulse1_enabled && this.pulse1_dacEnabled) i |= (byte)BIT_0; + return i; + } + + switch (addr) + { + case 0x60: i |= 0x80; break; // NR10 + case 0x62: i |= 0x3F; break; // NR11 + case 0x63: i |= 0x00; break; // NR12 + case 0x64: i |= 0xFF; break; // NR13 + case 0x65: i |= 0xBF; break; // NR14 + + case 0x66: i |= 0xFF; break; // Unused + case 0x68: i |= 0x3F; break; // NR21 + case 0x69: i |= 0x00; break; // NR22 + case 0x6C: i |= 0xFF; break; // NR23 + case 0x6D: i |= 0xBF; break; // NR24 + + case 0x70: i |= 0x7F; break; // NR30 + case 0x72: i |= 0xFF; break; // NR31 + case 0x73: i |= 0x9F; break; // NR32 + case 0x74: i |= 0xFF; break; // NR33 + case 0x75: i |= 0xBF; break; // NR34 + + case 0x76: i |= 0xFF; break; // Unused + case 0x78: i |= 0xFF; break; // NR41 + case 0x79: i |= 0x00; break; // NR42 + case 0x7C: i |= 0x00; break; // NR43 + case 0x7E: i |= 0xBF; break; // NR44 + + case 0x80: i |= 0x00; break; + case 0x81: i |= 0x00; break; + case 0x82: i |= 0xFF; break; + } + + return i; + } + + // // PCM12 + // if (addr == 0xFF76) + // { + // this.update(); + // return (byte)(this.pulse1Val | (this.pulse2Val << 4)); + // } + // // PCM34 + // if (addr == 0xFF77) + // { + // this.update(); + // return (byte)(this.waveVal | (this.noiseVal << 4)); + // } + + return 0xFF; + } + + void reset() + { + this.enabled = false; + + this.ticksEnvelopePulse1 = 0; + this.ticksEnvelopePulse2 = 0; + this.ticksEnvelopeNoise = 0; + + this.clockPulse1FreqSweep = 0; + this.freqSweepEnabled = false; + + this.frameSequencerStep = 0; + + this.soundRegisters = new byte[64]; + + this.pulse1_enabled = false; + this.pulse1_width = 3; + this.pulse1_dacEnabled = false; + this.pulse1_lengthEnable = false; + this.pulse1_lengthCounter = 0; + this.pulse1_frequencyUpper = 0; + this.pulse1_frequencyLower = 0; + this.pulse1_volume = 0; + this.pulse1_volumeEnvelopeUp = false; + this.pulse1_volumeEnvelopeSweep = 4; + this.pulse1_volumeEnvelopeStart = 0; + this.pulse1_outputLeft = false; + this.pulse1_outputRight = false; + this.pulse1_freqSweepPeriod = 0; + this.pulse1_freqSweepUp = false; + this.pulse1_freqSweepShift = 0; + this.pulse1_updated = true; + this.pulse2_enabled = false; + this.pulse2_width = 3; + this.pulse2_dacEnabled = false; + this.pulse2_lengthEnable = false; + this.pulse2_lengthCounter = 0; + this.pulse2_frequencyUpper = 0; + this.pulse2_frequencyLower = 0; + this.pulse2_volume = 0; + this.pulse2_volumeEnvelopeUp = false; + this.pulse2_volumeEnvelopeSweep = 4; + this.pulse2_volumeEnvelopeStart = 0; + this.pulse2_outputLeft = false; + this.pulse2_outputRight = false; + this.pulse2_updated = true; + this.wave_enabled = false; + this.wave_dacEnabled = false; + this.wave_lengthEnable = true; + this.wave_lengthCounter = 0; + this.wave_frequencyUpper = 0; + this.wave_frequencyLower = 0; + this.wave_volume = 0; + this.wave_oldVolume = 0; + this.wave_waveTable0 = new byte[32]; + this.wave_waveTable1 = new byte[32]; + this.wave_outputLeft = false; + this.wave_outputRight = false; + this.noise_enabled = false; + this.noise_divisorCode = 0; + this.noise_lengthEnable = false; + this.noise_lengthCounter = 0; + this.noise_dacEnabled = false; + this.noise_volume = 0; + this.noise_volumeEnvelopeUp = false; + this.noise_volumeEnvelopeSweep = 4; + this.noise_volumeEnvelopeStart = 0; + this.noise_outputLeft = false; + this.noise_outputRight = false; + this.noise_shiftClockFrequency = 0; + this.noise_counterStep = false; + this.noise_envelopeSweep = 0; + + this.pulse1Val = 0; + this.pulse2Val = 0; + this.waveVal = 0; + this.noiseVal = 0; + + this.pulse1FreqTimer = 0; + this.pulse2FreqTimer = 0; + this.waveFreqTimer = 0; + this.noiseFreqTimer = 0; + + this.updatePulse1Val(); + this.updatePulse2Val(); + this.updateWaveVal(); + this.updateNoiseVal(); + + this.vinLeftEnable = false; + this.vinRightEnable = false; + this.leftMasterVol = 0; + this.rightMasterVol = 0; + this.leftMasterVolMul = 0; + this.rightMasterVolMul = 0; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/GbAudio.cs.meta b/Assets/emulator/GbAudio.cs.meta new file mode 100644 index 0000000..ea2e5cc --- /dev/null +++ b/Assets/emulator/GbAudio.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05f5a54c4470e584daa86a0c04ddd943 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Gba.cs b/Assets/emulator/Gba.cs new file mode 100644 index 0000000..8cfb84c --- /dev/null +++ b/Assets/emulator/Gba.cs @@ -0,0 +1,218 @@ +using System; + +namespace OptimeGBA +{ + + public unsafe sealed class Gba + { + public ProviderGba Provider; + + public Scheduler Scheduler; + + public AudioCallback AudioCallback; + + public MemoryGba Mem; + public Arm7 Cpu; + public GbaAudio GbaAudio; + public Keypad Keypad; + public PpuGba Ppu; + public HwControlGba HwControl; + public DmaGba Dma; + public Timers Timers; + + public Gba(ProviderGba provider) + { + Provider = provider; + + Scheduler = new Scheduler(); + + Mem = new MemoryGba(this, provider); + GbaAudio = new GbaAudio(this, Scheduler); + Ppu = new PpuGba(this, Scheduler); + Keypad = new Keypad(); + Dma = new DmaGba(this); + Timers = new Timers(GbaAudio, HwControl, Scheduler, false, true); + HwControl = new HwControlGba(this); + Cpu = new Arm7(StateChange, Mem, false, false, null); + + Cpu.SetTimingsTable( + Cpu.Timing8And16, + 1, // BIOS + 1, // Unused + 3, // EWRAM + 1, // IWRAM + 1, // I/O Registers + 1, // PPU Palettes + 1, // PPU VRAM + 1, // PPU OAM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak ROM/FlashROM + 5, // Game Pak SRAM/Flash + 5 // Game Pak SRAM/Flash + ); + Cpu.SetTimingsTable( + Cpu.Timing32, + 1, // BIOS + 1, // Unused + 6, // EWRAM + 1, // IWRAM + 1, // I/O Registers + 2, // PPU Palettes + 2, // PPU VRAM + 1, // PPU OAM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak ROM/FlashROM + 8, // Game Pak SRAM/Flash + 8 // Game Pak SRAM/Flash + ); + + Cpu.SetTimingsTable( + Cpu.Timing8And16InstrFetch, + 1, // BIOS + 1, // Unused + 3, // EWRAM + 1, // IWRAM + 1, // I/O Registers + 1, // PPU Palettes + 1, // PPU VRAM + 1, // PPU OAM + // Compensate for no prefetch buffer 5 -> 2 + 2, // Game Pak ROM/FlashROM + 2, // Game Pak ROM/FlashROM + 2, // Game Pak ROM/FlashROM + 2, // Game Pak ROM/FlashROM + 2, // Game Pak ROM/FlashROM + 2, // Game Pak ROM/FlashROM + 5, // Game Pak SRAM/Flash + 5 // Game Pak SRAM/Flash + ); + + Cpu.SetTimingsTable( + Cpu.Timing32InstrFetch, + 1, // BIOS + 1, // Unused + 6, // EWRAM + 1, // IWRAM + 1, // I/O Registers + 2, // PPU Palettes + 2, // PPU VRAM + 1, // PPU OAM + // Compensate for no prefetch buffer 8 -> 4 + 4, // Game Pak ROM/FlashROM + 4, // Game Pak ROM/FlashROM + 4, // Game Pak ROM/FlashROM + 4, // Game Pak ROM/FlashROM + 4, // Game Pak ROM/FlashROM + 4, // Game Pak ROM/FlashROM + 8, // Game Pak SRAM/Flash + 8 // Game Pak SRAM/Flash + ); + + if (!provider.BootBios) + { + Cpu.SetModeReg(13, Arm7Mode.SVC, 0x03007FE0); + Cpu.SetModeReg(13, Arm7Mode.IRQ, 0x03007FA0); + Cpu.SetModeReg(13, Arm7Mode.USR, 0x03007F00); + + // Default Stack Pointer + Cpu.R[13] = Cpu.GetModeReg(13, Arm7Mode.USR); + Cpu.R[15] = 0x08000000; + } + + AudioCallback = provider.AudioCallback; + + Mem.InitPageTables(); + Cpu.InitFlushPipeline(); + +#if UNSAFE + Console.WriteLine("Starting in memory UNSAFE mode"); +#else + Console.WriteLine("Starting in memory SAFE mode"); +#endif + } + + public uint Step() + { + Cpu.CheckInterrupts(); + long beforeTicks = Scheduler.CurrentTicks; + if (!Cpu.ThumbState) + { + Scheduler.CurrentTicks += Cpu.ExecuteArm(); + } + else + { + Scheduler.CurrentTicks += Cpu.ExecuteThumb(); + } + while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks) + { + long current = Scheduler.CurrentTicks; + long next = Scheduler.NextEventTicks; + Scheduler.PopFirstEvent().Callback(current - next); + } + + return (uint)(Scheduler.CurrentTicks - beforeTicks); + } + + public void DoNothing(long cyclesLate) { } + + public void StateChange() + { + Scheduler.AddEventRelative(SchedulerId.None, 0, DoNothing); + } + + public uint StateStep() + { + Cpu.CheckInterrupts(); + + long beforeTicks = Scheduler.CurrentTicks; + if (!Cpu.ThumbState) + { + while (Scheduler.CurrentTicks < Scheduler.NextEventTicks) + { + Scheduler.CurrentTicks += Cpu.ExecuteArm(); + } + } + else + { + while (Scheduler.CurrentTicks < Scheduler.NextEventTicks) + { + Scheduler.CurrentTicks += Cpu.ExecuteThumb(); + } + } + + while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks) + { + long current = Scheduler.CurrentTicks; + long next = Scheduler.NextEventTicks; + Scheduler.PopFirstEvent().Callback(current - next); + } + + // Return cycles executed + return (uint)(Scheduler.CurrentTicks - beforeTicks); + } + + public void Tick(uint cycles) + { + Scheduler.CurrentTicks += cycles; + } + + public void HaltSkip(long cyclesLate) + { + long before = Scheduler.CurrentTicks; + while (!HwControl.Available) + { + long ticksPassed = Scheduler.NextEventTicks - Scheduler.CurrentTicks; + Scheduler.CurrentTicks = Scheduler.NextEventTicks; + Scheduler.PopFirstEvent().Callback(0); + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Gba.cs.meta b/Assets/emulator/Gba.cs.meta new file mode 100644 index 0000000..9031974 --- /dev/null +++ b/Assets/emulator/Gba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c7b25fc293f60540a475ccb2f6357b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/GbaAudio.cs b/Assets/emulator/GbaAudio.cs new file mode 100644 index 0000000..4c6fe21 --- /dev/null +++ b/Assets/emulator/GbaAudio.cs @@ -0,0 +1,483 @@ +using System; +using static OptimeGBA.Bits; +using System.Runtime.CompilerServices; + +namespace OptimeGBA +{ + public enum ResamplingMode + { + None, + Linear, + Sinc, + SincLowPass, + } + + public sealed class CircularBufferOverwriting + { + public uint Size; + public T[] Buffer; + public uint ReadPos = 0; + public uint WritePos = 0; + + public CircularBufferOverwriting(uint size) + { + Size = size; + Buffer = new T[Size]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Insert(T data) + { + Buffer[WritePos++] = data; + + if (WritePos >= Size) + { + WritePos = 0; + } + + return; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Pop() + { + T data = Buffer[ReadPos++]; + + if (ReadPos >= Size) + { + ReadPos = 0; + } + + return data; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Peek(int offset) + { + return Buffer[(ReadPos + offset) % Size]; + } + + public void Reset() + { + ReadPos = 0; + WritePos = 0; + } + } + + public sealed class CircularBuffer + { + public uint Size; + public T[] Buffer; + public T EmptyValue; + public uint ReadPos = 0; + public uint WritePos = 0; + public uint Entries = 0; + public uint TotalPops = 0; + public uint EmptyPops = 0; + public uint FullInserts = 0; + public uint Collisions = 0; + + public CircularBuffer(uint size, T emptyValue) + { + Size = size; + Buffer = new T[Size]; + EmptyValue = emptyValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Insert(T data) + { + if (Entries < Size) + { + if (ReadPos == WritePos) Collisions++; + Entries++; + Buffer[WritePos++] = data; + + if (WritePos >= Size) + { + WritePos = 0; + } + + return true; + } + + FullInserts++; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Pop() + { + T data; + TotalPops++; + if (Entries > 0) + { + Entries--; + data = Buffer[ReadPos++]; + + if (ReadPos >= Size) + { + ReadPos = 0; + } + } + else + { + EmptyPops++; + data = EmptyValue; + } + return data; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Peek(int offset) + { + return Buffer[(ReadPos + offset) % Size]; + } + + public void Reset() + { + Entries = 0; + ReadPos = 0; + WritePos = 0; + } + } + + public sealed class GbaAudio + { + Gba Gba; + Scheduler Scheduler; + public GbaAudio(Gba gba, Scheduler scheduler) + { + Gba = gba; + Scheduler = scheduler; + + Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample, Sample); + } + + public GbAudio GbAudio = new GbAudio(); + + public bool DebugEnableA = true; + public bool DebugEnableB = true; + + public CircularBuffer A = new CircularBuffer(32, 0); + public CircularBuffer B = new CircularBuffer(32, 0); + + public CircularBufferOverwriting VisBufA = new CircularBufferOverwriting(1024); + public CircularBufferOverwriting VisBufB = new CircularBufferOverwriting(1024); + + public short CurrentValueA; + public short CurrentValueB; + + public short PreviousValueA; + public short PreviousValueB; + + public long LastSampleTimeA; + public long LastSampleTimeB; + + public long IntervalA = 1; + public long IntervalB = 1; + + uint BiasLevel = 0x100; + uint AmplitudeRes; + + // SOUNDCNT_H + uint SoundVolume = 0; // 0-1 + byte DmaSoundAVolume = 0; // 2 + byte DmaSoundBVolume = 0; // 3 + + bool DmaSoundAEnableRight = false; // 8 + bool DmaSoundAEnableLeft = false; // 9 + bool DmaSoundATimerSelect = false; // 10 + + bool DmaSoundBEnableRight = false; // 11 + bool DmaSoundBEnableLeft = false; // 12 + bool DmaSoundBTimerSelect = false; // 13 + + bool MasterEnable = false; + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000082: // SOUNDCNT_H B0 + val |= (byte)((SoundVolume >> 0) & 0b11); // 0-1 + val |= (byte)(DmaSoundAVolume << 2); // 2 + val |= (byte)(DmaSoundBVolume << 3); // 3 + break; + case 0x4000083: // SOUNDCNT_H B1 + if (DmaSoundAEnableRight) val = BitSet(val, 8 - 8); // 8 + if (DmaSoundAEnableLeft) val = BitSet(val, 9 - 8); // 9 + if (DmaSoundATimerSelect) val = BitSet(val, 10 - 8); // 10 + if (DmaSoundBEnableRight) val = BitSet(val, 12 - 8); // 12 + if (DmaSoundBEnableLeft) val = BitSet(val, 13 - 8); // 13 + if (DmaSoundBTimerSelect) val = BitSet(val, 14 - 8); // 14 + break; + + // Special case, because SOUNDCNT_X contains both GB Audio status and GBA audio status + case 0x4000084: + // NR52 + byte i = 0; + i |= 0b01110000; + if (GbAudio.noise_enabled && GbAudio.noise_dacEnabled) i |= (byte)BIT_3; + if (GbAudio.wave_enabled && GbAudio.wave_dacEnabled) i |= (byte)BIT_2; + if (GbAudio.pulse2_enabled && GbAudio.pulse2_dacEnabled) i |= (byte)BIT_1; + if (GbAudio.pulse1_enabled && GbAudio.pulse1_dacEnabled) i |= (byte)BIT_0; + + if (MasterEnable) i |= (byte)BIT_7; + return i; + + case 0x4000088: // SOUNDBIAS B0 + val |= (byte)(BiasLevel << 1); + break; + case 0x4000089: // SOUNDBIAS B1 + val |= (byte)(BiasLevel >> 7); + val |= (byte)(AmplitudeRes << 6); + break; + } + + if (addr >= 0x4000060 && addr <= 0x4000084) + { + // GB Registers + val = GbAudio.ReadHwio8(addr & 0xFF); + } + else if (addr >= 0x4000090 && addr <= 0x400009F) + { + // Wave RAM + val = GbAudio.ReadHwio8(addr & 0xFF); + } + + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + if (addr >= 0x4000060 && addr <= 0x400009F) + { + GbAudio.WriteHwio8(addr & 0xFF, val); + } + + switch (addr) + { + case 0x4000082: // SOUNDCNT_H B0 + SoundVolume = (uint)(val & 0b11); // 0-1 + DmaSoundAVolume = (byte)((val >> 2) & 1); // 2 + DmaSoundBVolume = (byte)((val >> 3) & 1); // 3 + break; + case 0x4000083: // SOUNDCNT_H B1 + DmaSoundAEnableRight = BitTest(val, 8 - 8); // 8 + DmaSoundAEnableLeft = BitTest(val, 9 - 8); // 9 + DmaSoundATimerSelect = BitTest(val, 10 - 8); // 10 + if (BitTest(val, 11 - 8)) A.Reset(); + DmaSoundBEnableRight = BitTest(val, 12 - 8); // 12 + DmaSoundBEnableLeft = BitTest(val, 13 - 8); // 13 + DmaSoundBTimerSelect = BitTest(val, 14 - 8); // 14 + if (BitTest(val, 15 - 8)) B.Reset(); + break; + case 0x4000084: // SOUNDCNT_X + MasterEnable = BitTest(val, 7); + break; + case 0x4000088: // SOUNDBIAS B0 + BiasLevel &= 0b110000000; + BiasLevel |= (uint)((val >> 1) & 0b1111111); + break; + case 0x4000089: // SOUNDBIAS B1 + BiasLevel &= 0b001111111; + BiasLevel |= (uint)((val & 0b11) << 7); + + AmplitudeRes &= 0; + AmplitudeRes |= (uint)((val >> 6) & 0b11); + break; + + case 0x40000A0: + case 0x40000A1: + case 0x40000A2: + case 0x40000A3: + // Gba.Arm7.Error("FIFO Insert"); + A.Insert(val); + break; + + case 0x40000A4: + case 0x40000A5: + case 0x40000A6: + case 0x40000A7: + // Gba.Arm7.Error("FIFO Insert"); + B.Insert(val); + break; + } + } + + public bool CollectSamples = true; + + public bool EnablePsg = true; + public bool EnableFifo = true; + + public BlipBuf BlipBuf = new BlipBuf(32, true, 2); + public ResamplingMode ResamplingMode = ResamplingMode.Sinc; + public const int SampleRate = 32768; + const int CyclesPerSample = 16777216 / SampleRate; + // public CircularBuffer SampleBuffer = new CircularBuffer(32768, 0); + public const uint SampleBufferMax = 256; + public short[] SampleBuffer = new short[SampleBufferMax]; + public uint SampleBufferPos = 0; + public bool AudioReady; + + public uint VisSamplingTimer = 0; + + public void Sample(long cyclesLate) + { + GbAudio.Tick(CyclesPerSample / 4); // convert to GB sample rate + + BlipBuf.ReadOutSample(); + + double fifoASinc = BlipBuf.CurrentValL; + double fifoBSinc = BlipBuf.CurrentValR; + + short fifoA = 0; + short fifoB = 0; + short psgA = 0; + short psgB = 0; + + if (MasterEnable) + { + if (EnablePsg) + { + psgA += GbAudio.Out1; + psgB += GbAudio.Out2; + } + if (EnableFifo) + { + switch (ResamplingMode) + { + case ResamplingMode.None: + if (DebugEnableA) + { + if (DmaSoundAEnableLeft) fifoA += CurrentValueA; + if (DmaSoundAEnableRight) fifoB += CurrentValueA; + } + if (DebugEnableB) + { + if (DmaSoundBEnableLeft) fifoA += CurrentValueB; + if (DmaSoundBEnableRight) fifoB += CurrentValueB; + } + break; + case ResamplingMode.Linear: + long current = Scheduler.CurrentTicks - cyclesLate; + + if (DebugEnableA) + { + double ratio = (current - LastSampleTimeA) / (double)IntervalA; + double valDouble = (PreviousValueA + ratio * (double)(CurrentValueA - PreviousValueA)); + short val = (short)valDouble; + + if (DmaSoundAEnableLeft) fifoA += val; + if (DmaSoundAEnableRight) fifoB += val; + } + if (DebugEnableB) + { + double ratio = (current - LastSampleTimeB) / (double)IntervalB; + double valDouble = (PreviousValueB + ratio * (double)(CurrentValueB - PreviousValueB)); + short val = (short)valDouble; + + if (DmaSoundBEnableLeft) fifoA += val; + if (DmaSoundBEnableRight) fifoB += val; + } + break; + case ResamplingMode.Sinc: + case ResamplingMode.SincLowPass: + if (DebugEnableA) + { + if (DmaSoundAEnableLeft) fifoA += (short)(fifoASinc); + if (DmaSoundAEnableRight) fifoB += (short)(fifoASinc); + } + if (DebugEnableB) + { + if (DmaSoundBEnableLeft) fifoA += (short)(fifoBSinc); + if (DmaSoundBEnableRight) fifoB += (short)(fifoBSinc); + } + break; + } + } + } + + if (++VisSamplingTimer >= 4) + { + VisSamplingTimer = 0; + + VisBufA.Insert(CurrentValueA); + VisBufB.Insert(CurrentValueB); + } + + SampleBuffer[SampleBufferPos++] = (short)((fifoA + psgA) * 64); + SampleBuffer[SampleBufferPos++] = (short)((fifoB + psgB) * 64); + + if (SampleBufferPos >= SampleBufferMax) + { + SampleBufferPos = 0; + + Gba.AudioCallback(SampleBuffer); + } + + Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample - cyclesLate, Sample); + } + + public void TimerOverflowFifoA(long cyclesLate, uint timerId) + { + LastSampleTimeA = Scheduler.CurrentTicks - cyclesLate; + PreviousValueA = CurrentValueA; + IntervalA = Gba.Timers.T[timerId].Interval; + + CurrentValueA = (short)((sbyte)A.Pop() << DmaSoundAVolume); + + BlipBuf.SetValue(0, (float)LastSampleTimeA / CyclesPerSample, (float)CurrentValueA, 0); + + if (A.Entries <= 16) + { + Gba.Dma.RepeatFifoA(); + } + } + public void TimerOverflowFifoB(long cyclesLate, uint timerId) + { + LastSampleTimeB = Scheduler.CurrentTicks - cyclesLate; + PreviousValueB = CurrentValueB; + IntervalB = Gba.Timers.T[timerId].Interval; + + CurrentValueB = (short)((sbyte)B.Pop() << DmaSoundBVolume); + + BlipBuf.SetValue(1, (float)LastSampleTimeB / CyclesPerSample, 0, (float)CurrentValueB); + + if (B.Entries <= 16) + { + Gba.Dma.RepeatFifoB(); + } + } + + // Called when Timer 0 or 1 overflows. + public void TimerOverflow(long cyclesLate, uint timerId) + { + if (timerId == 0) + { + if (!DmaSoundATimerSelect) + { + TimerOverflowFifoA(cyclesLate, timerId); + } + if (!DmaSoundBTimerSelect) + { + TimerOverflowFifoB(cyclesLate, timerId); + } + } + else if (timerId == 1) + { + if (DmaSoundATimerSelect) + { + TimerOverflowFifoA(cyclesLate, timerId); + } + if (DmaSoundBTimerSelect) + { + TimerOverflowFifoB(cyclesLate, timerId); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/GbaAudio.cs.meta b/Assets/emulator/GbaAudio.cs.meta new file mode 100644 index 0000000..4787cf1 --- /dev/null +++ b/Assets/emulator/GbaAudio.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eda7cec1bdd2dee48802bc4b1456d549 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/HwControl.cs b/Assets/emulator/HwControl.cs new file mode 100644 index 0000000..eeb623c --- /dev/null +++ b/Assets/emulator/HwControl.cs @@ -0,0 +1,14 @@ +namespace OptimeGBA +{ + public abstract class HwControl + { + public bool IME; + + public uint IE; + public uint IF; + + public bool Available; + + public abstract void FlagInterrupt(uint interruptFlag); + } +} \ No newline at end of file diff --git a/Assets/emulator/HwControl.cs.meta b/Assets/emulator/HwControl.cs.meta new file mode 100644 index 0000000..d36d45d --- /dev/null +++ b/Assets/emulator/HwControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c64a99535514fa649aa2a79581ea1781 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/HwControlGba.cs b/Assets/emulator/HwControlGba.cs new file mode 100644 index 0000000..5ce7175 --- /dev/null +++ b/Assets/emulator/HwControlGba.cs @@ -0,0 +1,109 @@ +using System; +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + public enum InterruptGba + { + VBlank = 0, + HBlank = 1, + VCounterMatch = 2, + Timer0Overflow = 3, + Timer1Overflow = 4, + Timer2Overflow = 5, + Timer3Overflow = 6, + Serial = 7, + Dma0 = 8, + Dma1 = 9, + Dma2 = 10, + Dma3 = 11, + Keypad = 12, + GamePak = 13, + } + + public sealed class HwControlGba : HwControl + { + Gba Gba; + + public HwControlGba(Gba gba) + { + Gba = gba; + } + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000200: // IE B0 + return (byte)(IE >> 0); + case 0x4000201: // IE B1 + return (byte)(IE >> 8); + + case 0x4000202: // IF B0 + return (byte)(IF >> 0); + case 0x4000203: // IF B1 + return (byte)(IF >> 8); + + case 0x4000208: // IME + if (IME) val = BitSet(val, 0); + break; + } + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000200: // IE B0 + IE &= 0x3F00; + IE |= (ushort)((ushort)val << 0); + CheckAndFireInterrupts(); + break; + case 0x4000201: // IE B1 + IE &= 0x00FF; + IE |= (ushort)((val & 0x3F) << 8); + CheckAndFireInterrupts(); + break; + + case 0x4000202: // IF B0 + IF &= (ushort)(~((ushort)val << 0)); + CheckAndFireInterrupts(); + break; + case 0x4000203: // IF B1 + IF &= (ushort)(~((val & 0x3F) << 8)); + CheckAndFireInterrupts(); + break; + + case 0x4000208: // IME + IME = BitTest(val, 0); + + CheckAndFireInterrupts(); + break; + + case 0x4000301: // HALTCNT + if (BitTest(val, 7)) + { + } + else + { + Gba.Scheduler.AddEventRelative(SchedulerId.HaltSkip, 0, Gba.HaltSkip); + } + break; + } + } + + public override void FlagInterrupt(uint i) + { + IF |= (ushort)(1 << (int)i); + CheckAndFireInterrupts(); + } + + public void CheckAndFireInterrupts() + { + Available = (IE & IF & 0x3FFF) != 0; + Gba.Cpu.FlagInterrupt = Available && IME; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/HwControlGba.cs.meta b/Assets/emulator/HwControlGba.cs.meta new file mode 100644 index 0000000..bca7150 --- /dev/null +++ b/Assets/emulator/HwControlGba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 654dd2b57b6345649802e6a16a013537 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/HwControlNds.cs b/Assets/emulator/HwControlNds.cs new file mode 100644 index 0000000..0cb50a5 --- /dev/null +++ b/Assets/emulator/HwControlNds.cs @@ -0,0 +1,150 @@ +using System; +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + public enum InterruptNds + { + VBlank = 0, + HBlank = 1, + VCounterMatch = 2, + Timer0Overflow = 3, + Timer1Overflow = 4, + Timer2Overflow = 5, + Timer3Overflow = 6, + Rtc = 7, + Dma0 = 8, + Dma1 = 9, + Dma2 = 10, + Dma3 = 11, + Keypad = 12, + GamePak = 13, + // 14, 15, unused + IpcSync = 16, + IpcSendFifoEmpty = 17, + IpcRecvFifoPending = 18, + Slot1DataTransferComplete = 19, + Slot1rq = 20, + GeometryFifo = 21, // ARM9 only + ScreenUnfold = 22, // ARM7 only + SpiBus = 23, // ARM7 only + Wifi = 24, // ARM7 only + } + + public sealed class HwControlNds : HwControl + { + Arm7 Cpu; + + public HwControlNds(Arm7 cpu) + { + Cpu = cpu; + } + + public byte Postflg; // POSTFLG + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000208: // IME + if (IME) val = BitSet(val, 0); + break; + + case 0x4000210: // IE B0 + return (byte)(IE >> 0); + case 0x4000211: // IE B1 + return (byte)(IE >> 8); + case 0x4000212: // IE B2 + return (byte)(IE >> 16); + case 0x4000213: // IE B3 + return (byte)(IE >> 24); + + case 0x4000214: // IF B0 + return (byte)(IF >> 0); + case 0x4000215: // IF B1 + return (byte)(IF >> 8); + case 0x4000216: // IF B2 + return (byte)(IF >> 16); + case 0x4000217: // IF B3 + return (byte)(IF >> 24); + } + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000208: // IME + IME = BitTest(val, 0); + CheckAndFireInterrupts(); + break; + + case 0x4000210: // IE B0 + IE &= 0xFFFFFF00; + IE |= (uint)((uint)val << 0); + CheckAndFireInterrupts(); + break; + case 0x4000211: // IE B1 + IE &= 0xFFFF00FF; + IE |= (uint)((uint)val << 8); + CheckAndFireInterrupts(); + break; + case 0x4000212: // IE B2 + IE &= 0xFF00FFFF; + IE |= (uint)((uint)val << 16); + CheckAndFireInterrupts(); + break; + case 0x4000213: // IE B3 + IE &= 0x00FFFFFF; + IE |= (uint)((uint)val << 24); + CheckAndFireInterrupts(); + break; + + case 0x4000214: // IF B0 + IF &= ~(uint)((uint)val << 0); + CheckAndFireInterrupts(); + break; + case 0x4000215: // IF B1 + IF &= ~(uint)((uint)val << 8); + CheckAndFireInterrupts(); + break; + case 0x4000216: // IF B2 + IF &= ~(uint)((uint)val << 16); + CheckAndFireInterrupts(); + break; + case 0x4000217: // IF B3 + IF &= ~(uint)((uint)val << 24); + CheckAndFireInterrupts(); + break; + } + } + + public override void FlagInterrupt(uint i) + { + IF |= (uint)(1 << (int)i); + CheckAndFireInterrupts(); + } + + public void CheckAndFireInterrupts() + { + Available = (IE & IF & 0xFFFFFFFF) != 0; + Cpu.FlagInterrupt = Available && IME; + if (Cpu.Armv5) + { + if (Available && IME) + { + Cpu.Halted = false; + } + } + else + { + if (Available) + { + Cpu.Halted = false; + } + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/HwControlNds.cs.meta b/Assets/emulator/HwControlNds.cs.meta new file mode 100644 index 0000000..0cb0e5f --- /dev/null +++ b/Assets/emulator/HwControlNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 423f62bf81dca7648b5974281bffb372 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Ipc.cs b/Assets/emulator/Ipc.cs new file mode 100644 index 0000000..b882e98 --- /dev/null +++ b/Assets/emulator/Ipc.cs @@ -0,0 +1,207 @@ +using System; +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + public sealed class Ipc + { + Nds Nds; + byte Id; + + public Ipc(Nds nds, byte id) + { + Nds = nds; + Id = id; + } + + public CircularBuffer RecvFifo = new CircularBuffer(16, 0); + public uint LastSendValue; + public uint LastRecvValue; + + public bool SendFifoEmptyIrqLevel; + public bool RecvFifoPendingIrqLevel; + + public byte IpcSyncDataOut; + + // IPCSYNC + public bool EnableRemoteIrq; + + // IPCFIFOCNT + public bool EnableSendFifoEmptyIrq; + public bool EnableRecvFifoPendingIrq; + + public bool FifoError; + public bool EnableFifos; + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000180: // IPCSYNC B0 + val |= GetRemote().IpcSyncDataOut; + break; + case 0x4000181: // IPCSYNC B1 + val |= IpcSyncDataOut; + + if (EnableRemoteIrq) val = BitSet(val, 14 - 8); + break; + + case 0x4000184: // IPCFIFOCNT B0 + if (GetRemote().RecvFifo.Entries == 0) val = BitSet(val, 0); // Send FIFO empty + if (GetRemote().RecvFifo.Entries == 16) val = BitSet(val, 1); // Send FIFO full + if (EnableSendFifoEmptyIrq) val = BitSet(val, 2); + CheckSendFifoEmptyIrq("IPCFIFOCNT bit enable"); + break; + case 0x4000185: // IPCFIFOCNT B1 + if (RecvFifo.Entries == 0) val = BitSet(val, 0); // Receive FIFO empty + if (RecvFifo.Entries == 16) val = BitSet(val, 1); // Receive FIFO full + if (EnableRecvFifoPendingIrq) val = BitSet(val, 2); + CheckRecvFifoPendingIrq("IPCFIFOCNT bit enable"); + + if (FifoError) val = BitSet(val, 6); + if (EnableFifos) val = BitSet(val, 7); + break; + + case 0x4100000: // IPCFIFORECV B0 + if (RecvFifo.Entries > 0) + { + if (EnableFifos) + { + LastRecvValue = RecvFifo.Pop(); + GetRemote().CheckSendFifoEmptyIrq("remote pop"); + } + } + else + { + FifoError = true; + } + val = GetByteIn(LastRecvValue, addr & 3); + break; + case 0x4100001: // IPCFIFORECV B1 + case 0x4100002: // IPCFIFORECV B2 + case 0x4100003: // IPCFIFORECV B3 + val = GetByteIn(LastRecvValue, addr & 3); + break; + } + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000180: // IPCSYNC B0 + break; + case 0x4000181: // IPCSYNC B1 + IpcSyncDataOut = (byte)(val & 0xF); + + // send IRQ to remote + if (BitTest(val, 13 - 8) && GetRemote().EnableRemoteIrq) + { + // Console.WriteLine($"[{Id}] Sending IRQ"); + switch (Id) + { + case 0: + Nds.HwControl7.FlagInterrupt((uint)InterruptNds.IpcSync); + break; + case 1: + Nds.HwControl9.FlagInterrupt((uint)InterruptNds.IpcSync); + break; + } + } + EnableRemoteIrq = BitTest(val, 14 - 8); + break; + + case 0x4000184: // IPCFIFOCNT B0 + EnableSendFifoEmptyIrq = BitTest(val, 2); + if (BitTest(val, 3)) + { + GetRemote().RecvFifo.Reset(); + } + break; + case 0x4000185: // IPCFIFOCNT B1 + EnableRecvFifoPendingIrq = BitTest(val, 2); + + if (BitTest(val, 6)) + { + FifoError = false; + } + EnableFifos = BitTest(val, 7); + break; + + case 0x4000188: // IPCFIFOSEND B0 + case 0x4000189: // IPCFIFOSEND B1 + case 0x400018A: // IPCFIFOSEND B2 + LastSendValue = SetByteIn(LastSendValue, val, addr & 3); + break; + case 0x400018B: // IPCFIFOSEND B3 + LastSendValue = SetByteIn(LastSendValue, val, addr & 3); + if (EnableFifos) + { + GetRemote().RecvFifo.Insert(LastSendValue); + + bool eligible = true; + + // if ((LastSendValue >> 28) == 0x8) eligible = false; + // if ((LastSendValue >> 28) == 0x4) eligible = false; + // if ((LastSendValue >> 28) == 0xC) eligible = false; + // // if ((LastSendValue >> 28) == 0x0) eligible = false; + + // if (eligible) + // { + // if (Id == 0) Console.WriteLine("ARM9 to ARM7 " + Util.Hex(LastSendValue, 8)); + // // else Console.WriteLine("ARM7 to ARM9 " + Util.Hex(LastSendValue, 8)); + // } + + unsafe + { + GetRemote().CheckRecvFifoPendingIrq("remote insert R15: " + Util.Hex(Nds.Cpu7.R[15], 8)); + } + } + break; + } + } + + public Ipc GetRemote() + { + return Nds.Ipcs[Id ^ 1]; + } + + public void CheckSendFifoEmptyIrq(string from) + { + var prev = SendFifoEmptyIrqLevel; + SendFifoEmptyIrqLevel = GetRemote().RecvFifo.Entries == 0 && EnableSendFifoEmptyIrq; + if (!prev && SendFifoEmptyIrqLevel) + { + // Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Send FIFO Empty IRQ from " + from); + FlagSourceInterrupt(InterruptNds.IpcSendFifoEmpty); + } + } + + public void CheckRecvFifoPendingIrq(string from) + { + var prev = RecvFifoPendingIrqLevel; + RecvFifoPendingIrqLevel = RecvFifo.Entries > 0 && EnableRecvFifoPendingIrq; + if (!prev && RecvFifoPendingIrqLevel) + { + // Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Recv FIFO Pending Irq from " + from); + FlagSourceInterrupt(InterruptNds.IpcRecvFifoPending); + } + } + + public void FlagSourceInterrupt(InterruptNds interrupt) + { + switch (Id) + { + case 0: + Nds.HwControl9.FlagInterrupt((uint)interrupt); + break; + case 1: + Nds.HwControl7.FlagInterrupt((uint)interrupt); + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Ipc.cs.meta b/Assets/emulator/Ipc.cs.meta new file mode 100644 index 0000000..2b9ce0d --- /dev/null +++ b/Assets/emulator/Ipc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 84b3353082f9bee4f829c5b77d38c1fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Keypad.cs b/Assets/emulator/Keypad.cs new file mode 100644 index 0000000..7d6c086 --- /dev/null +++ b/Assets/emulator/Keypad.cs @@ -0,0 +1,61 @@ +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + public sealed class Keypad + { + public bool A; + public bool B; + public bool Start; + public bool Select; + public bool Right; + public bool Left; + public bool Up; + public bool Down; + public bool R; + public bool L; + + // DS Exclusive + public bool X; + public bool Y; + public bool DebugButton; + public bool Touch; + public bool ScreensOpen = true; // DS folded + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000130: // KEYINPUT B0 + if (!A) val = BitSet(val, 0); + if (!B) val = BitSet(val, 1); + if (!Select) val = BitSet(val, 2); + if (!Start) val = BitSet(val, 3); + if (!Right) val = BitSet(val, 4); + if (!Left) val = BitSet(val, 5); + if (!Up) val = BitSet(val, 6); + if (!Down) val = BitSet(val, 7); + break; + case 0x4000131: // KEYINPUT B1 + if (!R) val = BitSet(val, 8 - 8); + if (!L) val = BitSet(val, 9 - 8); + break; + + case 0x4000136: // EXTKEYIN - ARM7 only + if (!X) val = BitSet(val, 0); + if (!Y) val = BitSet(val, 1); + if (!DebugButton) val = BitSet(val, 3); + if (!Touch) val = BitSet(val, 6); + if (!ScreensOpen) val = BitSet(val, 7); + // System.Console.WriteLine(Util.Hex(val, 2)); + break; + case 0x4000137: // EXTKEYIN B1 + val = 0; + break; + } + + return val; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Keypad.cs.meta b/Assets/emulator/Keypad.cs.meta new file mode 100644 index 0000000..35668e0 --- /dev/null +++ b/Assets/emulator/Keypad.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 46a2eabfa7974ee478fa25cfdc9fa1f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Memory.cs b/Assets/emulator/Memory.cs new file mode 100644 index 0000000..b3b4347 --- /dev/null +++ b/Assets/emulator/Memory.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; +using static OptimeGBA.MemoryUtil; + +namespace OptimeGBA +{ + public unsafe abstract class Memory + { + public SaveProvider SaveProvider; + public SortedDictionary HwioWriteLog = new SortedDictionary(); + public SortedDictionary HwioReadLog = new SortedDictionary(); + public bool LogHwioAccesses = false; + + public abstract void InitPageTable(byte*[] pageTable, uint[] maskTable, bool write); + public const int PageSize = 1024; + + public uint[] MemoryRegionMasks = new uint[1048576]; + + public byte[] EmptyPage = new byte[PageSize]; + public byte*[] PageTableRead = new byte*[1048576]; + public byte*[] PageTableWrite = new byte*[1048576]; + + public abstract byte Read8Unregistered(bool debug, uint addr); + public abstract void Write8Unregistered(bool debug, uint addr, byte val); + public abstract ushort Read16Unregistered(bool debug, uint addr); + public abstract void Write16Unregistered(bool debug, uint addr, ushort val); + public abstract uint Read32Unregistered(bool debug, uint addr); + public abstract void Write32Unregistered(bool debug, uint addr, uint val); + + public Dictionary Handles = new Dictionary(); + + public byte* TryPinByteArray(byte[] arr) + { + GCHandle handle; + if (!Handles.TryGetValue(arr, out handle)) + { + handle = GCHandle.Alloc(arr, GCHandleType.Pinned); + Handles[arr] = handle; + } + + return (byte*)handle.AddrOfPinnedObject(); + } + + public void UnpinByteArray(byte[] arr) + { + Handles[arr].Free(); + if (!Handles.Remove(arr)) + { + throw new ArgumentException("Tried to unpin already unpinned array."); + } + } + + public void InitPageTables() + { + InitPageTable(PageTableRead, MemoryRegionMasks, false); + InitPageTable(PageTableWrite, MemoryRegionMasks, true); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint MaskAddress(uint addr) + { + return addr & MemoryRegionMasks[addr >> 12]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte* ResolvePageRead(uint addr) + { + return PageTableRead[addr >> 12]; + } + + public byte* ResolvePageWrite(uint addr) + { + return PageTableWrite[addr >> 12]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte Read8(uint addr) + { + var page = ResolvePageRead(addr); + if (page != null) + { + return GetByte(page, MaskAddress(addr)); + } + + return Read8Unregistered(false, addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Read16(uint addr) + { +#if DEBUG + if ((addr & 1) != 0) + { + Console.Error.WriteLine("Misaligned Read16! " + Util.HexN(addr, 8)); + } +#endif + + var page = ResolvePageRead(addr); + if (page != null) + { + return GetUshort(page, MaskAddress(addr)); + } + + return Read16Unregistered(false, addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadDebug16(uint addr) + { + var page = ResolvePageRead(addr); + if (page != null) + { + return GetUshort(page, MaskAddress(addr)); + } + + return Read16Unregistered(true, addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Read32(uint addr) + { +#if DEBUG + if ((addr & 3) != 0) + { + Console.Error.WriteLine("Misaligned Read32! " + Util.HexN(addr, 8)); + } +#endif + + var page = ResolvePageRead(addr); + if (page != null) + { + return GetUint(page, MaskAddress(addr)); + } + + return Read32Unregistered(false, addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadDebug32(uint addr) + { + var page = ResolvePageRead(addr); + if (page != null) + { + return GetUint(page, MaskAddress(addr)); + } + + return Read32Unregistered(true, addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write8(uint addr, byte val) + { + var page = ResolvePageWrite(addr); + if (page != null) + { + SetByte(page, MaskAddress(addr), val); + return; + } + + Write8Unregistered(false, addr, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write16(uint addr, ushort val) + { +#if DEBUG + if ((addr & 1) != 0) + { + Console.Error.WriteLine("Misaligned Write16! " + Util.HexN(addr, 8)); + } +#endif + + var page = ResolvePageWrite(addr); + if (page != null) + { + SetUshort(page, MaskAddress(addr), val); + return; + } + + Write16Unregistered(false, addr, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write32(uint addr, uint val) + { +#if DEBUG + if ((addr & 3) != 0) + { + Console.Error.WriteLine("Misaligned Write32! " + Util.HexN(addr, 8)); + } +#endif + + var page = ResolvePageWrite(addr); + if (page != null) + { + SetUint(page, MaskAddress(addr), val); + return; + } + + Write32Unregistered(false, addr, val); + } + } +} diff --git a/Assets/Iris/Iris.GBA/Memory.cs.meta b/Assets/emulator/Memory.cs.meta similarity index 83% rename from Assets/Iris/Iris.GBA/Memory.cs.meta rename to Assets/emulator/Memory.cs.meta index 640d07a..9361909 100644 --- a/Assets/Iris/Iris.GBA/Memory.cs.meta +++ b/Assets/emulator/Memory.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c63b68705a3d713459aaacd1a2506ed8 +guid: d3f8f528aeb42c246bc8da7f5e034c36 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/emulator/MemoryControlNds.cs b/Assets/emulator/MemoryControlNds.cs new file mode 100644 index 0000000..d5586ae --- /dev/null +++ b/Assets/emulator/MemoryControlNds.cs @@ -0,0 +1,157 @@ +using System; +using static OptimeGBA.Bits; +using System.Runtime.CompilerServices; + +namespace OptimeGBA +{ + public class MemoryControlNds + { + public byte SharedRamControl; + + public byte[] VRAMCNT = new byte[9]; + public bool VramConfigDirty; + + // EXMEMCNT + public byte Slot2SramWaitArm9; + public byte Slot2Rom0WaitArm9; + public byte Slot2Rom1WaitArm9; + public byte Slot2RomPhiPinOutArm9; + public byte Slot2SramWaitArm7; + public byte Slot2Rom0WaitArm7; + public byte Slot2Rom1WaitArm7; + public byte Slot2RomPhiPinOutArm7; + + // Shared between 7/9 EXMEMCNT/EXMEMSTAT + // true = ARM7 + public bool Nds7Slot2AccessRights; + public bool Nds7Slot1AccessRights; + public bool MainMemoryAccessPriority; + + public byte ReadHwio8Nds9(uint addr) + { + byte val = 0; + + switch (addr) + { + case 0x4000204: + // Console.WriteLine("read from exmemcnt b0"); + val |= (byte)((Slot2SramWaitArm9 & 0b11) << 0); + val |= (byte)((Slot2Rom0WaitArm9 & 0b11) << 2); + val |= (byte)((Slot2Rom1WaitArm9 & 0b1) << 4); + val |= (byte)((Slot2RomPhiPinOutArm9 & 0b11) << 5); + if (Nds7Slot2AccessRights) val = BitSet(val, 7); + break; + case 0x4000205: + // Console.WriteLine("read from exmemcnt b1"); + if (Nds7Slot1AccessRights) val = BitSet(val, 3); + if (MainMemoryAccessPriority) val = BitSet(val, 7); + val = BitSet(val, 6); + break; + } + + return val; + } + + public void WriteHwio8Nds9(uint addr, byte val) + { + switch (addr) + { + case 0x4000204: + // Console.WriteLine("write to exmemcnt b0"); + Slot2SramWaitArm9 = (byte)BitRange(val, 0, 1); + Slot2Rom0WaitArm9 = (byte)BitRange(val, 2, 3); + Slot2Rom1WaitArm9 = (byte)BitRange(val, 4, 4); + Slot2RomPhiPinOutArm9 = (byte)BitRange(val, 5, 6); + Nds7Slot2AccessRights = BitTest(val, 7); + break; + case 0x4000205: + // Console.WriteLine("write to exmemcnt b1"); + Nds7Slot1AccessRights = BitTest(val, 3); + MainMemoryAccessPriority = BitTest(val, 7); + break; + + case 0x4000240: if (VRAMCNT[0] != val) VramConfigDirty = true; VRAMCNT[0] = val; break; + case 0x4000241: if (VRAMCNT[1] != val) VramConfigDirty = true; VRAMCNT[1] = val; break; + case 0x4000242: if (VRAMCNT[2] != val) VramConfigDirty = true; VRAMCNT[2] = val; break; + case 0x4000243: if (VRAMCNT[3] != val) VramConfigDirty = true; VRAMCNT[3] = val; break; + case 0x4000244: if (VRAMCNT[4] != val) VramConfigDirty = true; VRAMCNT[4] = val; break; + case 0x4000245: if (VRAMCNT[5] != val) VramConfigDirty = true; VRAMCNT[5] = val; break; + case 0x4000246: if (VRAMCNT[6] != val) VramConfigDirty = true; VRAMCNT[6] = val; break; + case 0x4000248: if (VRAMCNT[7] != val) VramConfigDirty = true; VRAMCNT[7] = val; break; + case 0x4000249: if (VRAMCNT[8] != val) VramConfigDirty = true; VRAMCNT[8] = val; break; + + case 0x4000247: + SharedRamControl = (byte)(val & 0b11); + break; + } + + // if (VramEnabledAndSet(2, 2) || VramEnabledAndSet(3, 2)) + // { + // throw new NotImplementedException("Implement mapping VRAM banks C and D to ARM7"); + // } + } + + public byte ReadHwio8Nds7(uint addr) + { + byte val = 0; + + switch (addr) + { + case 0x4000204: + // Console.WriteLine("read from exmemstat b0"); + val |= (byte)((Slot2SramWaitArm7 & 0b11) << 0); + val |= (byte)((Slot2Rom0WaitArm7 & 0b11) << 2); + val |= (byte)((Slot2Rom1WaitArm7 & 0b1) << 4); + val |= (byte)((Slot2RomPhiPinOutArm7 & 0b11) << 5); + if (Nds7Slot2AccessRights) val = BitSet(val, 7); + break; + case 0x4000205: + // Console.WriteLine("read from exmemstat b1"); + if (Nds7Slot1AccessRights) val = BitSet(val, 3); + if (MainMemoryAccessPriority) val = BitSet(val, 7); + val = BitSet(val, 6); + break; + + case 0x4000240: + if (VramEnabledAndSet(2, 2)) val = BitSet(val, 0); + if (VramEnabledAndSet(3, 2)) val = BitSet(val, 1); + break; + case 0x4000241: + return SharedRamControl; + } + + return val; + } + + public void WriteHwio8Nds7(uint addr, byte val) + { + switch (addr) + { + case 0x4000204: + // Console.WriteLine("write to exmemstat b0"); + Slot2SramWaitArm7 = (byte)BitRange(val, 0, 1); + Slot2Rom0WaitArm7 = (byte)BitRange(val, 2, 3); + Slot2Rom1WaitArm7 = (byte)BitRange(val, 4, 4); + Slot2RomPhiPinOutArm7 = (byte)BitRange(val, 5, 6); + break; + } + + return; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool VramEnabledAndSet(uint bank, uint mst) + { + uint vramcntMst = VRAMCNT[bank] & 0b111U; + bool vramcntEnable = BitTest(VRAMCNT[bank], 7); + + return vramcntEnable && vramcntMst == mst; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetOffset(uint bank) + { + return (uint)(VRAMCNT[bank] >> 3) & 0b11U; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/MemoryControlNds.cs.meta b/Assets/emulator/MemoryControlNds.cs.meta new file mode 100644 index 0000000..1810999 --- /dev/null +++ b/Assets/emulator/MemoryControlNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cce821ff321fe34ea2e0c5cbabdd74e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/MemoryGba.cs b/Assets/emulator/MemoryGba.cs new file mode 100644 index 0000000..9368bc6 --- /dev/null +++ b/Assets/emulator/MemoryGba.cs @@ -0,0 +1,438 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; +using static OptimeGBA.MemoryUtil; + +namespace OptimeGBA +{ + public sealed unsafe class MemoryGba : Memory + { + Gba Gba; + + public MemoryGba(Gba gba, ProviderGba provider) + { + Gba = gba; + + for (uint i = 0; i < MaxRomSize && i < provider.Rom.Length; i++) + { + Rom[i] = provider.Rom[i]; + } + + for (uint i = 0; i < BiosSize && i < provider.Bios.Length; i++) + { + Bios[i] = provider.Bios[i]; + } + + RomSize = (uint)provider.Rom.Length; + + // Detect save type + + string[] strings = { + "NONE_LOLOLLEXTRATONOTMATCHRANDOMSTRINGS", + "EEPROM_", + "SRAM_", + "FLASH_", + "FLASH512_", + "FLASH1M_", + }; + uint matchedIndex = 0; + + for (uint i = 0; i < strings.Length; i++) + { + char[] chars = strings[i].ToCharArray(); + + int stringLength = chars.Length; + int matchLength = 0; + for (uint j = 0; j < provider.Rom.Length; j++) + { + if (provider.Rom[j] == chars[matchLength]) + { + matchLength++; + if (matchLength >= chars.Length) + { + matchedIndex = i; + goto breakOuterLoop; + } + } + else + { + matchLength = 0; + } + } + } + breakOuterLoop: + + Console.WriteLine($"Save Type: {strings[matchedIndex]}"); + + switch (matchedIndex) + { + case 0: SaveProvider = new NullSaveProvider(); break; + case 1: + SaveProvider = new Eeprom(Gba, EepromSize.Eeprom64k); + if (RomSize < 16777216) + { + EepromThreshold = 0x1000000; + } + else + { + EepromThreshold = 0x1FFFF00; + } + Console.WriteLine("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8)); + break; + case 2: SaveProvider = new Sram(); break; + case 3: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break; + case 4: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break; + case 5: SaveProvider = new Flash(Gba, FlashSize.Flash1m); break; + } + } + + public uint EepromThreshold = 0x2000000; + + public const int BiosSize = 16384; + public const int MaxRomSize = 67108864; + public const int EwramSize = 262144; + public const int IwramSize = 32768; + public uint RomSize; + + public byte[] Bios = new byte[BiosSize]; + public byte[] Rom = new byte[MaxRomSize]; + public byte[] Ewram = new byte[EwramSize]; + public byte[] Iwram = new byte[IwramSize]; + + public override void InitPageTable(byte*[] table, uint[] maskTable, bool write) + { + byte* bios = TryPinByteArray(Bios); + byte* ewram = TryPinByteArray(Ewram); + byte* iwram = TryPinByteArray(Iwram); + byte* palettes = TryPinByteArray(Gba.Ppu.Renderer.Palettes); + byte* vram = TryPinByteArray(Gba.Ppu.Vram); + byte* emptyPage = TryPinByteArray(EmptyPage); + byte* oam = TryPinByteArray(Gba.Ppu.Renderer.Oam); + byte* rom = TryPinByteArray(Rom); + + // 12 bits shaved off already, shave off another 12 to get 24 + for (uint i = 0; i < 1048576; i++) + { + uint addr = (uint)(i << 12); + switch (i >> 12) + { + case 0x0: // BIOS + if (!write) + { + table[i] = bios; + } + maskTable[i] = 0x00003FFF; + break; + case 0x2: // EWRAM + table[i] = ewram; + maskTable[i] = 0x0003FFFF; + break; + case 0x3: // IWRAM + table[i] = iwram; + maskTable[i] = 0x00007FFF; + break; + case 0x5: // Palettes + if (!write) + { + table[i] = palettes; + } + maskTable[i] = 0x3FF; + break; + case 0x6: // PPU VRAM + addr &= 0x1FFFF; + if (addr < 0x18000) + { + table[i] = vram; + } + else + { + table[i] = emptyPage; + } + maskTable[i] = 0x0001FFFF; // VRAM + break; + case 0x7: // PPU OAM + table[i] = oam; + maskTable[i] = 0x000003FF; + break; + case 0x8: // Game Pak ROM/FlashROM + case 0x9: // Game Pak ROM/FlashROM + case 0xA: // Game Pak ROM/FlashROM + case 0xB: // Game Pak ROM/FlashROM + case 0xC: // Game Pak ROM/FlashROM + if (!write) + { + table[i] = rom; + } + maskTable[i] = 0x01FFFFFF; + break; + case 0xD: // Game Pak ROM/FlashROM/EEPROM + maskTable[i] = 0x01FFFFFF; + break; + } + } + } + + ~MemoryGba() + { + Console.WriteLine("Cleaning up GBA memory..."); + UnpinByteArray(Bios); + UnpinByteArray(Ewram); + UnpinByteArray(Iwram); + UnpinByteArray(Gba.Ppu.Renderer.Palettes); + UnpinByteArray(Gba.Ppu.Vram); + UnpinByteArray(EmptyPage); + UnpinByteArray(Gba.Ppu.Renderer.Oam); + UnpinByteArray(Rom); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override byte Read8Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + // addr &= 0x400FFFF; + return ReadHwio8(debug, addr); + case 0xA: // ROM / EEPROM + case 0xB: // ROM / EEPROM + case 0xC: // ROM / EEPROM + case 0xD: // ROM / EEPROM + uint adjAddr = addr & 0x1FFFFFF; + if (adjAddr >= EepromThreshold) + { + return SaveProvider.Read8(adjAddr); + } + + return GetByte(Rom, adjAddr); + case 0xE: // Game Pak SRAM/Flash + case 0xF: // Game Pak SRAM/Flash + return SaveProvider.Read8(addr); + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override ushort Read16Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + byte f0 = Read8Unregistered(debug, addr++); + byte f1 = Read8Unregistered(debug, addr++); + + ushort u16 = (ushort)((f1 << 8) | (f0 << 0)); + + return u16; + case 0xA: // ROM / EEPROM + case 0xB: // ROM / EEPROM + case 0xC: // ROM / EEPROM + case 0xD: // ROM / EEPROM + uint adjAddr = addr & 0x1FFFFFF; + if (adjAddr >= EepromThreshold) + { + return SaveProvider.Read8(adjAddr); + } + + return GetUshort(Rom, adjAddr); + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override uint Read32Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + byte f0 = ReadHwio8(debug, addr++); + byte f1 = ReadHwio8(debug, addr++); + byte f2 = ReadHwio8(debug, addr++); + byte f3 = ReadHwio8(debug, addr++); + + uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0)); + + return u32; + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Write8Unregistered(bool debug, uint addr, byte val) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + // addr &= 0x400FFFF; + WriteHwio8(debug, addr, val); + break; + case 0xA: // ROM / EEPROM + case 0xB: // ROM / EEPROM + case 0xC: // ROM / EEPROM + case 0xD: // ROM / EEPROM + uint adjAddr = addr & 0x1FFFFFF; + if (adjAddr >= EepromThreshold) + { + SaveProvider.Write8(adjAddr, val); + } + break; + case 0xE: // Game Pak SRAM/Flash + case 0xF: // Game Pak SRAM/Flash + SaveProvider.Write8(addr, val); + return; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Write16Unregistered(bool debug, uint addr, ushort val) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + break; + case 0x5: // PPU Palettes + addr &= 0x3FF; + if (GetUshort(Gba.Ppu.Renderer.Palettes, addr) != val) + { + SetUshort(Gba.Ppu.Renderer.Palettes, addr, val); + } + break; + case 0xA: // ROM / EEPROM + case 0xB: // ROM / EEPROM + case 0xC: // ROM / EEPROM + case 0xD: // ROM / EEPROM + uint adjAddr = addr & 0x1FFFFFF; + if (adjAddr >= EepromThreshold) + { + SaveProvider.Write8(adjAddr, (byte)val); + } + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Write32Unregistered(bool debug, uint addr, uint val) + { + switch (addr >> 24) + { + case 0x4: // I/O Registers + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + WriteHwio8(debug, addr++, (byte)(val >> 16)); + WriteHwio8(debug, addr++, (byte)(val >> 24)); + break; + case 0x5: // PPU Palettes + addr &= 0x3FF; + if (GetUint(Gba.Ppu.Renderer.Palettes, addr) != val) + { + SetUint(Gba.Ppu.Renderer.Palettes, addr, val); + } + return; + case 0x6: // PPU VRAM + addr &= 0x1FFFF; + if (addr < 0x18000) + { + SetUint(Gba.Ppu.Vram, addr, val); + } + return; + } + + } + + public byte ReadHwio8(bool debug, uint addr) + { + if (LogHwioAccesses && (addr & ~1) != 0 && !debug) + { + uint count; + HwioWriteLog.TryGetValue(addr, out count); + HwioWriteLog[addr] = count + 1; + } + + if (addr >= 0x4000000 && addr <= 0x4000056) // PPU + { + return Gba.Ppu.ReadHwio8(addr); + } + else if (addr >= 0x4000060 && addr <= 0x40000A8) // Sound + { + return Gba.GbaAudio.ReadHwio8(addr); + } + else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA + { + return Gba.Dma.ReadHwio8(addr); + } + else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer + { + return Gba.Timers.ReadHwio8(addr); + } + else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial + { + + } + else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad + { + return Gba.Keypad.ReadHwio8(addr); + } + else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications + { + switch (addr) { + case 0x4000135: return 0x80; + } + } + else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control + { + return Gba.HwControl.ReadHwio8(addr); + } + return 0; + } + + public void WriteHwio8(bool debug, uint addr, byte val) + { + if (LogHwioAccesses && (addr & ~1) != 0 && !debug) + { + uint count; + HwioReadLog.TryGetValue(addr, out count); + HwioReadLog[addr] = count + 1; + } + + if (addr >= 0x4000000 && addr <= 0x4000056) // PPU + { + Gba.Ppu.WriteHwio8(addr, val); + } + else if (addr >= 0x4000060 && addr <= 0x40000A7) // Sound + { + Gba.GbaAudio.WriteHwio8(addr, val); + } + else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA + { + Gba.Dma.WriteHwio8(addr, val); + } + else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer + { + Gba.Timers.WriteHwio8(addr, val); + } + else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial + { + + } + else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad + { + + } + else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications + { + + } + else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control + { + Gba.HwControl.WriteHwio8(addr, val); + } + } + } +} diff --git a/Assets/emulator/MemoryGba.cs.meta b/Assets/emulator/MemoryGba.cs.meta new file mode 100644 index 0000000..0a1f5b5 --- /dev/null +++ b/Assets/emulator/MemoryGba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3ab94683bca44e4db631e74b158b993 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/MemoryNds7.cs b/Assets/emulator/MemoryNds7.cs new file mode 100644 index 0000000..7c267ff --- /dev/null +++ b/Assets/emulator/MemoryNds7.cs @@ -0,0 +1,449 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; +using static OptimeGBA.MemoryUtil; +using static Util; + +namespace OptimeGBA +{ + public sealed unsafe class MemoryNds7 : Memory + { + Nds Nds; + + public MemoryNds7(Nds nds, ProviderNds provider) + { + Nds = nds; + + SaveProvider = new NullSaveProvider(); + + for (uint i = 0; i < Arm7BiosSize && i < provider.Bios7.Length; i++) + { + Arm7Bios[i] = provider.Bios7[i]; + } + } + + public const int Arm7BiosSize = 16384; + public const int Arm7WramSize = 65536; + + public byte[] Arm7Bios = new byte[Arm7BiosSize]; + public byte[] Arm7Wram = new byte[Arm7WramSize]; + + public byte RCNT; + + public override void InitPageTable(byte*[] table, uint[] maskTable, bool write) + { + byte* arm7Bios = TryPinByteArray(Arm7Bios); + byte* mainRam = TryPinByteArray(Nds.MainRam); + byte* arm7Wram = TryPinByteArray(Arm7Wram); + + // 12 bits shaved off already, shave off another 12 to get 24 + for (uint i = 0; i < 1048576; i++) + { + uint addr = (uint)(i << 12); + switch (i >> 12) + { + case 0x0: // BIOS + if (!write) + { + table[i] = arm7Bios; + } + maskTable[i] = 0x00003FFF; + break; + case 0x2: // Main Memory + table[i] = mainRam; + maskTable[i] = 0x003FFFFF; + break; + case 0x3: // Shared RAM / ARM7 WRAM + if (addr >= 0x03800000) + { + table[i] = arm7Wram; + maskTable[i] = 0x0000FFFF; + } + break; + } + } + } + + ~MemoryNds7() + { + Console.WriteLine("Cleaning up NDS7 memory..."); + UnpinByteArray(Arm7Bios); + UnpinByteArray(Nds.MainRam); + UnpinByteArray(Arm7Wram); + } + + public (byte[] array, uint offset) GetSharedRamParams(uint addr) + { + switch (Nds.MemoryControl.SharedRamControl) + { + case 0: + default: + addr &= 0xFFFF; // ARM7 WRAM + return (Arm7Wram, addr); + case 1: + addr &= 0x3FFF; // 1st half of Shared RAM + return (Nds.SharedRam, addr); + case 2: + addr &= 0x3FFF; // 2st half of Shared RAM + addr += 0x4000; + return (Nds.SharedRam, addr); + case 3: + addr &= 0x7FFF; // All 32k of Shared RAM + return (Nds.SharedRam, addr); + } + } + + public override byte Read8Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetByte(array, offset); + case 0x4: // I/O Registers + return ReadHwio8(debug, addr); + case 0x6: // ARM7 VRAM + return Nds.Ppu.ReadVram8Arm7(addr); + } + + return 0; + } + + public override ushort Read16Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetUshort(array, offset); + case 0x4: // I/O Registers + byte f0 = ReadHwio8(debug, addr++); + byte f1 = ReadHwio8(debug, addr++); + + ushort u16 = (ushort)((f1 << 8) | (f0 << 0)); + + return u16; + case 0x6: // VRAM + return (ushort)( + (Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) | + (Nds.Ppu.ReadVram8Arm7(addr + 1) << 8) + ); + } + + return 0; + } + + public override uint Read32Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetUint(array, offset); + case 0x4: // I/O Registers + byte f0 = ReadHwio8(debug, addr++); + byte f1 = ReadHwio8(debug, addr++); + byte f2 = ReadHwio8(debug, addr++); + byte f3 = ReadHwio8(debug, addr++); + + uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0)); + + return u32; + case 0x6: // VRAM + return (uint)( + (Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) | + (Nds.Ppu.ReadVram8Arm7(addr + 1) << 8) | + (Nds.Ppu.ReadVram8Arm7(addr + 2) << 16) | + (Nds.Ppu.ReadVram8Arm7(addr + 3) << 24) + ); + } + + return 0; + } + + public override void Write8Unregistered(bool debug, uint addr, byte val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetByte(array, offset, val); + break; + case 0x4: // I/O Registers + WriteHwio8(debug, addr, val); + break; + case 0x6: // ARM7 VRAM + Nds.Ppu.WriteVram8Arm7(addr, val); + break; + } + } + + public override void Write16Unregistered(bool debug, uint addr, ushort val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetUshort(array, offset, val); + break; + case 0x4: // I/O Registers + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + break; + case 0x6: // ARM7 VRAM + Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0)); + Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8)); + break; + } + } + + public override void Write32Unregistered(bool debug, uint addr, uint val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetUint(array, offset, val); + break; + case 0x4: // I/O Registers + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + WriteHwio8(debug, addr++, (byte)(val >> 16)); + WriteHwio8(debug, addr++, (byte)(val >> 24)); + break; + case 0x6: // ARM7 VRAM + Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0)); + Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8)); + Nds.Ppu.WriteVram8Arm7(addr + 2, (byte)(val >> 16)); + Nds.Ppu.WriteVram8Arm7(addr + 3, (byte)(val >> 24)); + break; + } + } + + + public byte ReadHwio8(bool debug, uint addr) + { + if (LogHwioAccesses) + { + lock (HwioReadLog) + { + if ((addr & ~1) != 0 && !debug) + { + uint count; + HwioReadLog.TryGetValue(addr, out count); + HwioReadLog[addr] = count + 1; + } + } + } + + // Special exceptions for cleanly defined blocks of MMIO + if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels + { + return Nds.Audio.ReadHwio8Channels(addr); + } + + switch (addr) + { + case 0x4000004: case 0x4000005: // DISPSTAT + case 0x4000006: case 0x4000007: // VCOUNT + return Nds.Ppu.ReadHwio8Arm7(addr); + + case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD + case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD + case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT + case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD + case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD + case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT + case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD + case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD + case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT + case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD + case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD + case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT + case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data + case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data + case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data + case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data + return Nds.Dma7.ReadHwio8(addr); + + case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0 + case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1 + case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2 + case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3 + return Nds.Timers7.ReadHwio8(addr); + + case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC + case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT + case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND + case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV + return Nds.Ipcs[1].ReadHwio8(addr); + + case 0x40001A0: case 0x40001A1: // AUXSPICNT + case 0x40001A2: case 0x40001A3: // AUXSPIDATA + case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL + case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In + return Nds.Cartridge.ReadHwio8(true, addr); + + case 0x40001C0: case 0x40001C1: // SPICNT + case 0x40001C2: case 0x40001C3: // SPIDATA + return Nds.Spi.ReadHwio8(addr); + + case 0x4000136: case 0x4000137: // EXTKEYIN + // Console.WriteLine(Hex(Nds7.Cpu.R[15], 8)); + goto case 0x4000130; + case 0x4000130: case 0x4000131: // KEYINPUT + return Nds.Keypad.ReadHwio8(addr); + + case 0x4000204: case 0x4000205: // EXMEMSTAT + return Nds.MemoryControl.ReadHwio8Nds7(addr); + + case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME + case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE + case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF + return Nds.HwControl7.ReadHwio8(addr); + + case 0x4000134: + return 0x80; + case 0x4000135: // Stubbed RCNT + return 0; + + case 0x4000138: case 0x4000139: // RTC + return Nds.Rtc.ReadHwio8(addr); + + case 0x4000240: case 0x4000241: // Memory Control Status + return Nds.MemoryControl.ReadHwio8Nds7(addr); + + case 0x4000500: case 0x4000501: // SOUNDCNT + case 0x4000504: case 0x4000505: // SOUNDBIAS + case 0x4000508: case 0x4000509: // SNDCAPCNT + return Nds.Audio.ReadHwio8(addr); + + case 0x4000300: + // Console.WriteLine("NDS7 POSTFLG read"); + return Nds.HwControl7.Postflg; + + case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1 + return Nds.ReadHwio8Arm7(addr); + } + + // Console.WriteLine($"NDS7: Unmapped MMIO read addr:{Hex(addr, 8)}"); + + return 0; + } + + public void WriteHwio8(bool debug, uint addr, byte val) + { + if (LogHwioAccesses) + { + lock (HwioWriteLog) + { + if ((addr & ~1) != 0 && !debug) + { + uint count; + HwioWriteLog.TryGetValue(addr, out count); + HwioWriteLog[addr] = count + 1; + } + } + } + + // Special exceptions for cleanly defined blocks of MMIO + if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels + { + Nds.Audio.WriteHwio8Channels(addr, val); + return; + } + + switch (addr) + { + case 0x4000004: case 0x4000005: // DISPSTAT + case 0x4000006: case 0x4000007: // VCOUNT + Nds.Ppu.WriteHwio8Arm7(addr, val); return; + + case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD + case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD + case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT + case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD + case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD + case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT + case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD + case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD + case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT + case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD + case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD + case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT + case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data + case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data + case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data + case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data + Nds.Dma7.WriteHwio8(addr, val); return; + + case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0 + case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1 + case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2 + case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3 + Nds.Timers7.WriteHwio8(addr, val); return; + + case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC + case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT + case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND + Nds.Ipcs[1].WriteHwio8(addr, val); return; + + case 0x40001A0: case 0x40001A1: // AUXSPICNT + case 0x40001A2: case 0x40001A3: // AUXSPIDATA + case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL + case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3 + case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7 + Nds.Cartridge.WriteHwio8(true, addr, val); return; + + case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed + case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7: + case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB: + return; + + case 0x40001C0: case 0x40001C1: // SPICNT + case 0x40001C2: case 0x40001C3: // SPIDATA + Nds.Spi.WriteHwio8(addr, val); return; + + case 0x4000204: case 0x4000205: // EXMEMSTAT + Nds.MemoryControl.WriteHwio8Nds7(addr, val); return; + + case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME + case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE + case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF + Nds.HwControl7.WriteHwio8(addr, val); return; + + case 0x4000134: case 0x4000135: // Stubbed RCNT + return; + + case 0x4000138: case 0x4000139: // RTC + Nds.Rtc.WriteHwio8(addr, val); return; + + case 0x4000500: case 0x4000501: // SOUNDCNT + case 0x4000504: case 0x4000505: // SOUNDBIAS + case 0x4000508: case 0x4000509: // SNDCAPCNT + Nds.Audio.WriteHwio8(addr, val); return; + + case 0x4000300: + Console.WriteLine("NDS7 POSTFLG write"); + Nds.HwControl7.Postflg = (byte)(val & 1); + return; + + case 0x4000301: + if ((val & 0b11000000) == 0b10000000) + { + Nds.Cpu7.Halted = true; + } + return; + + case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1 + Nds.WriteHwio8Arm7(addr, val); + return; + } + + // Console.WriteLine($"NDS7: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}"); + } + } +} diff --git a/Assets/emulator/MemoryNds7.cs.meta b/Assets/emulator/MemoryNds7.cs.meta new file mode 100644 index 0000000..7858fbb --- /dev/null +++ b/Assets/emulator/MemoryNds7.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3898147a3efcd8499a8a51b3f945ff3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/MemoryNds9.cs b/Assets/emulator/MemoryNds9.cs new file mode 100644 index 0000000..4a505fd --- /dev/null +++ b/Assets/emulator/MemoryNds9.cs @@ -0,0 +1,622 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; +using static OptimeGBA.MemoryUtil; +using static Util; + +namespace OptimeGBA +{ + public sealed unsafe class MemoryNds9 : Memory + { + Nds Nds; + + public MemoryNds9(Nds nds, ProviderNds provider) + { + Nds = nds; + + SaveProvider = new NullSaveProvider(); + + for (uint i = 0; i < Arm9BiosSize && i < provider.Bios9.Length; i++) + { + Arm9Bios[i] = provider.Bios9[i]; + } + } + + public const int Arm9BiosSize = 4096; + public byte[] Arm9Bios = new byte[Arm9BiosSize]; + public const int ItcmSize = 32768; + public byte[] Itcm = new byte[ItcmSize]; + public const int DtcmSize = 16384; + public byte[] Dtcm = new byte[DtcmSize]; + + public uint DtcmBase = 0; + public uint ItcmVirtualSize = 0; + public uint DtcmVirtualSize = 0; + public bool ItcmLoadMode = false; + public bool DtcmLoadMode = false; + + public override void InitPageTable(byte*[] table, uint[] maskTable, bool write) + { + byte* mainRam = TryPinByteArray(Nds.MainRam); + byte* arm9Bios = TryPinByteArray(Arm9Bios); + byte* dtcm = TryPinByteArray(Dtcm); + byte* itcm = TryPinByteArray(Itcm); + + // 12 bits shaved off already, shave off another 12 to get 24 + for (uint i = 0; i < 1048576; i++) + { + table[i] = null; // Clear everything out first, since on ARM9 things can move around + + uint addr = (uint)(i << 12); + switch (i >> 12) + { + case 0x2: // Main Memory + table[i] = mainRam; + maskTable[i] = 0x003FFFFF; + break; + case 0xFF: // BIOS + if (!write) + { + table[i] = arm9Bios; + } + maskTable[i] = 0x00000FFF; + break; + } + + if (addr >= DtcmBase && addr < DtcmBase + DtcmVirtualSize) + { + + if (write || !DtcmLoadMode) + { + // Console.WriteLine("DTCM page set at " + Util.Hex(addr, 8)); + table[i] = dtcm; + } + maskTable[i] = 0x00003FFF; + } + + // ITCM is immovable + // ITCM has higher priority so write pages in after DTCM + if (addr < ItcmVirtualSize) + { + if (write || !ItcmLoadMode) + { + table[i] = itcm; + } + maskTable[i] = 0x00007FFF; + } + } + } + + ~MemoryNds9() + { + Console.WriteLine("Cleaning up NDS9 memory..."); + UnpinByteArray(Nds.MainRam); + UnpinByteArray(Arm9Bios); + UnpinByteArray(Dtcm); + UnpinByteArray(Itcm); + } + + public void UpdateTcmSettings() + { + // Console.WriteLine("Data TCM Settings: " + Util.Hex(Nds.Cp15.DataTcmSettings, 8)); + ItcmVirtualSize = 512U << (int)((Nds.Cp15.InstTcmSettings >> 1) & 0x1F); + DtcmVirtualSize = 512U << (int)((Nds.Cp15.DataTcmSettings >> 1) & 0x1F); + + DtcmBase = (uint)(Nds.Cp15.DataTcmSettings & 0xFFFFF000); + + ItcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 19); + DtcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 17); + + Console.WriteLine("ITCM set to: " + Util.Hex(0, 8) + " - " + Util.Hex(ItcmVirtualSize - 1, 8)); + Console.WriteLine("DTCM set to: " + Util.Hex(DtcmBase, 8) + " - " + Util.Hex(DtcmBase + DtcmVirtualSize - 1, 8)); + + InitPageTables(); + } + + public (byte[] array, uint offset) GetSharedRamParams(uint addr) + { + switch (Nds.MemoryControl.SharedRamControl) + { + case 0: + default: + addr &= 0x7FFF; // All 32k of Shared RAM + return (Nds.SharedRam, addr); + case 1: + addr &= 0x3FFF; // 2nd half of Shared RAM + addr += 0x4000; + return (Nds.SharedRam, addr); + case 2: + addr &= 0x3FFF; // 1st half of Shared RAM + return (Nds.SharedRam, addr); + case 3: + // throw new NotImplementedException("Implement unmapping Shared RAM from ARM9 without EmptyPage, since some game can possibly try to write to the EmptyPage"); + EmptyPage[0] = 0; + return (EmptyPage, 0); // Unmapped + } + } + + public override byte Read8Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetByte(array, offset); + case 0x4: // I/O Registers + return ReadHwio8(debug, addr); + case 0x5: // PPU Palettes + return Nds.Ppu.ReadPalettes8(addr); + case 0x6: // VRAM + return Nds.Ppu.ReadVram8Arm9(addr); + case 0x7: // PPU OAM + return Nds.Ppu.ReadOam8(addr); + } + + return 0; + } + + public override ushort Read16Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetUshort(array, offset); + case 0x4: // I/O Registers + byte f0 = ReadHwio8(debug, addr++); + byte f1 = ReadHwio8(debug, addr++); + + ushort u16 = (ushort)((f1 << 8) | (f0 << 0)); + + return u16; + case 0x5: // PPU Palettes + return Nds.Ppu.ReadPalettes16(addr); + case 0x6: // VRAM + return (ushort)( + (Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) | + (Nds.Ppu.ReadVram8Arm9(addr + 1) << 8) + ); + case 0x7: // PPU OAM + return Nds.Ppu.ReadOam16(addr); + } + + return 0; + } + + public override uint Read32Unregistered(bool debug, uint addr) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + return GetUint(array, offset); + case 0x4: // I/O Registers + if (addr >= 0x4000320 && addr < 0x40006A4) // 3D + { + return Nds.Ppu3D.ReadHwio32(addr); + } + + byte f0 = ReadHwio8(debug, addr + 0); + byte f1 = ReadHwio8(debug, addr + 1); + byte f2 = ReadHwio8(debug, addr + 2); + byte f3 = ReadHwio8(debug, addr + 3); + + uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0)); + + return u32; + case 0x5: // PPU Palettes + return Nds.Ppu.ReadPalettes32(addr); + case 0x6: // VRAM + return (uint)( + (Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) | + (Nds.Ppu.ReadVram8Arm9(addr + 1) << 8) | + (Nds.Ppu.ReadVram8Arm9(addr + 2) << 16) | + (Nds.Ppu.ReadVram8Arm9(addr + 3) << 24) + ); + case 0x7: // PPU OAM + return Nds.Ppu.ReadOam32(addr); + } + + return 0; + } + + public override void Write8Unregistered(bool debug, uint addr, byte val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetByte(array, offset, val); + break; + case 0x4: // I/O Registers + WriteHwio8(debug, addr, val); + break; + case 0x5: // PPU Palettes - duplicated across upper-lower in 8-bit?? + Console.WriteLine("NDS: 8-bit write to palettes"); + // Nds.Ppu.WritePalettes8(addr + 0, val); + // Nds.Ppu.WritePalettes8(addr + 1, val); + break; + } + } + + public override void Write16Unregistered(bool debug, uint addr, ushort val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetUshort(array, offset, val); + break; + case 0x4: // I/O Registers + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + break; + case 0x5: // PPU Palettes + Nds.Ppu.WritePalettes16(addr, val); + break; + case 0x6: // VRAM + Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0)); + Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8)); + break; + case 0x7: // PPU OAM + Nds.Ppu.WriteOam16(addr, val); + break; + } + } + + public override void Write32Unregistered(bool debug, uint addr, uint val) + { + switch (addr >> 24) + { + case 0x3: // Shared RAM + (byte[] array, uint offset) = GetSharedRamParams(addr); + SetUint(array, offset, val); + break; + case 0x4: // I/O Registers + if (addr >= 0x4000320 && addr < 0x40006A4) // 3D + { + Nds.Ppu3D.WriteHwio32(addr, val); + return; + } + WriteHwio8(debug, addr++, (byte)(val >> 0)); + WriteHwio8(debug, addr++, (byte)(val >> 8)); + WriteHwio8(debug, addr++, (byte)(val >> 16)); + WriteHwio8(debug, addr++, (byte)(val >> 24)); + break; + case 0x5: // PPU Palettes + Nds.Ppu.WritePalettes32(addr, val); + break; + case 0x6: // VRAM + Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0)); + Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8)); + Nds.Ppu.WriteVram8Arm9(addr + 2, (byte)(val >> 16)); + Nds.Ppu.WriteVram8Arm9(addr + 3, (byte)(val >> 24)); + break; + case 0x7: // PPU OAM + Nds.Ppu.WriteOam32(addr, val); + break; + } + } + + public byte ReadHwio8(bool debug, uint addr) + { + if (LogHwioAccesses) + { + lock (HwioReadLog) { + if ((addr & ~1) != 0 && !debug) + { + uint count; + HwioReadLog.TryGetValue(addr, out count); + HwioReadLog[addr] = count + 1; + } + } + } + + if (addr >= 0x4000320 && addr < 0x40006A4) // 3D + { + Console.Error.WriteLine("8-bit or 16-bit read to 3D"); + return 0; + } + + switch (addr) + { + // Engine A + case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A + case 0x4000004: case 0x4000005: // DISPSTAT + case 0x4000006: case 0x4000007: // VCOUNT + case 0x4000008: case 0x4000009: // BG0CNT + case 0x400000A: case 0x400000B: // BG1CNT + case 0x400000C: case 0x400000D: // BG2CNT + case 0x400000E: case 0x400000F: // BG3CNT + case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS + case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS + case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS + case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS + case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB + case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD + case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X + case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y + case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB + case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD + case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X + case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y + case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH + case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV + case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT + case 0x400004C: case 0x400004D: // MOSAIC + case 0x4000050: case 0x4000051: // BLDCNT + case 0x4000052: case 0x4000053: // BLDALPHA + case 0x4000054: case 0x4000055: // BLDY + case 0x4000060: case 0x4000061: // DISP3DCNT + case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT + case 0x400006C: case 0x400006D: // MASTER_BRIGHT + + // Engine B + case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A + case 0x4001008: case 0x4001009: // BG0CNT + case 0x400100A: case 0x400100B: // BG1CNT + case 0x400100C: case 0x400100D: // BG2CNT + case 0x400100E: case 0x400100F: // BG3CNT + case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS + case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS + case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS + case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS + case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB + case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD + case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X + case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y + case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB + case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD + case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X + case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y + case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH + case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV + case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT + case 0x400104C: case 0x400104D: // MOSAIC + case 0x4001050: case 0x4001051: // BLDCNT + case 0x4001052: case 0x4001053: // BLDALPHA + case 0x4001054: case 0x4001055: // BLDY + case 0x400106C: case 0x400106D: // MASTER_BRIGHT + return Nds.Ppu.ReadHwio8Arm9(addr); + + case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD + case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD + case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT + case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD + case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD + case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT + case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD + case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD + case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT + case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD + case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD + case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT + case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data + case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data + case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data + case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data + return Nds.Dma9.ReadHwio8(addr); + + case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0 + case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1 + case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2 + case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3 + return Nds.Timers9.ReadHwio8(addr); + + case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC + case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT + case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND + case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV + return Nds.Ipcs[0].ReadHwio8(addr); + + case 0x40001A0: case 0x40001A1: // AUXSPICNT + case 0x40001A2: case 0x40001A3: // AUXSPIDATA + case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL + case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In + return Nds.Cartridge.ReadHwio8(false, addr); + + case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME + case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE + case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF + return Nds.HwControl9.ReadHwio8(addr); + + case 0x4000130: case 0x4000131: // KEYINPUT + return Nds.Keypad.ReadHwio8(addr); + + case 0x4000204: case 0x4000205: // EXMEMCNT + case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT + case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT + case 0x4000248: case 0x4000249: // VRAMCNT + return Nds.MemoryControl.ReadHwio8Nds9(addr); + + case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3 + case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER + case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER + case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM + case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM + case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT + case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT + case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT + case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT + case 0x40002B0: case 0x40002B1: // SQRTCNT + case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT + case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM + case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM + return Nds.Math.ReadHwio8(addr); + + case 0x4000300: + // Console.WriteLine("NDS9 POSTFLG read"); + return Nds.HwControl9.Postflg; + case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1 + return Nds.ReadHwio8Arm9(addr); + } + + // Console.WriteLine($"NDS9: Unmapped MMIO read addr:{Hex(addr, 8)}"); + + return 0; + } + + public void WriteHwio8(bool debug, uint addr, byte val) + { + if (LogHwioAccesses) + { + lock (HwioWriteLog) { + if ((addr & ~1) != 0 && !debug) + { + uint count; + HwioWriteLog.TryGetValue(addr, out count); + HwioWriteLog[addr] = count + 1; + } + } + } + + if (addr >= 0x4000320 && addr < 0x40006A4) // 3D + { + // Console.Error.WriteLine($"8-bit or 16-bit write to 3D addr:{Hex(addr, 8)} val:{Hex(val, 2)}"); + return; + } + + switch (addr) + { + // Engine A + case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A + case 0x4000004: case 0x4000005: // DISPSTAT + case 0x4000006: case 0x4000007: // VCOUNT + case 0x4000008: case 0x4000009: // BG0CNT + case 0x400000A: case 0x400000B: // BG1CNT + case 0x400000C: case 0x400000D: // BG2CNT + case 0x400000E: case 0x400000F: // BG3CNT + case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS + case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS + case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS + case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS + case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB + case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD + case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X + case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y + case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB + case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD + case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X + case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y + case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH + case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV + case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT + case 0x400004C: case 0x400004D: // MOSAIC + case 0x4000050: case 0x4000051: // BLDCNT + case 0x4000052: case 0x4000053: // BLDALPHA + case 0x4000054: case 0x4000055: // BLDY + case 0x4000060: case 0x4000061: // DISP3DCNT + case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT + case 0x400006C: case 0x400006D: // MASTER_BRIGHT + + // Engine B + case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A + case 0x4001008: case 0x4001009: // BG0CNT + case 0x400100A: case 0x400100B: // BG1CNT + case 0x400100C: case 0x400100D: // BG2CNT + case 0x400100E: case 0x400100F: // BG3CNT + case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS + case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS + case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS + case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS + case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB + case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD + case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X + case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y + case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB + case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD + case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X + case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y + case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH + case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV + case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT + case 0x400104C: case 0x400104D: // MOSAIC + case 0x4001050: case 0x4001051: // BLDCNT + case 0x4001052: case 0x4001053: // BLDALPHA + case 0x4001054: case 0x4001055: // BLDY + case 0x400106C: case 0x400106D: // MASTER_BRIGHT + Nds.Ppu.WriteHwio8Arm9(addr, val); return; + + case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD + case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD + case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT + case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD + case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD + case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT + case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD + case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD + case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT + case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD + case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD + case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT + case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data + case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data + case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data + case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data + Nds.Dma9.WriteHwio8(addr, val); return; + + case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0 + case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1 + case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2 + case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3 + Nds.Timers9.WriteHwio8(addr, val); return; + + case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC + case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT + case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND + Nds.Ipcs[0].WriteHwio8(addr, val); return; + + case 0x40001A0: case 0x40001A1: // AUXSPICNT + case 0x40001A2: case 0x40001A3: // AUXSPIDATA + case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL + case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3 + case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7 + Nds.Cartridge.WriteHwio8(false, addr, val); return; + + case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed + case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7: + case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB: + return; + + case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME + case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE + case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF + Nds.HwControl9.WriteHwio8(addr, val); return; + + case 0x4000204: case 0x4000205: // EXMEMCNT + case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT + case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT + case 0x4000248: case 0x4000249: // VRAMCNT + Nds.MemoryControl.WriteHwio8Nds9(addr, val); return; + + case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3 + case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER + case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER + case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM + case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM + case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT + case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT + case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT + case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT + case 0x40002B0: case 0x40002B1: // SQRTCNT + case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT + case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM + case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM + Nds.Math.WriteHwio8(addr, val); return; + + case 0x4000300: + Console.WriteLine("NDS9 POSTFLG write"); + Nds.HwControl9.Postflg = (byte)(val & 0b11); + return; + case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307:// POWCNT1 + Nds.WriteHwio8Arm9(addr, val); + return; + } + + // Console.WriteLine($"NDS9: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}"); + } + } +} diff --git a/Assets/emulator/MemoryNds9.cs.meta b/Assets/emulator/MemoryNds9.cs.meta new file mode 100644 index 0000000..78db1fa --- /dev/null +++ b/Assets/emulator/MemoryNds9.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c562e007e77a5c448b8dfa6621f2efb8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/MemoryUtil.cs b/Assets/emulator/MemoryUtil.cs new file mode 100644 index 0000000..9f80ea3 --- /dev/null +++ b/Assets/emulator/MemoryUtil.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Collections.Concurrent; +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; + +namespace OptimeGBA +{ + public static unsafe class MemoryUtil + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong GetUlong(byte[] arr, uint addr) + { + return + ((ulong)arr[addr + 0] << 0) | + ((ulong)arr[addr + 1] << 8) | + ((ulong)arr[addr + 2] << 16) | + ((ulong)arr[addr + 3] << 24) | + ((ulong)arr[addr + 4] << 32) | + ((ulong)arr[addr + 5] << 40) | + ((ulong)arr[addr + 6] << 48) | + ((ulong)arr[addr + 7] << 56); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetUint(byte[] arr, uint addr) + { + return (uint)( + (arr[addr + 0] << 0) | + (arr[addr + 1] << 8) | + (arr[addr + 2] << 16) | + (arr[addr + 3] << 24) + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort GetUshort(byte[] arr, uint addr) + { + return (ushort)( + (arr[addr + 0] << 0) | + (arr[addr + 1] << 8) + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByte(byte[] arr, uint addr) + { + return arr[addr]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong GetUlong(byte* arr, uint addr) + { + return *(ulong*)(arr + addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetUint(byte* arr, uint addr) + { + return *(uint*)(arr + addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort GetUshort(byte* arr, uint addr) + { + return *(ushort*)(arr + addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte GetByte(byte* arr, uint addr) + { + return *(byte*)(arr + addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUlong(byte[] arr, uint addr, ulong val) + { + arr[addr + 0] = (byte)(val >> 0); + arr[addr + 1] = (byte)(val >> 8); + arr[addr + 2] = (byte)(val >> 16); + arr[addr + 3] = (byte)(val >> 24); + arr[addr + 4] = (byte)(val >> 32); + arr[addr + 5] = (byte)(val >> 40); + arr[addr + 6] = (byte)(val >> 48); + arr[addr + 7] = (byte)(val >> 56); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUint(byte[] arr, uint addr, uint val) + { + arr[addr + 0] = (byte)(val >> 0); + arr[addr + 1] = (byte)(val >> 8); + arr[addr + 2] = (byte)(val >> 16); + arr[addr + 3] = (byte)(val >> 24); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUshort(byte[] arr, uint addr, ushort val) + { + arr[addr + 0] = (byte)(val >> 0); + arr[addr + 1] = (byte)(val >> 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetByte(byte[] arr, uint addr, byte val) + { + arr[addr] = val; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUlong(byte* arr, uint addr, ulong val) + { + *(ulong*)(arr + addr) = val; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUint(byte* arr, uint addr, uint val) + { + *(uint*)(arr + addr) = val; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetUshort(byte* arr, uint addr, ushort val) + { + *(ushort*)(arr + addr) = val; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetByte(byte* arr, uint addr, byte val) + { + *(byte*)(arr + addr) = val; + } + + public static byte* AllocateUnmanagedArray(int size) + { + byte* arr = (byte*)Marshal.AllocHGlobal(size).ToPointer(); + + // Zero out array + for (int i = 0; i < size; i++) + { + arr[i] = 0; + } + + return arr; + } + + public static ushort* AllocateUnmanagedArray16(int size) + { + ushort* arr = (ushort*)Marshal.AllocHGlobal(size * sizeof(ushort)).ToPointer(); + + // Zero out array + for (int i = 0; i < size; i++) + { + arr[i] = 0; + } + + return arr; + } + + public static uint* AllocateUnmanagedArray32(int size) + { + uint* arr = (uint*)Marshal.AllocHGlobal(size * sizeof(uint)).ToPointer(); + + // Zero out array + for (int i = 0; i < size; i++) + { + arr[i] = 0; + } + + return arr; + } + + public static void FreeUnmanagedArray(void* arr) + { + Marshal.FreeHGlobal(new IntPtr(arr)); + } + } +} diff --git a/Assets/emulator/MemoryUtil.cs.meta b/Assets/emulator/MemoryUtil.cs.meta new file mode 100644 index 0000000..69d21ff --- /dev/null +++ b/Assets/emulator/MemoryUtil.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d5d8289e96e59342bab0324b6d66a91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Nds.cs b/Assets/emulator/Nds.cs new file mode 100644 index 0000000..4ecdc83 --- /dev/null +++ b/Assets/emulator/Nds.cs @@ -0,0 +1,396 @@ +using System; +using static OptimeGBA.MemoryUtil; +using static OptimeGBA.Bits; +using static Util; + +namespace OptimeGBA +{ + public unsafe sealed class Nds + { + public ProviderNds Provider; + + // ARM9 side + public MemoryNds9 Mem9; + public Arm7 Cpu9; + public HwControlNds HwControl9; + public DmaNds Dma9; + public Timers Timers9; + public Nds9Math Math; + + // ARM7 side + public MemoryNds7 Mem7; + public Arm7 Cpu7; + public HwControlNds HwControl7; + public Spi Spi; + public NdsAudio Audio; + public DmaNds Dma7; + public Timers Timers7; + + public Scheduler Scheduler; + + public Cp15 Cp15; + + public CartridgeNds Cartridge; + + // Based off of EXMEMCNT ownership rules, there 1 is ARM7 + public Ipc[] Ipcs; // 0: ARM9 to ARM7, 1: ARM7 to ARM9 + + public PpuNds Ppu; + public PpuNds3D Ppu3D; + + public MemoryControlNds MemoryControl; + + public Keypad Keypad = new Keypad(); + + public RtcNds Rtc; + + public byte[] MainRam = new byte[4194304]; + public byte[] SharedRam = new byte[32768]; + + public int Arm9PendingTicks; + + public ulong Steps; + + public Nds(ProviderNds provider) + { + Provider = provider; + Scheduler = new Scheduler(); + + Ipcs = new Ipc[] { + new Ipc(this, 0), + new Ipc(this, 1), + }; + + Cp15 = new Cp15(this); + + Cartridge = new CartridgeNds(this); + + Ppu = new PpuNds(this, Scheduler); + Ppu3D = new PpuNds3D(this, Scheduler); + + MemoryControl = new MemoryControlNds(); + + Rtc = new RtcNds(); + + // ARM9 Init + Mem9 = new MemoryNds9(this, Provider); + Cpu9 = new Arm7(StateChangeArm9, Mem9, true, true, Cp15); + HwControl9 = new HwControlNds(Cpu9); + Dma9 = new DmaNds(false, Mem9, HwControl9); + Timers9 = new Timers(null, HwControl9, Scheduler, true, false); + Math = new Nds9Math(this); + Mem9.InitPageTables(); + Cpu9.InitFlushPipeline(); + Cpu9.SetVectorMode(true); + // screw it + Cpu9.SetTimingsTable( + Cpu9.Timing8And16, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu9.SetTimingsTable( + Cpu9.Timing32, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu9.SetTimingsTable( + Cpu9.Timing8And16InstrFetch, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu9.SetTimingsTable( + Cpu9.Timing32InstrFetch, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + + // ARM7 init + Mem7 = new MemoryNds7(this, Provider); + Spi = new Spi(this); + Audio = new NdsAudio(this); + Cpu7 = new Arm7(StateChangeArm7, Mem7, false, false, null); + HwControl7 = new HwControlNds(Cpu7); + Dma7 = new DmaNds(true, Mem7, HwControl7); + Timers7 = new Timers(null, HwControl7, Scheduler, true, true); + Mem7.InitPageTables(); + Cpu7.InitFlushPipeline(); + // screw it + Cpu7.SetTimingsTable( + Cpu7.Timing8And16, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu7.SetTimingsTable( + Cpu7.Timing32, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu7.SetTimingsTable( + Cpu7.Timing8And16InstrFetch, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + Cpu7.SetTimingsTable( + Cpu7.Timing32InstrFetch, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ); + +#if UNSAFE + Console.WriteLine("Starting in memory UNSAFE mode"); +#else + Console.WriteLine("Starting in memory SAFE mode"); +#endif + + if (provider.DirectBoot) + { + var rom = provider.Rom; + + // Firmware init + MemoryControl.SharedRamControl = 3; + HwControl7.Postflg = 1; + HwControl9.Postflg = 1; + + Cartridge.Slot1Enable = true; + + Cpu9.IRQDisable = true; + Cpu9.FIQDisable = true; + + // Thanks Hydr8gon / fleroviux lol + Mem7.Write16(0x4000184, 0x8501); // IPCFIFOCNT7 + Mem9.Write16(0x4000184, 0x8501); // IPCFIFOCNT9 + + Cp15.TransferTo(0, 0x0005707D, 1, 0, 0); // CP15 Control + Cp15.TransferTo(0, 0x0300000A, 9, 1, 0); // Data TCM base/size + Cp15.TransferTo(0, 0x00000020, 9, 1, 1); // Instruction TCM size + Mem9.Write8(0x4000247, 0x03); // WRAMCNT + Mem9.Write16(0x4000304, 0x0001); // POWCNT1 + Mem7.Write16(0x4000504, 0x0200); // SOUNDBIAS + + Mem9.Write32(0x027FF800, 0x1FC2); // Chip ID 1 + Mem9.Write32(0x027FF804, 0x1FC2); // Chip ID 2 + Mem9.Write16(0x027FF850, 0x5835); // ARM7 BIOS CRC + Mem9.Write16(0x027FF880, 0x0007); // Message from ARM9 to ARM7 + Mem9.Write16(0x027FF884, 0x0006); // ARM7 boot task + Mem9.Write32(0x027FFC00, 0x1FC2); // Copy of chip ID 1 + Mem9.Write32(0x027FFC04, 0x1FC2); // Copy of chip ID 2 + Mem9.Write16(0x027FFC10, 0x5835); // Copy of ARM7 BIOS CRC + Mem9.Write16(0x027FFC40, 0x0001); // Boot indicator + + Mem9.Write32(0x027FF864, 0); + Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3)); + + Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26)); + Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04)); + + // Copy in header + if (rom.Length >= 0x170) + { + for (uint i = 0; i < 0x170; i++) + { + Mem9.Write8(0x027FFE00 + i, rom[i]); + } + } + + for (uint i = 0; i < 0x70; i++) + { + Mem9.Write8(0x27FFC80 + i, Provider.Firmware[0x3FF00 + i]); + } + + Mem9.Write32(0x027FF864, 0); + Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3)); + + Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26)); + Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04)); + + if (rom.Length >= 0x20) + { + uint arm7RomOffset = GetUint(rom, 0x30); + uint arm7EntryAddr = GetUint(rom, 0x34); + uint arm7RamAddr = GetUint(rom, 0x38); + uint arm7Size = GetUint(rom, 0x3C); + + // ROM offset is aligned by 0x1000 + Console.WriteLine("ARM7 ROM Offset: " + Hex(arm7RomOffset, 8)); + Console.WriteLine("ARM7 RAM Address: " + Hex(arm7RamAddr, 8)); + Console.WriteLine("ARM7 Entry: " + Hex(arm7EntryAddr, 8)); + Console.WriteLine("ARM7 Size: " + arm7Size); + for (uint i = 0; i < arm7Size; i++) + { + Mem7.Write8(arm7RamAddr + i, rom[arm7RomOffset + i]); + } + Cpu7.R[13] = 0x3002F7C; + Cpu7.SetModeReg(13, Arm7Mode.IRQ, 0x3003F80); + Cpu7.SetModeReg(13, Arm7Mode.SVC, 0x3003FC0); + Cpu7.R[12] = arm7EntryAddr; + Cpu7.R[14] = arm7EntryAddr; + Cpu7.R[15] = arm7EntryAddr; + Cpu7.InitFlushPipeline(); + + uint arm9RomOffset = GetUint(rom, 0x20); + uint arm9EntryAddr = GetUint(rom, 0x24); + uint arm9RamAddr = GetUint(rom, 0x28); + uint arm9Size = GetUint(rom, 0x2C); + + Console.WriteLine("ARM9 ROM Offset: " + Hex(arm9RomOffset, 8)); + Console.WriteLine("ARM9 RAM Address: " + Hex(arm9RamAddr, 8)); + Console.WriteLine("ARM9 Entry: " + Hex(arm9EntryAddr, 8)); + Console.WriteLine("ARM9 Size: " + arm9Size); + for (uint i = 0; i < arm9Size; i++) + { + Mem9.Write8(arm9RamAddr + i, rom[arm9RomOffset + i]); + } + Cpu9.R[13] = 0x380FD80; + Cpu9.SetModeReg(13, Arm7Mode.IRQ, 0x380FF80); + Cpu9.SetModeReg(13, Arm7Mode.SVC, 0x380FFC0); + Cpu9.R[12] = arm9EntryAddr; + Cpu9.R[14] = arm9EntryAddr; + Cpu9.R[15] = arm9EntryAddr; + Cpu9.InitFlushPipeline(); + } + } + } + + public uint Step() + { + Steps++; + + long beforeTicks = Scheduler.CurrentTicks; + + while (Scheduler.CurrentTicks < Scheduler.NextEventTicks) + { + // Running both CPUs at 1CPI at 32 MHz causes the firmware to loop the setup screen, + // so don't do that when not debugging simple test ROMs + // Cpu7.Execute(); + // Cpu9.Execute(); + // Scheduler.CurrentTicks += 1; + + // TODO: Proper NDS timings + // TODO: Figure out a better way to implement halting + uint ticks7 = 0; + // Run 32 ARM7 instructions at a time, who needs tight synchronization + const uint instrsAtATime = 32; + if (!Cpu7.Halted) + { + for (uint i = 0; i < instrsAtATime; i++) + { + if (!Cpu7.Halted) + { + ticks7 += Cpu7.Execute(); + } + else + { + ticks7 += instrsAtATime; + break; + } + } + } + else + { + ticks7 += instrsAtATime; + } + + Arm9PendingTicks += (int)ticks7 * 2; // ARM9 runs at twice the speed of ARM7 + while (Arm9PendingTicks > 0) + { + if (!Cpu9.Halted) + { + Arm9PendingTicks -= (int)Cpu9.Execute(); + } + else + { + Arm9PendingTicks -= (int)(Scheduler.NextEventTicks - Scheduler.CurrentTicks) * 2; + break; + } + } + + Ppu3D.Run(); + + Scheduler.CurrentTicks += ticks7; + } + + long current = Scheduler.CurrentTicks; + long next = Scheduler.NextEventTicks; + Scheduler.PopFirstEvent().Callback(current - next); + + return (uint)(Scheduler.CurrentTicks - beforeTicks); + } + + public void DoNothing(long cyclesLate) { } + + public void Tick(uint cycles) + { + Scheduler.CurrentTicks += cycles; + } + + public void HaltSkip(long cyclesOffset) { } + + // POWCNT1 + public bool EnableDisplay; + public bool Enable2DEngineA; + public bool Enable3DRenderingEngine; + public bool Enable3DGeometryEngine; + public bool Enable2DEngineB; + public bool DisplaySwap; + + public byte ReadHwio8Arm9(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000304: + if (EnableDisplay) val = BitSet(val, 0); + if (Enable2DEngineA) val = BitSet(val, 1); + if (Enable3DRenderingEngine) val = BitSet(val, 2); + if (Enable3DGeometryEngine) val = BitSet(val, 3); + break; + case 0x4000305: + if (Enable2DEngineB) val = BitSet(val, 1); + if (DisplaySwap) val = BitSet(val, 7); + break; + } + return val; + } + + public void WriteHwio8Arm9(uint addr, byte val) + { + switch (addr) + { + case 0x4000304: + EnableDisplay = BitTest(val, 0); + Enable2DEngineA = BitTest(val, 1); + Enable3DRenderingEngine = BitTest(val, 2); + Enable3DGeometryEngine = BitTest(val, 3); + break; + case 0x4000305: + Enable2DEngineB = BitTest(val, 1); + DisplaySwap = BitTest(val, 7); + break; + } + } + + public void StateChangeArm9() { } + + // POWCNT2 + public bool EnableSpeakers; + public bool EnableWifi; + + public byte ReadHwio8Arm7(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000304: + if (EnableSpeakers) val = BitSet(val, 0); + if (EnableWifi) val = BitSet(val, 1); + break; + } + return val; + } + + public void WriteHwio8Arm7(uint addr, byte val) + { + switch (addr) + { + case 0x4000304: + EnableSpeakers = BitTest(val, 0); + EnableWifi = BitTest(val, 1); + break; + } + } + + public void StateChangeArm7() { } + } +} \ No newline at end of file diff --git a/Assets/emulator/Nds.cs.meta b/Assets/emulator/Nds.cs.meta new file mode 100644 index 0000000..ed372df --- /dev/null +++ b/Assets/emulator/Nds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4cdddf1f287aa24d91a6aa89a4bae70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Nds9Math.cs b/Assets/emulator/Nds9Math.cs new file mode 100644 index 0000000..a1cca53 --- /dev/null +++ b/Assets/emulator/Nds9Math.cs @@ -0,0 +1,276 @@ +using System; +using static OptimeGBA.Bits; +namespace OptimeGBA +{ + public unsafe sealed class Nds9Math + { + public Nds Nds; + + public Nds9Math(Nds nds) + { + Nds = nds; + } + + public long DIV_NUMER; + public long DIV_DENOM; + + public long DIV_RESULT; + public long DIVREM_RESULT; + + public uint SQRT_RESULT; + public ulong SQRT_PARAM; + + // DIVCNT + public uint DivisionMode; + public bool DividedByZero; + public bool DivideBusy; + + // SQRTCNT + public bool SqrtUse64BitInput; + public bool SqrtBusy; + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000280: // DIVCNT B0 + val |= (byte)(DivisionMode & 0b11); + break; + case 0x4000281: // DIVCNT B1 + if (DividedByZero) val = BitSet(val, 6); + if (DivideBusy) val = BitSet(val, 7); + break; + case 0x4000282: // DIVCNT B2 + case 0x4000283: // DIVCNT B3 + break; + + case 0x4000290: // DIV_NUMER B0 + case 0x4000291: // DIV_NUMER B1 + case 0x4000292: // DIV_NUMER B2 + case 0x4000293: // DIV_NUMER B3 + case 0x4000294: // DIV_NUMER B4 + case 0x4000295: // DIV_NUMER B5 + case 0x4000296: // DIV_NUMER B6 + case 0x4000297: // DIV_NUMER B7 + return GetByteIn(DIV_NUMER, addr & 7); + case 0x4000298: // DIV_DENOM B0 + case 0x4000299: // DIV_DENOM B1 + case 0x400029A: // DIV_DENOM B2 + case 0x400029B: // DIV_DENOM B3 + case 0x400029C: // DIV_DENOM B4 + case 0x400029D: // DIV_DENOM B5 + case 0x400029E: // DIV_DENOM B6 + case 0x400029F: // DIV_DENOM B7 + return GetByteIn(DIV_DENOM, addr & 7); + case 0x40002A0: // DIV_RESULT B0 + case 0x40002A1: // DIV_RESULT B1 + case 0x40002A2: // DIV_RESULT B2 + case 0x40002A3: // DIV_RESULT B3 + case 0x40002A4: // DIV_RESULT B4 + case 0x40002A5: // DIV_RESULT B5 + case 0x40002A6: // DIV_RESULT B6 + case 0x40002A7: // DIV_RESULT B7 + return GetByteIn(DIV_RESULT, addr & 7); + case 0x40002A8: // DIVREM_RESULT B0 + case 0x40002A9: // DIVREM_RESULT B1 + case 0x40002AA: // DIVREM_RESULT B2 + case 0x40002AB: // DIVREM_RESULT B3 + case 0x40002AC: // DIVREM_RESULT B4 + case 0x40002AD: // DIVREM_RESULT B5 + case 0x40002AE: // DIVREM_RESULT B6 + case 0x40002AF: // DIVREM_RESULT B7 + return GetByteIn(DIVREM_RESULT, addr & 7); + + case 0x40002B0: // SQRTCNT B0 + if (SqrtUse64BitInput) val = BitSet(val, 0); + break; + case 0x40002B1: // SQRTCNT B0 + break; + + case 0x40002B4: // SQRT_RESULT B0 + case 0x40002B5: // SQRT_RESULT B1 + case 0x40002B6: // SQRT_RESULT B2 + case 0x40002B7: // SQRT_RESULT B3 + return GetByteIn(SQRT_RESULT, addr & 3); + case 0x40002B8: // SQRT_PARAM B0 + case 0x40002B9: // SQRT_PARAM B1 + case 0x40002BA: // SQRT_PARAM B2 + case 0x40002BB: // SQRT_PARAM B3 + case 0x40002BC: // SQRT_PARAM B4 + case 0x40002BD: // SQRT_PARAM B5 + case 0x40002BE: // SQRT_PARAM B6 + case 0x40002BF: // SQRT_PARAM B7 + return GetByteIn(SQRT_PARAM, addr & 7); + + default: + throw new NotImplementedException("Read from DS math @ " + Util.Hex(addr, 8)); + } + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000280: // DIVCNT B0 + DivisionMode = (byte)(val & 0b11); + Divide(); + break; + case 0x4000281: // DIVCNT B1 + case 0x4000282: // DIVCNT B2 + case 0x4000283: // DIVCNT B3 + break; + + case 0x4000290: // DIV_NUMER B0 + case 0x4000291: // DIV_NUMER B1 + case 0x4000292: // DIV_NUMER B2 + case 0x4000293: // DIV_NUMER B3 + case 0x4000294: // DIV_NUMER B4 + case 0x4000295: // DIV_NUMER B5 + case 0x4000296: // DIV_NUMER B6 + case 0x4000297: // DIV_NUMER B7 + DIV_NUMER = SetByteIn(DIV_NUMER, val, addr & 7); + Divide(); + break; + case 0x4000298: // DIV_DENOM B0 + case 0x4000299: // DIV_DENOM B1 + case 0x400029A: // DIV_DENOM B2 + case 0x400029B: // DIV_DENOM B3 + case 0x400029C: // DIV_DENOM B4 + case 0x400029D: // DIV_DENOM B5 + case 0x400029E: // DIV_DENOM B6 + case 0x400029F: // DIV_DENOM B7 + DIV_DENOM = SetByteIn(DIV_DENOM, val, addr & 7); + Divide(); + break; + + case 0x40002B0: // SQRTCNT B0 + SqrtUse64BitInput = BitTest(val, 0); + TakeSquareRoot(); + break; + case 0x40002B1: // SQRTCNT B0 + break; + + case 0x40002B8: // SQRT_PARAM B0 + case 0x40002B9: // SQRT_PARAM B1 + case 0x40002BA: // SQRT_PARAM B2 + case 0x40002BB: // SQRT_PARAM B3 + case 0x40002BC: // SQRT_PARAM B4 + case 0x40002BD: // SQRT_PARAM B5 + case 0x40002BE: // SQRT_PARAM B6 + case 0x40002BF: // SQRT_PARAM B7 + SQRT_PARAM = SetByteIn(SQRT_PARAM, val, addr & 7); + TakeSquareRoot(); + return; + + // default: + // throw new NotImplementedException("Write to DS math @ " + Util.Hex(addr, 8)); + } + } + + public void Divide() + { + DividedByZero = DIV_DENOM == 0; + + switch (DivisionMode) + { + case 0: // 32bit / 32bit + if ((int)DIV_NUMER == int.MinValue && (int)DIV_DENOM == -1) // Overflow + { + DIV_RESULT = (long)(int)DIV_NUMER ^ (0xFFFFFFFFL << 32); + DIVREM_RESULT = 0; + } + else if ((int)DIV_DENOM != 0) + { + DIV_RESULT = (int)DIV_NUMER / (int)DIV_DENOM; + DIVREM_RESULT = (int)DIV_NUMER % (int)DIV_DENOM; + } + else // Division by 0 + { + DIV_RESULT = (((int)DIV_NUMER < 0) ? 1 : -1) ^ (0xFFFFFFFFL << 32); + DIVREM_RESULT = (int)DIV_NUMER; + } + break; + case 3: + case 1: // 64bit / 32bit + if (DIV_NUMER == long.MinValue && (int)DIV_DENOM == -1) // Overflow + { + DIV_RESULT = DIV_NUMER; + DIVREM_RESULT = 0; + } + else if ((int)DIV_DENOM != 0) + { + DIV_RESULT = DIV_NUMER / (int)DIV_DENOM; + DIVREM_RESULT = DIV_NUMER % (int)DIV_DENOM; + } + else // Division by 0 + { + DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1; + DIVREM_RESULT = DIV_NUMER; + } + break; + case 2: // 64bit / 64bit + if (DIV_NUMER == long.MinValue && DIV_DENOM == -1) // Overflow + { + DIV_RESULT = DIV_NUMER; + DIVREM_RESULT = 0; + } + else if (DIV_DENOM != 0) + { + DIV_RESULT = DIV_NUMER / DIV_DENOM; + DIVREM_RESULT = DIV_NUMER % DIV_DENOM; + } + else // Division by 0 + { + DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1; + DIVREM_RESULT = DIV_NUMER; + } + break; + } + + + // Console.WriteLine("Divison Mode: " + DivisionMode); + // Console.WriteLine("Numerator : " + DIV_NUMER); + // Console.WriteLine("Demoninator: " + DIV_DENOM); + // Console.WriteLine("Result : " + DIV_RESULT); + // Console.WriteLine("Remainder : " + DIVREM_RESULT); + } + + public void TakeSquareRoot() + { + if (SqrtUse64BitInput) + { + ulong val = SQRT_PARAM; + + uint final = 0; + ulong rem = 0; + uint prod = 0; + + const uint nbits = 32; + const int topShift = 62; + + for (int i = 0; i < nbits; i++) + { + rem = (rem << 2) + ((val >> topShift) & 0x3); + val <<= 2; + final <<= 1; + + prod = (final << 1) + 1; + if (rem >= prod) + { + rem -= prod; + final++; + } + } + + SQRT_RESULT = final; + } + else + { + SQRT_RESULT = (uint)Math.Sqrt((uint)SQRT_PARAM); + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Nds9Math.cs.meta b/Assets/emulator/Nds9Math.cs.meta new file mode 100644 index 0000000..d93fc39 --- /dev/null +++ b/Assets/emulator/Nds9Math.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 969688026ca7e194dbc2b0acc79a84d9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/NdsAudio.cs b/Assets/emulator/NdsAudio.cs new file mode 100644 index 0000000..29ba13c --- /dev/null +++ b/Assets/emulator/NdsAudio.cs @@ -0,0 +1,525 @@ +using static OptimeGBA.Bits; +using static OptimeGBA.MemoryUtil; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Numerics; +namespace OptimeGBA +{ + public class CustomSample + { + public short[] Data; // In PCM16 + public uint LoopPoint; // In samples + public uint RepeatMode; + + public CustomSample(short[] data, uint loopPoint, uint repeatMode) + { + Data = data; + LoopPoint = loopPoint; + RepeatMode = repeatMode; + } + } + + public class AudioChannelNds + { + // SOUNDxCNT + public uint Volume; + public byte VolumeDiv; + public bool Hold; + public byte Pan; + public byte PulseDuty; + public byte RepeatMode; + public byte Format; + public bool Playing; + + public uint SOUNDSAD; + public uint SOUNDTMR; + public uint SOUNDPNT; + public uint SOUNDLEN; + + public uint SamplePos; + public uint Timer; + + public uint Interval; + public int CurrentValue; + public int AdpcmIndex; + public int AdpcmLoopValue; + public int AdpcmLoopIndex; + public uint AdpcmLoopCurrentData; + public uint CurrentData; + + public bool DebugEnable = true; + public uint DebugAdpcmSaved; + public uint DebugAdpcmRestored; + + public long DebugStartTicks; + } + + public class NdsAudio + { + Nds Nds; + + public NdsAudio(Nds nds) + { + Nds = nds; + + for (uint i = 0; i < 16; i++) + { + Channels[i] = new AudioChannelNds(); + } + + Sample(0); + } + + public AudioChannelNds[] Channels = new AudioChannelNds[16]; + + public const int SampleRate = 32768; + + public bool EnableBlipBufResampling = true; + public BlipBuf BlipBuf = new BlipBuf(32, true, 16); + + public bool Record = false; + public WavWriter WavWriter = new WavWriter(SampleRate); + public WavWriter WavWriterSinc = new WavWriter(SampleRate); + + public int SampleTimer = 0; + + public const uint SampleBufferMax = 256; + public short[] SampleBuffer = new short[SampleBufferMax]; + public uint SampleBufferPos = 0; + + public static sbyte[] IndexTable = { -1, -1, -1, -1, 2, 4, 6, 8 }; + public static short[] AdpcmTable = { + 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010, 0x0011, 0x0013, 0x0015, + 0x0017, 0x0019, 0x001C, 0x001F, 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, + 0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, 0x009D, 0x00AD, 0x00BE, 0x00D1, + 0x00E6, 0x00FD, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292, + 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, 0x0610, 0x06AB, 0x0756, 0x0812, + 0x08E0, 0x09C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954, + 0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E, + 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF + }; + + // SOUNDCNT + public byte MasterVolume; + public uint LeftOutputFrom; + public uint RightOutputFrom; + public bool Ch1ToMixer; + public bool Ch3ToMixer; + public bool MasterEnable; + + // SOUNDBIAS + public ushort SOUNDBIAS; + + // SNDCAPCNT + byte SNDCAP0CNT; + byte SNDCAP1CNT; + + public byte ReadHwio8(uint addr) + { + byte val = 0; + + switch (addr) + { + case 0x4000500: // SOUNDCNT B0 + val |= (byte)(MasterVolume & 0x7FU); + break; + case 0x4000501: // SOUNDCNT B1 + val |= (byte)((LeftOutputFrom & 0b11) << 0); + val |= (byte)((RightOutputFrom & 0b11) << 2); + if (Ch1ToMixer) val = BitSet(val, 4); + if (Ch3ToMixer) val = BitSet(val, 5); + if (MasterEnable) val = BitSet(val, 7); + break; + + case 0x4000504: + return (byte)(SOUNDBIAS >> 0); + case 0x4000505: + return (byte)(SOUNDBIAS >> 8); + + case 0x4000508: + return SNDCAP0CNT; + case 0x4000509: + return SNDCAP1CNT; + } + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000500: // SOUNDCNT B0 + MasterVolume = (byte)(val & 0x7FU); + break; + case 0x4000501: // SOUNDCNT B1 + LeftOutputFrom = (byte)((val >> 0) & 0b11); + RightOutputFrom = (byte)((val >> 2) & 0b11); + Ch1ToMixer = BitTest(val, 4); + Ch3ToMixer = BitTest(val, 5); + MasterEnable = BitTest(val, 7); + break; + + case 0x4000504: + SOUNDBIAS &= 0xFF00; + SOUNDBIAS |= (ushort)(val << 0); + break; + case 0x4000505: + SOUNDBIAS &= 0x00FF; + SOUNDBIAS |= (ushort)(val << 8); + break; + + case 0x4000508: + SNDCAP0CNT = val; + break; + case 0x4000509: + SNDCAP1CNT = val; + break; + } + } + + public byte ReadHwio8Channels(uint addr) + { + var c = Channels[(addr >> 4) & 0xF]; + + byte val = 0; + + switch (addr & 0xF) + { + case 0x0: + val |= (byte)(c.Volume & 0x7F); + break; + case 0x1: + val |= c.VolumeDiv; + if (c.Hold) val = BitSet(val, 7); + break; + case 0x2: + val |= c.Pan; + break; + case 0x3: + val |= c.PulseDuty; + val |= (byte)(c.RepeatMode << 3); + val |= (byte)(c.Format << 5); + if (c.Playing) val = BitSet(val, 7); + break; + } + + return val; + } + + public void WriteHwio8Channels(uint addr, byte val) + { + var c = Channels[(addr >> 4) & 0xF]; + + switch (addr & 0xF) + { + case 0x0: + c.Volume = (byte)(val & 0x7F); + break; + case 0x1: + c.VolumeDiv = (byte)(val & 3); + c.Hold = BitTest(val, 7); + break; + case 0x2: + c.Pan = (byte)(val & 0x7F); + break; + case 0x3: + c.PulseDuty = (byte)(val & 7); + c.RepeatMode = (byte)((val >> 3) & 3); + c.Format = (byte)((val >> 5) & 3); + if (!c.Playing && BitTest(val, 7)) + { + StartChannel(c); + } + c.Playing = BitTest(val, 7); + break; + + case 0x4: + case 0x5: + case 0x6: + case 0x7: + Console.WriteLine(Util.Hex(Nds.Cpu7.GetCurrentInstrAddr(), 8)); + // Console.WriteLine(Nds.MemoryControl.SharedRamControl); + + c.SOUNDSAD = SetByteIn(c.SOUNDSAD, val, addr & 3) & 0x7FFFFFC; + + if (c.Playing) + { + StartChannel(c); + } + break; + case 0x8: + case 0x9: + c.SOUNDTMR = SetByteIn(c.SOUNDTMR, val, addr & 1); + c.Interval = 2 * (0x10000 - c.SOUNDTMR); + break; + case 0xA: + case 0xB: + c.SOUNDPNT = SetByteIn(c.SOUNDPNT, val, addr & 1); + break; + case 0xC: + case 0xD: + case 0xE: + case 0xF: + c.SOUNDLEN = SetByteIn(c.SOUNDLEN, val, addr & 3) & 0x3FFFFF; + break; + } + } + + public void StartChannel(AudioChannelNds c) + { + c.SamplePos = 0; + c.Timer = 0; + c.CurrentValue = 0; + + c.DebugStartTicks = Nds.Scheduler.CurrentTicks; + } + + public void Sample(long cyclesLate) + { + long left = 0; + long right = 0; + + for (int i = 0; i < 16; i++) + { + var c = Channels[i]; + + if (c.Playing) + { + c.Timer += 1024; + while (c.Timer >= c.Interval && c.Interval != 0) + { + c.Timer -= c.Interval; + + // Advance sample + switch (c.Format) + { + case 0: // PCM8 + if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 4) + { + switch (c.RepeatMode) + { + case 1: // Infinite + c.SamplePos = c.SOUNDPNT * 4; + break; + case 2: // One-shot + c.Playing = false; + if (!c.Hold) + { + c.CurrentValue = 0; + } + break; + } + } + + if ((c.SamplePos & 3) == 0) + { + c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos); + } + + c.CurrentValue = (short)((byte)c.CurrentData << 8); + c.CurrentData >>= 8; + + c.SamplePos++; + break; + case 1: // PCM16 + if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 2) + { + switch (c.RepeatMode) + { + case 1: // Infinite + c.SamplePos = c.SOUNDPNT * 2; + break; + case 2: // One-shot + c.Playing = false; + if (!c.Hold) + { + c.CurrentValue = 0; + } + break; + } + } + + if ((c.SamplePos & 1) == 0) + { + c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos * 2); + } + + c.CurrentValue = (short)c.CurrentData; + c.CurrentData >>= 16; + + c.SamplePos++; + break; + case 2: // IMA-ADPCM + if ((c.SamplePos & 7) == 0) + { + c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos / 2); + // ADPCM header + if (c.SamplePos == 0) + { + c.CurrentValue = (short)c.CurrentData; + // Console.WriteLine("header set " + x++); + // Console.WriteLine("interval: " + Util.Hex(c.Interval, 8)); + c.AdpcmIndex = Math.Clamp((int)(c.CurrentData >> 16), 0, 88); + } + // Console.WriteLine("addr: " + Util.Hex(c.Source, 8)); + } + if (c.SamplePos > 7) + { + // End of sound, loop or stop + if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 8) + { + switch (c.RepeatMode) + { + case 1: // Infinite + c.SamplePos = c.SOUNDPNT * 8; + c.CurrentValue = c.AdpcmLoopValue; + c.AdpcmIndex = c.AdpcmLoopIndex; + c.CurrentData = c.AdpcmLoopCurrentData; + // Console.WriteLine($"Ch{i}: Loaded at " + c.SampleNum); + + c.DebugAdpcmRestored = c.SamplePos; + break; + case 2: // One-shot + c.Playing = false; + if (!c.Hold) + { + c.CurrentValue = 0; + } + break; + } + } + else + { + byte data = (byte)(c.CurrentData & 0xF); + + short tableVal = AdpcmTable[c.AdpcmIndex]; + int diff = tableVal / 8; + if ((data & 1) != 0) diff += tableVal / 4; + if ((data & 2) != 0) diff += tableVal / 2; + if ((data & 4) != 0) diff += tableVal / 1; + + if ((data & 8) == 8) + { + c.CurrentValue = Math.Max((int)c.CurrentValue - diff, -0x7FFF); + } + else + { + c.CurrentValue = Math.Min((int)c.CurrentValue + diff, 0x7FFF); + } + c.AdpcmIndex = Math.Clamp(c.AdpcmIndex + IndexTable[data & 7], 0, 88); + + c.CurrentData >>= 4; + + // Save value and ADPCM table index for loop + if (c.SamplePos == c.SOUNDPNT * 8) + { + c.AdpcmLoopValue = c.CurrentValue; + c.AdpcmLoopIndex = c.AdpcmIndex; + c.AdpcmLoopCurrentData = c.CurrentData; + + c.DebugAdpcmSaved = c.SamplePos; + // Console.WriteLine($"Ch{i}: Saved at " + c.SampleNum); + } + } + } + c.SamplePos++; + break; + case 3: // Pulse / Noise + if (((c.SamplePos ^ 7) & 7) <= c.PulseDuty) + { + c.CurrentValue = -0x7FFF; + } + else + { + c.CurrentValue = 0x7FFF; + } + c.SamplePos++; + break; + } + + if (EnableBlipBufResampling) + { + long timeTicks = Nds.Scheduler.CurrentTicks - cyclesLate + 1024 - (c.Timer % 1024); + double timeSec = (double)timeTicks / 33513982D; + double timeSample = (double)timeTicks / (33513982D / (double)SampleRate); + + if (c.DebugEnable) + { + uint effectiveVol = c.Volume; + if (effectiveVol == 127) effectiveVol++; + long leftCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10; + long rightCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10; + + BlipBuf.SetValue(i, timeSample, leftCh, rightCh); + } + else + { + BlipBuf.SetValue(i, timeSample, 0, 0); + } + } + } + + if (c.DebugEnable) + { + uint effectiveVol = c.Volume; + if (effectiveVol == 127) effectiveVol++; + left += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10; + right += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10; + } + } + } + + // Decimate samples to 32768 hz + // Since 33513982 hz / 1024 ≅ 32728.498 hz + SampleTimer += SampleRate * 1024; + while (SampleTimer >= 33513982) + { + SampleTimer -= 33513982; + + BlipBuf.ReadOutSample(); + + // 28 bits now, after mixing all channels + // add master volume to get 35 bits + // add + // strip 19 to get 16 bits for our short output + uint effectiveMasterVol = MasterVolume; + if (effectiveMasterVol == 127) effectiveMasterVol++; + + short leftFinalSinc = (short)(((long)BlipBuf.CurrentValL * effectiveMasterVol) >> 16); + short rightFinalSinc = (short)(((long)BlipBuf.CurrentValR * effectiveMasterVol) >> 16); + + short leftFinal = (short)((left * effectiveMasterVol) >> 16); + short rightFinal = (short)((right * effectiveMasterVol) >> 16); + + if (EnableBlipBufResampling) + { + SampleBuffer[SampleBufferPos++] = leftFinalSinc; + SampleBuffer[SampleBufferPos++] = rightFinalSinc; + } + else + { + SampleBuffer[SampleBufferPos++] = leftFinal; + SampleBuffer[SampleBufferPos++] = rightFinal; + } + + if (Record) + { + WavWriterSinc.AddSample(leftFinalSinc, rightFinalSinc); + WavWriter.AddSample(leftFinal, rightFinal); + } + + if (SampleBufferPos >= SampleBufferMax) + { + SampleBufferPos = 0; + + Nds.Provider.AudioCallback(SampleBuffer); + } + } + + Nds.Scheduler.AddEventRelative(SchedulerId.ApuSample, 1024 - cyclesLate, Sample); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/NdsAudio.cs.meta b/Assets/emulator/NdsAudio.cs.meta new file mode 100644 index 0000000..78799d2 --- /dev/null +++ b/Assets/emulator/NdsAudio.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aac09fde41176a14b888ed3e2d5d4676 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Ppu.cs b/Assets/emulator/Ppu.cs new file mode 100644 index 0000000..6ba7c6b --- /dev/null +++ b/Assets/emulator/Ppu.cs @@ -0,0 +1,261 @@ +using static OptimeGBA.Bits; +using System.Runtime.InteropServices; + +namespace OptimeGBA +{ + public enum BackgroundMode + { + Char, + Display3D, + Affine, + Extended, + Large, + + // Extended + Affine16BitBgMapEntries, + Affine256ColorBitmap, + AffineFullColorBitmap, + } + public sealed class Background + { + bool Nds; + + public Background(bool nds, byte id) + { + Nds = nds; + Id = id; + } + + byte[] BGCNTValue = new byte[2]; + + // BGCNT + public byte Priority = 0; + public uint CharBaseBlock = 0; + public bool EnableMosaic = false; + public bool Use8BitColor = false; + public uint MapBaseBlock = 0; + public bool OverflowWrap = false; + public uint ScreenSize = 0; + + // BGCNT NDS + public bool AffineBitmap; + public bool AffineBitmapFullColor; + + // BGH/VOFS + public uint HorizontalOffset; + public uint VerticalOffset; + + public byte Id; + + public uint RefPointX; + public uint RefPointY; + + public short AffineA; + public short AffineB; + public short AffineC; + public short AffineD; + + public int AffinePosX; + public int AffinePosY; + + // Set by PrepareBackgroundAndWindow() and used by RenderBgModes() + public BackgroundMode Mode; + + public byte ReadBGCNT(uint addr) + { + switch (addr) + { + case 0x00: // BGCNT B0 + return BGCNTValue[0]; + case 0x01: // BGCNT B1 + return BGCNTValue[1]; + } + return 0; + } + + public void WriteBGCNT(uint addr, byte val) + { + switch (addr) + { + case 0x00: // BGCNT B0 + Priority = (byte)((val >> 0) & 0b11); + + // These bits overlay other bits on NDS + AffineBitmap = BitTest(val, 7); + AffineBitmapFullColor = BitTest(val, 2); + + EnableMosaic = BitTest(val, 6); + if (!Nds) + { + CharBaseBlock = (uint)(val >> 2) & 0b11; + } + else + { + CharBaseBlock = (uint)(val >> 2) & 0b1111; + } + EnableMosaic = BitTest(val, 6); + Use8BitColor = BitTest(val, 7); + + BGCNTValue[0] = val; + break; + case 0x01: // BGCNT B1 + MapBaseBlock = (uint)(val >> 0) & 0b11111; + OverflowWrap = BitTest(val, 5); + ScreenSize = (uint)(val >> 6) & 0b11; + + BGCNTValue[1] = val; + break; + } + } + + public void WriteBGOFS(uint addr, byte val) + { + switch (addr) + { + case 0x0: // BGHOFS B0 + HorizontalOffset &= ~0x0FFu; + HorizontalOffset |= (uint)((val << 0) & 0x0FFu); + break; + case 0x1: // BGHOFS B1 + HorizontalOffset &= ~0x100u; + HorizontalOffset |= (uint)((val << 8) & 0x100u); + break; + + case 0x2: // BGVOFS B0 + VerticalOffset &= ~0x0FFu; + VerticalOffset |= (uint)((val << 0) & 0x0FFu); + break; + case 0x3: // BGVOFS B1 + VerticalOffset &= ~0x100u; + VerticalOffset |= (uint)((val << 8) & 0x100u); + break; + } + } + + public void WriteBGXY(uint addr, byte val) + { + byte offset = (byte)((addr & 3) * 8); + switch (addr) + { + case 0x0: // BGX_L + case 0x1: // BGX_L + case 0x2: // BGX_H + case 0x3: // BGX_H + RefPointX &= ~(0xFFu << offset); + RefPointX |= (uint)(val << offset); + break; + + case 0x4: // BGY_L + case 0x5: // BGY_L + case 0x6: // BGY_H + case 0x7: // BGY_H + RefPointY &= ~(0xFFu << offset); + RefPointY |= (uint)(val << offset); + break; + } + + CopyAffineParams(); + } + + public void CopyAffineParams() + { + // also sign extend + AffinePosX = ((int)RefPointX << 4) >> 4; + AffinePosY = ((int)RefPointY << 4) >> 4; + } + + public void WriteBGPX(uint addr, byte val) + { + byte offset = (byte)((addr & 1) * 8); + switch (addr) + { + case 0x0: // BGPA B0 + case 0x1: // BGPA B1 + AffineA &= (short)~(0xFFu << offset); + AffineA |= (short)(val << offset); + break; + case 0x2: // BGPB B0 + case 0x3: // BGPB B1 + AffineB &= (short)~(0xFFu << offset); + AffineB |= (short)(val << offset); + break; + + case 0x4: // BGPC B0 + case 0x5: // BGPC B1 + AffineC &= (short)~(0xFFu << offset); + AffineC |= (short)(val << offset); + break; + case 0x6: // BGPD B0 + case 0x7: // BGPD B1 + AffineD &= (short)~(0xFFu << offset); + AffineD |= (short)(val << offset); + break; + } + } + + // Metadata used for rendering + public ushort GetMeta() + { + return (ushort)((Priority << 8) | (1 << Id)); + } + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + public struct ObjPixel + { + public ushort Color; + public byte PaletteIndex; + public byte Priority; + public ObjMode Mode; + + public ObjPixel(ushort color, byte paletteIndex, byte priority, ObjMode transparent) + { + Color = color; + PaletteIndex = paletteIndex; + Priority = priority; + Mode = transparent; + } + } + + public enum ObjShape + { + Square = 0, + Horizontal = 1, + Vertical = 2, + } + + public enum ObjMode : byte + { + Normal = 0, + Translucent = 1, + ObjWindow = 2, + } + + public enum BlendEffect + { + None = 0, + Blend = 1, + Lighten = 2, + Darken = 3, + } + + public enum BlendFlag : byte + { + Bg0 = 1 << 0, + Bg1 = 1 << 1, + Bg2 = 1 << 2, + Bg3 = 1 << 3, + Obj = 1 << 4, + Backdrop = 1 << 5, + } + + public enum WindowFlag : byte + { + Bg0 = 1 << 0, + Bg1 = 1 << 1, + Bg2 = 1 << 2, + Bg3 = 1 << 3, + Obj = 1 << 4, + ColorMath = 1 << 5, + } +} \ No newline at end of file diff --git a/Assets/emulator/Ppu.cs.meta b/Assets/emulator/Ppu.cs.meta new file mode 100644 index 0000000..625f686 --- /dev/null +++ b/Assets/emulator/Ppu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01c48e13c41f22a45ab867df39d272b6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/PpuGba.cs b/Assets/emulator/PpuGba.cs new file mode 100644 index 0000000..19b732c --- /dev/null +++ b/Assets/emulator/PpuGba.cs @@ -0,0 +1,296 @@ +using static OptimeGBA.Bits; +using System.Runtime.CompilerServices; +using static OptimeGBA.PpuRenderer; +using System; + +namespace OptimeGBA +{ + public sealed unsafe class PpuGba + { + Gba Gba; + Scheduler Scheduler; + + public PpuRenderer Renderer; + + public PpuGba(Gba gba, Scheduler scheduler) + { + Gba = gba; + Scheduler = scheduler; + Renderer = new PpuRenderer(null, 240, 160); + + Scheduler.AddEventRelative(SchedulerId.Ppu, 960, EndDrawingToHblank); + + /* + ⠀⠀⠀⡯⡯⡾⠝⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢊⠘⡮⣣⠪⠢⡑⡌ + ⠀⠀⠀⠟⠝⠈⠀⠀⠀⠡⠀⠠⢈⠠⢐⢠⢂⢔⣐⢄⡂⢔⠀⡁⢉⠸⢨⢑⠕⡌ + ⠀⠀⡀⠁⠀⠀⠀⡀⢂⠡⠈⡔⣕⢮⣳⢯⣿⣻⣟⣯⣯⢷⣫⣆⡂⠀⠀⢐⠑⡌ + ⢀⠠⠐⠈⠀⢀⢂⠢⡂⠕⡁⣝⢮⣳⢽⡽⣾⣻⣿⣯⡯⣟⣞⢾⢜⢆⠀⡀⠀⠪ + ⣬⠂⠀⠀⢀⢂⢪⠨⢂⠥⣺⡪⣗⢗⣽⢽⡯⣿⣽⣷⢿⡽⡾⡽⣝⢎⠀⠀⠀⢡ + ⣿⠀⠀⠀⢂⠢⢂⢥⢱⡹⣪⢞⡵⣻⡪⡯⡯⣟⡾⣿⣻⡽⣯⡻⣪⠧⠑⠀⠁⢐ + ⣿⠀⠀⠀⠢⢑⠠⠑⠕⡝⡎⡗⡝⡎⣞⢽⡹⣕⢯⢻⠹⡹⢚⠝⡷⡽⡨⠀⠀⢔ + ⣿⡯⠀⢈⠈⢄⠂⠂⠐⠀⠌⠠⢑⠱⡱⡱⡑⢔⠁⠀⡀⠐⠐⠐⡡⡹⣪⠀⠀⢘ + ⣿⣽⠀⡀⡊⠀⠐⠨⠈⡁⠂⢈⠠⡱⡽⣷⡑⠁⠠⠑⠀⢉⢇⣤⢘⣪⢽⠀⢌⢎ + ⣿⢾⠀⢌⠌⠀⡁⠢⠂⠐⡀⠀⢀⢳⢽⣽⡺⣨⢄⣑⢉⢃⢭⡲⣕⡭⣹⠠⢐⢗ + ⣿⡗⠀⠢⠡⡱⡸⣔⢵⢱⢸⠈⠀⡪⣳⣳⢹⢜⡵⣱⢱⡱⣳⡹⣵⣻⢔⢅⢬⡷ + ⣷⡇⡂⠡⡑⢕⢕⠕⡑⠡⢂⢊⢐⢕⡝⡮⡧⡳⣝⢴⡐⣁⠃⡫⡒⣕⢏⡮⣷⡟ + ⣷⣻⣅⠑⢌⠢⠁⢐⠠⠑⡐⠐⠌⡪⠮⡫⠪⡪⡪⣺⢸⠰⠡⠠⠐⢱⠨⡪⡪⡰ + ⣯⢷⣟⣇⡂⡂⡌⡀⠀⠁⡂⠅⠂⠀⡑⡄⢇⠇⢝⡨⡠⡁⢐⠠⢀⢪⡐⡜⡪⡊ + ⣿⢽⡾⢹⡄⠕⡅⢇⠂⠑⣴⡬⣬⣬⣆⢮⣦⣷⣵⣷⡗⢃⢮⠱⡸⢰⢱⢸⢨⢌ + ⣯⢯⣟⠸⣳⡅⠜⠔⡌⡐⠈⠻⠟⣿⢿⣿⣿⠿⡻⣃⠢⣱⡳⡱⡩⢢⠣⡃⠢⠁ + ⡯⣟⣞⡇⡿⣽⡪⡘⡰⠨⢐⢀⠢⢢⢄⢤⣰⠼⡾⢕⢕⡵⣝⠎⢌⢪⠪⡘⡌⠀ + ⡯⣳⠯⠚⢊⠡⡂⢂⠨⠊⠔⡑⠬⡸⣘⢬⢪⣪⡺⡼⣕⢯⢞⢕⢝⠎⢻⢼⣀⠀ + ⠁⡂⠔⡁⡢⠣⢀⠢⠀⠅⠱⡐⡱⡘⡔⡕⡕⣲⡹⣎⡮⡏⡑⢜⢼⡱⢩⣗⣯⣟ + ⢀⢂⢑⠀⡂⡃⠅⠊⢄⢑⠠⠑⢕⢕⢝⢮⢺⢕⢟⢮⢊⢢⢱⢄⠃⣇⣞⢞⣞⢾ + ⢀⠢⡑⡀⢂⢊⠠⠁⡂⡐⠀⠅⡈⠪⠪⠪⠣⠫⠑⡁⢔⠕⣜⣜⢦⡰⡎⡯⡾⡽ + */ + if (Gba.Provider.BootBios) + { + PrideMode = true; + // 250 frames + Scheduler.AddEventRelative(SchedulerId.None, 70224000, DisablePrideMode); + // 120 frames + Scheduler.AddEventRelative(SchedulerId.None, 33707520, EnablePrideModeLayer2); + } + } + + public byte[] Vram = new byte[98304]; + + public long ScanlineStartCycles; + + public bool PrideMode = false; + public bool PrideModeLayer2 = false; + + public ushort DISPCNTValue; + + // DISPSTAT + public bool VCounterMatch; + public bool VBlankIrqEnable; + public bool HBlankIrqEnable; + public bool VCounterIrqEnable; + public byte VCountSetting; + + // State + public uint VCount; + + public void DisablePrideMode(long cyclesLate) + { + PrideMode = false; + } + + public void EnablePrideModeLayer2(long cyclesLate) + { + PrideModeLayer2 = true; + } + + public long GetScanlineCycles() + { + return Scheduler.CurrentTicks - ScanlineStartCycles; + } + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000000: // DISPCNT B0 + return (byte)(DISPCNTValue >> 0); + case 0x4000001: // DISPCNT B1 + return (byte)(DISPCNTValue >> 8); + + case 0x4000004: // DISPSTAT B0 + // Vblank flag is set in scanlines 160-226, not including 227 for some reason + if (VCount >= 160 && VCount <= 226) val = BitSet(val, 0); + // Hblank flag is set at cycle 1006, not cycle 960 + if (GetScanlineCycles() >= 1006) val = BitSet(val, 1); + if (VCounterMatch) val = BitSet(val, 2); + if (VBlankIrqEnable) val = BitSet(val, 3); + if (HBlankIrqEnable) val = BitSet(val, 4); + if (VCounterIrqEnable) val = BitSet(val, 5); + break; + case 0x4000005: // DISPSTAT B1 + val |= VCountSetting; + break; + + case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS + val |= (byte)VCount; + break; + case 0x4000007: + return 0; + + default: + return Renderer.ReadHwio8(addr & 0xFF); + } + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000000: // DISPCNT B0 + Renderer.BgMode = (uint)(val & 0b111); + Renderer.CgbMode = BitTest(val, 3); + Renderer.DisplayFrameSelect = BitTest(val, 4); + Renderer.HBlankIntervalFree = BitTest(val, 5); + Renderer.ObjCharOneDimensional = BitTest(val, 6); + Renderer.ForcedBlank = BitTest(val, 7); + + DISPCNTValue &= 0xFF00; + DISPCNTValue |= (ushort)(val << 0); + + Renderer.BackgroundSettingsDirty = true; + return; + case 0x4000001: // DISPCNT B1 + Renderer.ScreenDisplayBg[0] = BitTest(val, 8 - 8); + Renderer.ScreenDisplayBg[1] = BitTest(val, 9 - 8); + Renderer.ScreenDisplayBg[2] = BitTest(val, 10 - 8); + Renderer.ScreenDisplayBg[3] = BitTest(val, 11 - 8); + Renderer.ScreenDisplayObj = BitTest(val, 12 - 8); + Renderer.Window0DisplayFlag = BitTest(val, 13 - 8); + Renderer.Window1DisplayFlag = BitTest(val, 14 - 8); + Renderer.ObjWindowDisplayFlag = BitTest(val, 15 - 8); + Renderer.AnyWindowEnabled = (val & 0b11100000) != 0; + + DISPCNTValue &= 0x00FF; + DISPCNTValue |= (ushort)(val << 8); + + Renderer.BackgroundSettingsDirty = true; + return; + + case 0x4000004: // DISPSTAT B0 + VBlankIrqEnable = BitTest(val, 3); + HBlankIrqEnable = BitTest(val, 4); + VCounterIrqEnable = BitTest(val, 5); + return; + case 0x4000005: // DISPSTAT B1 + VCountSetting = val; + return; + + default: + Renderer.WriteHwio8(addr & 0xFF, val); + return; + } + + } + + public void EndDrawingToHblank(long cyclesLate) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank); + + if (HBlankIrqEnable) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank); + } + + if (Renderer.DebugEnableRendering) + { + Renderer.RenderScanlineGba(VCount, Vram); + } + Renderer.IncrementMosaicCounters(); + + Gba.Dma.RepeatHblank(); + } + + public void EndVblankToHblank(long cyclesLate) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 272 - cyclesLate, EndHblank); + + if (HBlankIrqEnable) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.HBlank); + } + } + + public void EndHblank(long cyclesLate) + { + ScanlineStartCycles = Scheduler.CurrentTicks; + + if (VCount != 227) + { + VCount++; + + if (VCount > 159) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndVblankToHblank); + + if (VCount == 160) + { +#if DS_RESOLUTION + while (VCount < HEIGHT) { + RenderScanline(); + Renderer.IncrementMosaicCounters(); + VCount++; + } + VCount = 160; +#endif + + Gba.Dma.RepeatVblank(); + + Renderer.RunVblankOperations(); + + if (VBlankIrqEnable) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.VBlank); + } + + Renderer.TotalFrames++; + if (Renderer.DebugEnableRendering) Renderer.SwapBuffers(); + + Renderer.RenderingDone = true; + } + } + else + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank); + } + } + else + { + if (PrideMode) + { + uint objE0 = 8 * 3; + uint objE1 = 8 * 19; + uint objM0 = 8 * 4; + uint objM1 = 8 * 20; + + for (uint i = 0; i < 6; i++) + { + Renderer.Oam[objE0++] = 0; + Renderer.Oam[objE1++] = 0; + } + + Renderer.Oam[objM0 + 4] = 68; + Renderer.Oam[objM0 + 5] |= 2; + + if (PrideModeLayer2) + { + Renderer.Oam[objM1 + 4] = 68; + Renderer.Oam[objM1 + 5] |= 2; + } + } + + VCount = 0; + VCounterMatch = VCount == VCountSetting; + if (VCounterMatch && VCounterIrqEnable) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch); + } + Scheduler.AddEventRelative(SchedulerId.Ppu, 960 - cyclesLate, EndDrawingToHblank); + + // Pre-render sprites for line zero + fixed (byte* vram = Vram) + { + if (Renderer.DebugEnableObj && Renderer.ScreenDisplayObj) Renderer.RenderObjs(0, vram); + } + } + + VCounterMatch = VCount == VCountSetting; + + if (VCounterMatch && VCounterIrqEnable) + { + Gba.HwControl.FlagInterrupt((uint)InterruptGba.VCounterMatch); + } + } + } +} diff --git a/Assets/emulator/PpuGba.cs.meta b/Assets/emulator/PpuGba.cs.meta new file mode 100644 index 0000000..7a8e375 --- /dev/null +++ b/Assets/emulator/PpuGba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03dc9232a0ce9c44ea7bda048c1ff906 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/PpuNds.cs b/Assets/emulator/PpuNds.cs new file mode 100644 index 0000000..8254dfd --- /dev/null +++ b/Assets/emulator/PpuNds.cs @@ -0,0 +1,925 @@ +using static OptimeGBA.Bits; +using static OptimeGBA.MemoryUtil; +using System.Runtime.CompilerServices; +using System; + +namespace OptimeGBA +{ + public sealed unsafe class PpuNds + { + Nds Nds; + Scheduler Scheduler; + + public PpuRenderer[] Renderers; + + public PpuNds(Nds nds, Scheduler scheduler) + { + Nds = nds; + Scheduler = scheduler; + Renderers = new PpuRenderer[] { + new PpuRenderer(nds, 256, 192), + new PpuRenderer(nds, 256, 192) + }; + + Scheduler.AddEventRelative(SchedulerId.Ppu, 1536, EndDrawingToHblank); + } + + // Raw VRAM Blocks + public byte[] VramA = new byte[131072]; + public byte[] VramB = new byte[131072]; + public byte[] VramC = new byte[131072]; + public byte[] VramD = new byte[131072]; + public byte[] VramE = new byte[65536]; + public byte[] VramF = new byte[16384]; + public byte[] VramG = new byte[16384]; + public byte[] VramH = new byte[32768]; + public byte[] VramI = new byte[16384]; + + // Built arrays (Passed to PpuRenderer for rendering) + public byte[] VramLcdc = new byte[671744]; + public byte[] VramBgA = new byte[524288]; + public byte[] VramObjA = new byte[262144]; + public byte[] VramBgB = new byte[131072]; + public byte[] VramObjB = new byte[131072]; + + public bool DebugDisableVramUpdates; + + public byte ReadVram8Arm9(uint addr) + { + switch (addr & 0xFFE00000) + { + case 0x06000000: // Engine A BG VRAM + return ReadVram8Arm9BgA(addr); + case 0x06200000: // Engine B BG VRAM + return ReadVram8Arm9BgB(addr); + case 0x06400000: // Engine A OBJ VRAM + return ReadVram8Arm9ObjA(addr); + case 0x06600000: // Engine B OBJ VRAM + return ReadVram8Arm9ObjB(addr); + case 0x06800000: // LCDC VRAM + return ReadVram8Arm9Lcdc(addr); + } + + return 0; + } + + public byte ReadVram8Arm9BgA(uint addr) + { + addr &= 0x1FFFFF; + byte val = 0; + uint offs = Nds.MemoryControl.GetOffset(0) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1)) + { + val |= VramA[addr & 0x1FFFF]; + } + offs = Nds.MemoryControl.GetOffset(1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1)) + { + val |= VramB[addr & 0x1FFFF]; + } + offs = Nds.MemoryControl.GetOffset(2) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1)) + { + val |= VramC[addr & 0x1FFFF]; + } + offs = Nds.MemoryControl.GetOffset(3) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1)) + { + val |= VramD[addr & 0x1FFFF]; + } + if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1)) + { + val |= VramE[addr & 0xFFFF]; + } + offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1)) + { + val |= VramF[addr & 0x3FFF]; + } + offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1)) + { + val |= VramG[addr & 0x3FFF]; + } + return val; + } + + public byte ReadVram8Arm9BgB(uint addr) + { + byte val = 0; + addr &= 0x1FFFFF; + if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4)) + { + val |= VramC[addr & 0x1FFFF]; + } + if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1)) + { + val |= VramH[addr & 0x7FFF]; + } + if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1)) + { + val |= VramI[addr & 0x3FFF]; + } + return val; + } + + public byte ReadVram8Arm9ObjA(uint addr) + { + byte val = 0; + addr &= 0x1FFFFF; + uint offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2)) + { + val |= VramA[addr & 0x1FFFF]; + } + offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2)) + { + val |= VramB[addr & 0x1FFFF]; + } + if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2)) + { + val |= VramE[addr & 0xFFFF]; + } + offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2)) + { + val |= VramF[addr & 0x3FFF]; + } + offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2)) + { + val |= VramG[addr & 0x3FFF]; + } + return val; + } + + public byte ReadVram8Arm9ObjB(uint addr) + { + byte val = 0; + addr &= 0x1FFFFF; + if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4)) + { + val |= VramD[addr & 0x1FFFF]; + } + if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2)) + { + val |= VramI[addr & 0x3FFF]; + } + return val; + } + + public byte ReadVram8Arm9Lcdc(uint addr) + { + switch (addr & 0xE0000) + { + case 0x00000: // A + if (Nds.MemoryControl.VramEnabledAndSet(0, 0)) + return VramA[addr & 0x1FFFF]; + return 0; + case 0x20000: // B + if (Nds.MemoryControl.VramEnabledAndSet(1, 0)) + return VramB[addr & 0x1FFFF]; + return 0; + case 0x40000: // C + if (Nds.MemoryControl.VramEnabledAndSet(2, 0)) + return VramC[addr & 0x1FFFF]; + return 0; + case 0x60000: // D + if (Nds.MemoryControl.VramEnabledAndSet(3, 0)) + return VramD[addr & 0x1FFFF]; + return 0; + case 0x80000: // E, F, G, H + switch (addr & 0xFF000) + { + case 0x00000: + if (Nds.MemoryControl.VramEnabledAndSet(4, 0)) + return VramE[addr & 0xFFFF]; + return 0; + case 0x90000: // F + if (Nds.MemoryControl.VramEnabledAndSet(5, 0)) + return VramF[addr & 0x3FFF]; + return 0; + case 0x94000: // G + if (Nds.MemoryControl.VramEnabledAndSet(6, 0)) + return VramG[addr & 0x3FFF]; + return 0; + case 0x98000: // H + if (Nds.MemoryControl.VramEnabledAndSet(7, 0)) + return VramH[addr & 0x7FFF]; + return 0; + } + break; + case 0x8A000: // I + if (Nds.MemoryControl.VramEnabledAndSet(8, 0)) + return VramI[addr & 0x3FFF]; + return 0; + } + + return 0; + } + + public void WriteVram8Arm9(uint addr, byte val) + { + uint offs; + byte readVal = 0; + switch (addr & 0xFFE00000) + { + case 0x06000000: // Engine A BG VRAM + addr &= 0x1FFFFF; + offs = Nds.MemoryControl.GetOffset(0) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 1)) + { + readVal |= val; VramA[addr & 0x1FFFF] = val; + } + offs = Nds.MemoryControl.GetOffset(1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 1)) + { + readVal |= val; VramB[addr & 0x1FFFF] = val; + } + offs = Nds.MemoryControl.GetOffset(2) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 1)) + { + readVal |= val; VramC[addr & 0x1FFFF] = val; + } + offs = Nds.MemoryControl.GetOffset(3) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 1)) + { + readVal |= val; VramD[addr & 0x1FFFF] = val; + } + if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 1)) + { + readVal |= val; VramE[addr & 0xFFFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 1)) + { + readVal |= val; VramF[addr & 0x3FFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 1)) + { + readVal |= val; VramG[addr & 0x3FFF] = val; + } + VramBgA[addr & 0x1FFFFF] = readVal; + break; + case 0x06200000: // Engine B BG VRAM + addr &= 0x1FFFFF; + if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(2, 4)) + { + readVal |= val; VramC[addr & 0x1FFFF] = val; + } + if (addr < 0x8000 && Nds.MemoryControl.VramEnabledAndSet(7, 1)) + { + readVal |= val; VramH[addr & 0x7FFF] = val; + } + if (addr >= 0x8000 && addr < 0xC000 && Nds.MemoryControl.VramEnabledAndSet(8, 1)) + { + readVal |= val; VramI[addr & 0x3FFF] = val; + } + VramBgB[addr & 0x1FFFF] = readVal; + break; + case 0x06400000: // Engine A OBJ VRAM + addr &= 0x1FFFFF; + offs = (Nds.MemoryControl.GetOffset(0) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(0, 2)) + { + readVal |= val; VramA[addr & 0x1FFFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(1) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(1, 2)) + { + readVal |= val; VramB[addr & 0x1FFFF] = val; + } + if (addr >= 0 && addr < 0x10000 && Nds.MemoryControl.VramEnabledAndSet(4, 2)) + { + readVal |= val; VramE[addr & 0xFFFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(5) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(5) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(5, 2)) + { + readVal |= val; VramF[addr & 0x3FFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(6) & 1) * 0x4000 + ((Nds.MemoryControl.GetOffset(6) >> 1) & 1) * 0x10000; + if (addr >= offs && addr < 0x4000 + offs && Nds.MemoryControl.VramEnabledAndSet(6, 2)) + { + readVal |= val; VramG[addr & 0x3FFF] = val; + } + VramObjA[addr & 0xFFFFF] = readVal; + break; + case 0x06600000: // Engine B OBJ VRAM + addr &= 0x1FFFFF; + if (addr < 0x20000 && Nds.MemoryControl.VramEnabledAndSet(3, 4)) + { + readVal |= val; VramD[addr & 0x1FFFF] = val; + } + if (addr < 0x4000 && Nds.MemoryControl.VramEnabledAndSet(8, 2)) + { + readVal |= val; VramI[addr & 0x3FFF] = val; + } + VramObjB[addr & 0x1FFFF] = readVal; + break; + case 0x06800000: // LCDC VRAM + switch (addr & 0xFFFE0000) + { + case 0x06800000: // A + if (Nds.MemoryControl.VramEnabledAndSet(0, 0)) + readVal |= val; VramA[addr & 0x1FFFF] = val; + break; + case 0x06820000: // B + if (Nds.MemoryControl.VramEnabledAndSet(1, 0)) + readVal |= val; VramB[addr & 0x1FFFF] = val; + break; + case 0x06840000: // C + if (Nds.MemoryControl.VramEnabledAndSet(2, 0)) + readVal |= val; VramC[addr & 0x1FFFF] = val; + break; + case 0x06860000: // D + if (Nds.MemoryControl.VramEnabledAndSet(3, 0)) + readVal |= val; VramD[addr & 0x1FFFF] = val; + break; + case 0x06880000: // E, F, G, H + switch (addr & 0xFFFFF000) + { + case 0x68800000: + if (Nds.MemoryControl.VramEnabledAndSet(4, 0)) + readVal |= val; VramE[addr & 0xFFFF] = val; + break; + case 0x06890000: // F + if (Nds.MemoryControl.VramEnabledAndSet(5, 0)) + readVal |= val; VramF[addr & 0x3FFF] = val; + break; + case 0x06894000: // G + if (Nds.MemoryControl.VramEnabledAndSet(6, 0)) + readVal |= val; VramG[addr & 0x3FFF] = val; + break; + case 0x06898000: // H + if (Nds.MemoryControl.VramEnabledAndSet(7, 0)) + readVal |= val; VramH[addr & 0x7FFF] = val; + break; + } + break; + case 0x068A0000: // I + if (Nds.MemoryControl.VramEnabledAndSet(8, 0)) + readVal |= val; VramI[addr & 0x3FFF] = val; + break; + } + addr &= 0xFFFFF; + if (addr < 671744) + { + VramLcdc[addr] = readVal; + } + break; + } + } + + public byte ReadVram8Arm7(uint addr) + { + uint offs; + byte val = 0; + addr &= 0x1FFFFF; + offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2)) + { + val |= VramC[addr & 0x1FFFF]; + } + offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2)) + { + val |= VramD[addr & 0x1FFFF]; + } + + return val; + } + + public void WriteVram8Arm7(uint addr, byte val) + { + uint offs; + addr &= 0x1FFFFF; + offs = (Nds.MemoryControl.GetOffset(2) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(2, 2)) + { + VramC[addr & 0x1FFFF] = val; + } + offs = (Nds.MemoryControl.GetOffset(3) & 1) * 0x20000; + if (addr >= offs && addr < 0x20000 + offs && Nds.MemoryControl.VramEnabledAndSet(3, 2)) + { + VramD[addr & 0x1FFFF] = val; + } + } + + public void CompileVram() + { + if (Nds.MemoryControl.VramConfigDirty && !DebugDisableVramUpdates) + { + Nds.MemoryControl.VramConfigDirty = false; + if (Renderers[0].DisplayMode == 2) // LCDC MODE + { + uint index = 0; + VramA.CopyTo(VramLcdc, index); index += 131072; + VramB.CopyTo(VramLcdc, index); index += 131072; + VramC.CopyTo(VramLcdc, index); index += 131072; + VramD.CopyTo(VramLcdc, index); index += 131072; + VramE.CopyTo(VramLcdc, index); index += 65536; + VramF.CopyTo(VramLcdc, index); index += 16384; + VramG.CopyTo(VramLcdc, index); index += 16384; + VramH.CopyTo(VramLcdc, index); index += 32768; + VramI.CopyTo(VramLcdc, index); index += 16384; + } + else + { + Console.WriteLine("VRAM reconfigured, recompiling from scratch"); + for (uint i = 0; i < 524288; i++) + { + VramBgA[i] = ReadVram8Arm9(0x06000000 + i); + } + for (uint i = 0; i < 262144; i++) + { + VramObjA[i] = ReadVram8Arm9(0x06400000 + i); + } + for (uint i = 0; i < 131072; i++) + { + VramBgB[i] = ReadVram8Arm9(0x06200000 + i); + } + for (uint i = 0; i < 131072; i++) + { + VramObjB[i] = ReadVram8Arm9(0x06600000 + i); + } + } + } + } + + public long ScanlineStartCycles; + + public uint DISPCNTAValue; + public uint DISPCNTBValue; + + // DISPSTAT + public bool VCounterMatch7; + public bool VBlankIrqEnable7; + public bool HBlankIrqEnable7; + public bool VCounterIrqEnable7; + public uint VCountSetting7; + public bool VCounterMatch9; + public bool VBlankIrqEnable9; + public bool HBlankIrqEnable9; + public bool VCounterIrqEnable9; + public uint VCountSetting9; + + // State + public uint VCount; + + public long GetScanlineCycles() + { + return Scheduler.CurrentTicks - ScanlineStartCycles; + } + + public byte ReadHwio8Arm9(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x4000000: // DISPCNTA B0 + return (byte)(DISPCNTAValue >> 0); + case 0x4000001: // DISPCNTA B1 + return (byte)(DISPCNTAValue >> 8); + case 0x4000002: // DISPCNTA B2 + return (byte)(DISPCNTAValue >> 16); + case 0x4000003: // DISPCNTA B3 + return (byte)(DISPCNTAValue >> 24); + + case 0x4000004: // DISPSTAT B0 + // Vblank flag is set in scanlines 192-261, not including 262 for some reason + if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0); + // Hblank flag is set at cycle 1606, not cycle 1536 + if (GetScanlineCycles() >= 1606) val = BitSet(val, 1); + if (VCounterMatch9) val = BitSet(val, 2); + if (VBlankIrqEnable9) val = BitSet(val, 3); + if (HBlankIrqEnable9) val = BitSet(val, 4); + if (VCounterIrqEnable9) val = BitSet(val, 5); + val |= (byte)((VCountSetting9 >> 1) & 0x80); + return val; + case 0x4000005: // DISPSTAT B1 + val |= (byte)VCountSetting9; + return val; + + case 0x4000006: // VCOUNT B0 - B1 only exists for Nintendo DS + val |= (byte)VCount; + return val; + case 0x4000007: + val |= (byte)((VCount >> 8) & 1); + return val; + + case 0x4001000: // DISPCNTB B0 + return (byte)(DISPCNTBValue >> 0); + case 0x4001001: // DISPCNTB B1 + return (byte)(DISPCNTBValue >> 8); + case 0x4001002: // DISPCNTB B2 + return (byte)(DISPCNTBValue >> 16); + case 0x4001003: // DISPCNTB B3 + return (byte)(DISPCNTBValue >> 24); + } + + if (addr >= 0x4000000 && addr < 0x4000058) + { + return Renderers[0].ReadHwio8(addr & 0xFF); + } + if (addr >= 0x4001000 && addr < 0x4001058) + { + return Renderers[1].ReadHwio8(addr & 0xFF); + } + + return val; + } + + public void WriteHwio8Arm9(uint addr, byte val) + { + switch (addr) + { + // A lot of these DISPCNT values are shared between A/B. + case 0x4000000: // DISPCNT B0 + // A + Renderers[0].Bg0Is3D = BitTest(val, 3); + + // A+B + Renderers[0].BgMode = BitRange(val, 0, 2); + Renderers[0].ObjCharOneDimensional = BitTest(val, 4); + Renderers[0].BitmapObjShape = BitTest(val, 5); + Renderers[0].BitmapObjMapping = BitTest(val, 6); + Renderers[0].ForcedBlank = BitTest(val, 7); + + Renderers[0].BackgroundSettingsDirty = true; + + DISPCNTAValue &= 0xFFFFFF00; + DISPCNTAValue |= (uint)(val << 0); + + break; + case 0x4000001: // DISPCNT B1 + // A+B + Renderers[0].ScreenDisplayBg[0] = BitTest(val, 8 - 8); + Renderers[0].ScreenDisplayBg[1] = BitTest(val, 9 - 8); + Renderers[0].ScreenDisplayBg[2] = BitTest(val, 10 - 8); + Renderers[0].ScreenDisplayBg[3] = BitTest(val, 11 - 8); + Renderers[0].ScreenDisplayObj = BitTest(val, 12 - 8); + Renderers[0].Window0DisplayFlag = BitTest(val, 13 - 8); + Renderers[0].Window1DisplayFlag = BitTest(val, 14 - 8); + Renderers[0].ObjWindowDisplayFlag = BitTest(val, 15 - 8); + Renderers[0].AnyWindowEnabled = (val & 0b11100000) != 0; + + Renderers[0].BackgroundSettingsDirty = true; + + DISPCNTAValue &= 0xFFFF00FF; + DISPCNTAValue |= (uint)(val << 8); + break; + case 0x4000002: // DISPCNT B2 + // A + Renderers[0].LcdcVramBlock = BitRange(val, 2, 3); + Renderers[0].BitmapObj1DBoundary = BitTest(val, 6); + + // A+B + // var oldDisplayMode = Renderers[0].DisplayMode; + // if (Renderers[0].DisplayMode != oldDisplayMode) VramDirty = true; + Renderers[0].DisplayMode = BitRange(val, 0, 1); + Renderers[0].TileObj1DBoundary = BitRange(val, 4, 5); + Renderers[0].HBlankIntervalFree = BitTest(val, 7); + + Renderers[0].BackgroundSettingsDirty = true; + + DISPCNTAValue &= 0xFF00FFFF; + DISPCNTAValue |= (uint)(val << 16); + break; + case 0x4000003: // DISPCNT B3 + // A + Renderers[0].CharBaseBlockCoarse = BitRange(val, 0, 2); + Renderers[0].MapBaseBlockCoarse = BitRange(val, 3, 5); + + // A+B + Renderers[0].BgExtendedPalettes = BitTest(val, 6); + Renderers[0].ObjExtendedPalettes = BitTest(val, 7); + + DISPCNTAValue &= 0x00FFFFFF; + DISPCNTAValue |= (uint)(val << 24); + break; + + case 0x4000004: // DISPSTAT B0 + VBlankIrqEnable9 = BitTest(val, 3); + HBlankIrqEnable9 = BitTest(val, 4); + VCounterIrqEnable9 = BitTest(val, 5); + + VCountSetting9 &= 0x0FFU; + VCountSetting9 |= (uint)((val & 0x80) << 1); + break; + case 0x4000005: // DISPSTAT B1 + VCountSetting9 &= 0x100U; + VCountSetting9 |= val; + break; + + case 0x4000006: // Vcount + case 0x4000007: + // throw new NotImplementedException("NDS: write to vcount"); + break; + + case 0x4001000: // DISPCNTB B0 + // A+B + Renderers[1].BgMode = BitRange(val, 0, 2); + Renderers[1].ObjCharOneDimensional = BitTest(val, 4); + Renderers[1].BitmapObjShape = BitTest(val, 5); + Renderers[1].BitmapObjMapping = BitTest(val, 6); + Renderers[1].ForcedBlank = BitTest(val, 7); + + Renderers[1].BackgroundSettingsDirty = true; + + DISPCNTBValue &= 0xFFFFFF00; + DISPCNTBValue |= (uint)(val << 0); + + break; + case 0x4001001: // DISPCNTB B1 + // A+B + Renderers[1].ScreenDisplayBg[0] = BitTest(val, 8 - 8); + Renderers[1].ScreenDisplayBg[1] = BitTest(val, 9 - 8); + Renderers[1].ScreenDisplayBg[2] = BitTest(val, 10 - 8); + Renderers[1].ScreenDisplayBg[3] = BitTest(val, 11 - 8); + Renderers[1].ScreenDisplayObj = BitTest(val, 12 - 8); + Renderers[1].Window0DisplayFlag = BitTest(val, 13 - 8); + Renderers[1].Window1DisplayFlag = BitTest(val, 14 - 8); + Renderers[1].ObjWindowDisplayFlag = BitTest(val, 15 - 8); + Renderers[1].AnyWindowEnabled = (val & 0b11100000) != 0; + + Renderers[1].BackgroundSettingsDirty = true; + + DISPCNTBValue &= 0xFFFF00FF; + DISPCNTBValue |= (uint)(val << 8); + break; + case 0x4001002: // DISPCNTB B2 + // A+B + // var oldDisplayModeB = Renderers[1].DisplayMode; + // if (Renderers[1].DisplayMode != oldDisplayModeB) VramDirty = true; + Renderers[1].DisplayMode = BitRange(val, 0, 1); + Renderers[1].TileObj1DBoundary = BitRange(val, 4, 5); + Renderers[1].HBlankIntervalFree = BitTest(val, 7); + + Renderers[1].BackgroundSettingsDirty = true; + + DISPCNTBValue &= 0xFF00FFFF; + DISPCNTBValue |= (uint)(val << 16); + break; + case 0x4001003: // DISPCNTB B3 + // A+B + Renderers[1].BgExtendedPalettes = BitTest(val, 6); + Renderers[1].ObjExtendedPalettes = BitTest(val, 7); + + DISPCNTBValue &= 0x00FFFFFF; + DISPCNTBValue |= (uint)(val << 24); + break; + } + + if (addr >= 0x4000000 && addr < 0x4000058) + { + Renderers[0].WriteHwio8(addr & 0xFF, val); + } + if (addr >= 0x4001000 && addr < 0x4001058) + { + Renderers[1].WriteHwio8(addr & 0xFF, val); + } + } + + public byte ReadHwio8Arm7(uint addr) + { + byte val = 0; + + switch (addr) + { + case 0x4000004: // DISPSTAT B0 + // Vblank flag is set in scanlines 192-261, not including 262 for some reason + if (VCount >= 192 && VCount <= 261) val = BitSet(val, 0); + // Hblank flag is set at cycle 1606, not cycle 1536 + if (GetScanlineCycles() >= 1606) val = BitSet(val, 1); + if (VCounterMatch7) val = BitSet(val, 2); + if (VBlankIrqEnable7) val = BitSet(val, 3); + if (HBlankIrqEnable7) val = BitSet(val, 4); + if (VCounterIrqEnable7) val = BitSet(val, 5); + val |= (byte)((VCountSetting7 >> 1) & 0x80); + return val; + case 0x4000005: // DISPSTAT B1 + val |= (byte)VCountSetting7; + return val; + } + + return 0; + } + + public void WriteHwio8Arm7(uint addr, byte val) + { + switch (addr) + { + case 0x4000004: // DISPSTAT B0 + VBlankIrqEnable7 = BitTest(val, 3); + HBlankIrqEnable7 = BitTest(val, 4); + VCounterIrqEnable7 = BitTest(val, 5); + + VCountSetting7 &= 0x0FFU; + VCountSetting7 |= (uint)((val & 0x80) << 1); + break; + case 0x4000005: // DISPSTAT B1 + VCountSetting7 &= 0x100U; + VCountSetting7 |= val; + break; + } + } + + public byte ReadOam8(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetByte(Renderers[id].Oam, addr); + } + + public ushort ReadOam16(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetUshort(Renderers[id].Oam, addr); + } + + public uint ReadOam32(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetUint(Renderers[id].Oam, addr); + } + + public void WriteOam16(uint addr, ushort val) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + SetUshort(Renderers[id].Oam, addr, val); + } + + public void WriteOam32(uint addr, uint val) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + SetUint(Renderers[id].Oam, addr, val); + } + + public byte ReadPalettes8(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetByte(Renderers[id].Palettes, addr); + } + + public ushort ReadPalettes16(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetUshort(Renderers[id].Palettes, addr); + } + + public uint ReadPalettes32(uint addr) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + return GetUint(Renderers[id].Palettes, addr); + } + + public void WritePalettes16(uint addr, ushort val) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + if (GetUshort(Renderers[id].Palettes, addr) != val) + { + SetUshort(Renderers[id].Palettes, addr, val); + } + } + + public void WritePalettes32(uint addr, uint val) + { + addr &= 0x7FF; + var id = addr >= 0x400 ? 1 : 0; + addr &= 0x3FF; + if (GetUint(Renderers[id].Palettes, addr) != val) + { + SetUint(Renderers[id].Palettes, addr, val); + } + } + + public void EndDrawingToHblank(long cyclesLate) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank); + + // if (HBlankIrqEnable) + // { + // Gba.HwControl.FlagInterrupt(InterruptGba.HBlank); + // } + + if (Renderers[0].DisplayMode == 2) // LCDC MODE + { + Renderers[0].RenderScanlineNds(VCount, VramLcdc, VramLcdc); + } + else + { + if (Renderers[0].DebugEnableRendering) Renderers[0].RenderScanlineNds(VCount, VramBgA, VramObjA); + } + if (Renderers[1].DisplayMode == 2) + { + Renderers[1].RenderScanlineNds(VCount, VramLcdc, VramLcdc); + } + else + { + if (Renderers[1].DebugEnableRendering) Renderers[1].RenderScanlineNds(VCount, VramBgB, VramObjB); + } + Renderers[0].IncrementMosaicCounters(); + Renderers[1].IncrementMosaicCounters(); + + Nds.Dma9.Repeat((byte)DmaStartTimingNds9.HBlank); + } + + public void EndVblankToHblank(long cyclesLate) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 594 - cyclesLate, EndHblank); + + // if (HBlankIrqEnable) + // { + // Nds.HwControl.FlagInterrupt(InterruptGba.HBlank); + // } + } + + public void EndHblank(long cyclesLate) + { + ScanlineStartCycles = Scheduler.CurrentTicks; + + if (VCount != 262) + { + VCount++; + + if (VCount > 191) + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndVblankToHblank); + + if (VCount == 192) + { + // Nds.Dma.RepeatVblank(); + + if (VBlankIrqEnable7) + { + Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VBlank); + } + if (VBlankIrqEnable9) + { + Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VBlank); + } + + Renderers[0].RunVblankOperations(); + Renderers[1].RunVblankOperations(); + + Renderers[0].TotalFrames++; + if (Renderers[0].DebugEnableRendering) Renderers[0].SwapBuffers(); + if (Renderers[1].DebugEnableRendering) Renderers[1].SwapBuffers(); + + Renderers[0].RenderingDone = true; + } + } + else + { + Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank); + } + } + else + { + VCount = 0; + Scheduler.AddEventRelative(SchedulerId.Ppu, 1536 - cyclesLate, EndDrawingToHblank); + + // CompileVram(); + + // Pre-render sprites for line zero + fixed (byte* vramObjA = VramObjA, vramObjB = VramObjB) + { + if (Renderers[0].DebugEnableObj && Renderers[0].ScreenDisplayObj) Renderers[0].RenderObjs(0, vramObjA); + if (Renderers[1].DebugEnableObj && Renderers[1].ScreenDisplayObj) Renderers[1].RenderObjs(0, vramObjB); + } + } + + VCounterMatch7 = VCount == VCountSetting7; + VCounterMatch9 = VCount == VCountSetting9; + + if (VCounterMatch7 && VCounterIrqEnable7) + { + Nds.HwControl7.FlagInterrupt((uint)InterruptNds.VCounterMatch); + } + if (VCounterMatch9 && VCounterIrqEnable9) + { + Nds.HwControl9.FlagInterrupt((uint)InterruptNds.VCounterMatch); + } + } + } +} diff --git a/Assets/emulator/PpuNds.cs.meta b/Assets/emulator/PpuNds.cs.meta new file mode 100644 index 0000000..65452d8 --- /dev/null +++ b/Assets/emulator/PpuNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94cef7d2d800ba548a03c0fb676340ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/PpuNds3D.cs b/Assets/emulator/PpuNds3D.cs new file mode 100644 index 0000000..00189a7 --- /dev/null +++ b/Assets/emulator/PpuNds3D.cs @@ -0,0 +1,1016 @@ +using System; +using static OptimeGBA.Bits; +using static OptimeGBA.CoreUtil; +using static Util; +using System.Runtime.CompilerServices; +using System.Diagnostics; + +namespace OptimeGBA +{ + public struct Command + { + public byte Cmd; + public uint Param; + + public Command(byte cmd, uint param) + { + Cmd = cmd; + Param = param; + } + } + + public enum PrimitiveType + { + Tris, + Quads, + TriStrips, + QuadStrips + } + + public unsafe struct Matrix + { + public fixed int Data[16]; + + public static Matrix GetIdentity() + { + var m = new Matrix(); + + m.Data[0] = 0x00001000; + m.Data[5] = 0x00001000; + m.Data[10] = 0x00001000; + m.Data[15] = 0x00001000; + + return m; + } + + public Matrix Multiply(Matrix a) + { + var m = new Matrix(); + + for (int i = 0; i < 16; i++) + { + long sum = 0; + + int ai = i & ~3; // Matrix A row + int bi = i & 3; // Matrix B column + + for (int j = 0; j < 4; j++) + { + sum += (long)a.Data[ai] * Data[bi]; + ai++; + bi += 4; + } + + // Trim multiplied fixed point digits off + m.Data[i] = (int)(sum >> 12); + } + + return m; + } + + public Vector Multiply(Vector a) + { + var v = new Vector(); + + for (int i = 0; i < 4; i++) + { + long sum = 0; + + for (int j = 0; j < 4; j++) + { + sum += (long)Data[j * 4 + i] * a.Data[j]; + } + + // Trim multiplied fixed point digits off + v.Data[i] = (int)(sum >> 12); + } + + return v; + } + + public void Print(string s) + { + PpuNds3D.Debug(s); + PpuNds3D.Debug($"{HexN((uint)Data[0x0], 8)} {HexN((uint)Data[0x1], 8)} {HexN((uint)Data[0x2], 8)} {HexN((uint)Data[0x3], 8)}"); + PpuNds3D.Debug($"{HexN((uint)Data[0x4], 8)} {HexN((uint)Data[0x5], 8)} {HexN((uint)Data[0x6], 8)} {HexN((uint)Data[0x7], 8)}"); + PpuNds3D.Debug($"{HexN((uint)Data[0x8], 8)} {HexN((uint)Data[0x9], 8)} {HexN((uint)Data[0xA], 8)} {HexN((uint)Data[0xB], 8)}"); + PpuNds3D.Debug($"{HexN((uint)Data[0xC], 8)} {HexN((uint)Data[0xD], 8)} {HexN((uint)Data[0xE], 8)} {HexN((uint)Data[0xF], 8)}"); + } + } + + public unsafe struct Vector + { + public fixed int Data[4]; + + public void Print(string s) + { + PpuNds3D.Debug(s); + PpuNds3D.Debug($"{HexN((uint)Data[0x0], 8)} {HexN((uint)Data[0x1], 8)} {HexN((uint)Data[0x2], 8)} {HexN((uint)Data[0x3], 8)}"); + } + } + + public unsafe struct Vertex + { + public Vector Pos; + public fixed byte Color[3]; + } + + public class MatrixStack + { + public Matrix[] Stack; + public Matrix Current; + + public sbyte Sp; + public sbyte SpMask; + + public MatrixStack(uint size, sbyte spMask) + { + Stack = new Matrix[size]; + SpMask = spMask; + } + + public void Push() + { + // Debug("Matrix push"); + if ((Sp & 31) != 31) + { + Stack[Sp & 31] = Current; + } + Sp++; + Sp &= SpMask; + } + + public void Pop(sbyte offset) + { + // Debug("Matrix pop"); + Sp -= offset; + Sp &= SpMask; + if ((Sp & 31) != 31) + { + Current = Stack[Sp & 31]; + } + } + + public void Store(byte addr) + { + Stack[addr] = Current; + } + + public void Restore(byte addr) + { + Current = Stack[addr]; + } + } + + public enum MatrixMode + { + Projection, + Position, + PositionDirection, + Texture + } + + public sealed unsafe class PpuNds3D + { + Nds Nds; + Scheduler Scheduler; + + public PpuNds3D(Nds nds, Scheduler scheduler) + { + Nds = nds; + Scheduler = scheduler; + } + + public static readonly byte[] CommandParamLengths = new byte[256] { + /* 0x00 No operation */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 Matrix ops */ 1, 0, 1, 1, 1, 0, 16, 12, 16, 12, 9, 3, 3, 0, 0, 0, + /* 0x20 Vertex data */ 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, + /* 0x30 Lighting */ 1, 1, 1, 1, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 Begin/end vertex list */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x50 Swap buffers */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x60 Set viewport */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x70 Tests */ 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + + // Screen + public ushort[] Screen = new ushort[256 * 192]; + + // GXSTAT + public byte CommandFifoIrqMode; + + // GXFIFO + public CircularBuffer PackedCommandQueue = new CircularBuffer(4, 0); + public int PackedParamsQueued = 0; + public CircularBuffer CommandFifo = new CircularBuffer(256, new Command()); + + // GPU State + public MatrixMode MatrixMode; + public MatrixStack ProjectionStack = new MatrixStack(1, 0); + public MatrixStack PositionStack = new MatrixStack(31, 63); + public MatrixStack DirectionStack = new MatrixStack(31, 63); + public MatrixStack TextureStack = new MatrixStack(1, 0); + public Matrix ClipMatrix; + // public bool ClipMatrixDirty; // TODO - reimplement this when I need more speed + + public byte[] Viewport1 = new byte[2]; + public byte[] Viewport2 = new byte[2]; + + // Debug State + public Matrix DebugProjectionMatrix; + public Matrix DebugPositionMatrix; + public Matrix DebugDirectionMatrix; + + + + + public Matrix DebugTextureMatrix; + + public PrimitiveType PrimitiveType; + public short[] VertexCoords = new short[3]; + public byte[] VertexColor = new byte[3]; + public CircularBuffer VertexQueueFront = new CircularBuffer(6144, new Vertex()); + public CircularBuffer VertexQueueBack = new CircularBuffer(6144, new Vertex()); + + public uint ReadHwio32(uint addr) + { + uint val = 0; + + switch (addr) + { + case 0x4000600: // GXSTAT + val |= 0b10; // always true boxtest + + val |= (uint)(PositionStack.Sp & 0b11111) << 8; + val |= (uint)(ProjectionStack.Sp & 0b1) << 13; + + val |= CommandFifo.Entries << 16; + if (CommandFifo.Entries == 256) val = BitSet(val, 24); + if (CommandFifo.Entries < 128) val = BitSet(val, 25); + if (CommandFifo.Entries == 0) val = BitSet(val, 26); + if (CommandFifo.Entries > 0) val = BitSet(val, 27); + val |= (uint)CommandFifoIrqMode << 30; + break; + } + + return val; + } + + public void WriteHwio32(uint addr, uint val) + { + if (addr >= 0x4000440 && addr < 0x4000600) + { + Debug2("3D: MMIO insert cmd " + Hex((byte)(addr >> 2), 2) + " param " + Hex(val, 8)); + + QueueCommand((byte)(addr >> 2), val); + // Debug("3D: Port command send"); + } + + // GXFIFO + if (addr >= 0x4000400 && addr < 0x4000440) + { + // QueueCommand(0); + // TODO: GXFIFO commands + // Console.WriteLine("GXFIFO command send"); + if (PackedCommandQueue.Entries == 0) + { + // Console.WriteLine(CommandFifo.Entries); + for (int i = 0; i < 4; i++) + { + byte cmd = (byte)val; + + if (cmd != 0) + { + if (cmd > 0x72) + { + throw new Exception("3D: GXFIFO insert invalid cmd " + Hex(cmd, 2) + " from addr " + Hex(addr, 8)); + } + + Debug2("3D: GXFIFO insert " + Hex(cmd, 2) + " from addr " + Hex(addr, 8)); + if (CommandParamLengths[cmd] != 0) + { + PackedCommandQueue.Insert(cmd); + } + else + { + // if no params just queue it up + QueueCommand(cmd, 0); + } + } + + val >>= 8; + } + } + else + { + // Console.WriteLine("quued"); + byte cmd = PackedCommandQueue.Peek(0); + QueueCommand(cmd, val); + + PackedParamsQueued++; + + Debug2("3D: GXFIFO take param cmd " + Hex(cmd, 2) + " param " + Hex(val, 8) + " remaining " + (CommandParamLengths[cmd] - PackedParamsQueued) + " from addr " + Hex(addr, 8)); + + if (PackedParamsQueued >= CommandParamLengths[cmd]) + { + PackedParamsQueued = 0; + + PackedCommandQueue.Pop(); + + Debug("execu"); + } + } + return; + } + + switch (addr) + { + case 0x4000600: + CommandFifoIrqMode = (byte)((val >> 30) & 0b11); + return; + } + } + + public void QueueCommand(byte cmd, uint val) + { + if (!CommandFifo.Insert(new Command(cmd, val))) + { + Console.Error.WriteLine("3D: GXFIFO overflow"); + } + + CheckCommands(); + } + + public void RunCommand(Command cmd) + { + Debug2("3D CMD: " + Hex(cmd.Cmd, 2) + " take " + CommandParamLengths[cmd.Cmd] + " params " + "queue " + CommandFifo.Entries); + + switch (cmd.Cmd) + { + case 0x10: // Set Matrix Mode + PopCommand(); + MatrixMode = (MatrixMode)(cmd.Param & 0b11); + Debug("Set Matrix Mode: " + MatrixMode); + break; + case 0x11: // Push Current Matrix + Debug("Push Current Matrix"); + PopCommand(); + switch (MatrixMode) + { + case MatrixMode.Projection: + // ProjectionStack.Current.Print("Push Projection"); + ProjectionStack.Push(); + break; + case MatrixMode.Position: + case MatrixMode.PositionDirection: + // PositionStack.Current.Print("Push PositionDirection"); + PositionStack.Push(); + DirectionStack.Push(); + break; + case MatrixMode.Texture: + TextureStack.Push(); + break; + } + break; + case 0x12: // Pop Current Matrix + Debug("Pop Current Matrix"); + PopCommand(); + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Pop(SignExtend8((byte)cmd.Param, 5)); + break; + case MatrixMode.Position: + case MatrixMode.PositionDirection: + // PositionStack.Current.Print("Pre-pop position matrix"); + PositionStack.Pop(SignExtend8((byte)cmd.Param, 5)); + DirectionStack.Pop(SignExtend8((byte)cmd.Param, 5)); + break; + case MatrixMode.Texture: + TextureStack.Pop(SignExtend8((byte)cmd.Param, 5)); + break; + } + break; + case 0x13: // Store Current Matrix + PopCommand(); + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Store((byte)(cmd.Param & 0b11111)); + break; + case MatrixMode.Position: + case MatrixMode.PositionDirection: + PositionStack.Store((byte)(cmd.Param & 0b11111)); + DirectionStack.Store((byte)(cmd.Param & 0b11111)); + break; + case MatrixMode.Texture: + TextureStack.Store((byte)(cmd.Param & 0b11111)); + break; + } + break; + case 0x14: // Restore Current Matrix + PopCommand(); + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Restore((byte)(cmd.Param & 0b11111)); + break; + case MatrixMode.Position: + case MatrixMode.PositionDirection: + PositionStack.Restore((byte)(cmd.Param & 0b11111)); + DirectionStack.Restore((byte)(cmd.Param & 0b11111)); + break; + case MatrixMode.Texture: + TextureStack.Restore((byte)(cmd.Param & 0b11111)); + break; + } + break; + case 0x15: // Load Identity Matrix + Debug("Load Identity Matrix"); + PopCommand(); + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Current = Matrix.GetIdentity(); + break; + case MatrixMode.Position: + PositionStack.Current = Matrix.GetIdentity(); + break; + case MatrixMode.PositionDirection: + PositionStack.Current = Matrix.GetIdentity(); + DirectionStack.Current = Matrix.GetIdentity(); + break; + case MatrixMode.Texture: + TextureStack.Current = Matrix.GetIdentity(); + break; + } + break; + case 0x16: // Load 4x4 Matrix to Current Matrix + { + Matrix m = new Matrix(); + + for (int i = 0; i < 16; i++) + { + m.Data[i] = (int)PopCommand().Param; + } + + LoadCurrentMatrix(ref m); + } + break; + case 0x17: // Load 4x3 Matrix to Current Matrix + { + Matrix m = Matrix.GetIdentity(); + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 3; j++) + { + m.Data[i * 4 + j] = (int)PopCommand().Param; + } + } + + LoadCurrentMatrix(ref m); + } + break; + case 0x18: // Multiply Current Matrix by 4x4 Matrix + Debug("Multiply Current Matrix by 4x4 Matrix"); + { + Matrix m = new Matrix(); + + for (int i = 0; i < 16; i++) + { + m.Data[i] = (int)PopCommand().Param; + } + + MultiplyCurrentMatrixBy(ref m); + } + break; + case 0x19: // Multiply Current Matrix by 4x3 Matrix + Debug("Multiply Current Matrix by 4x3 Matrix"); + { + Matrix m = Matrix.GetIdentity(); + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 3; j++) + { + m.Data[i * 4 + j] = (int)PopCommand().Param; + } + } + + MultiplyCurrentMatrixBy(ref m); + } + break; + case 0x1A: // Multiply Current Matrix by 3x3 Matrix + Debug("Multiply Current Matrix by 3x3 Matrix"); + { + Matrix m = Matrix.GetIdentity(); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + m.Data[i * 4 + j] = (int)PopCommand().Param; + } + } + + MultiplyCurrentMatrixBy(ref m); + } + break; + case 0x1B: // Multiply Current Matrix by Scale Matrix + Debug("Multiply Current Matrix by Scale Matrix"); + { + Matrix m = Matrix.GetIdentity(); + + m.Data[0] = (int)PopCommand().Param; + m.Data[5] = (int)PopCommand().Param; + m.Data[10] = (int)PopCommand().Param; + + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Current = ProjectionStack.Current.Multiply(m); + break; + case MatrixMode.Position: + case MatrixMode.PositionDirection: + PositionStack.Current = PositionStack.Current.Multiply(m); + break; + case MatrixMode.Texture: + TextureStack.Current = TextureStack.Current.Multiply(m); + break; + } + } + break; + case 0x1C: // Multiply Current Matrix by Translation Matrix + Debug("Multiply Current Matrix by Translation Matrix"); + { + Matrix m = Matrix.GetIdentity(); + + m.Data[12] = (int)PopCommand().Param; + m.Data[13] = (int)PopCommand().Param; + m.Data[14] = (int)PopCommand().Param; + + MultiplyCurrentMatrixBy(ref m); + } + break; + case 0x20: // Directly Set Vertex Color + Debug("Directly Set Vertex Color"); + PopCommand(); + for (int i = 0; i < 3; i++) + { + uint color = cmd.Param & 0b11111; + + VertexColor[i] = (byte)(color * 2 + (color + 31) / 32); + cmd.Param >>= 5; + } + break; + case 0x21: // TODO: Set Normal Vector + PopCommand(); + break; + case 0x22: // TODO: Set Texture Coordinates + PopCommand(); + break; + case 0x23: // Set Vertex XYZ 12-bit fraction + Debug("Set Vertex XYZ 12-bit fraction"); + PopCommand(); + VertexCoords[0] = (short)cmd.Param; + VertexCoords[1] = (short)(cmd.Param >> 16); + cmd = PopCommand(); + VertexCoords[2] = (short)cmd.Param; + + TransformAndAddVertex(); + break; + case 0x24: // Set Vertex XYZ 6-bit fraction + PopCommand(); + VertexCoords[0] = (short)(((cmd.Param >> 0) & 1023) << 6); + VertexCoords[1] = (short)(((cmd.Param >> 10) & 1023) << 6); + VertexCoords[2] = (short)(((cmd.Param >> 20) & 1023) << 6); + + TransformAndAddVertex(); + break; + case 0x25: // Set Vertex XY 12-bit fraction + PopCommand(); + VertexCoords[0] = (short)cmd.Param; + VertexCoords[1] = (short)(cmd.Param >> 16); + + TransformAndAddVertex(); + break; + case 0x26: // Set Vertex XZ 12-bit fraction + PopCommand(); + VertexCoords[0] = (short)cmd.Param; + VertexCoords[2] = (short)(cmd.Param >> 16); + + TransformAndAddVertex(); + break; + case 0x27: // Set Vertex YZ 12-bit fraction + PopCommand(); + VertexCoords[1] = (short)cmd.Param; + VertexCoords[2] = (short)(cmd.Param >> 16); + + TransformAndAddVertex(); + break; + case 0x28: // Relative add vertex coordinates + PopCommand(); + VertexCoords[0] += (short)((((cmd.Param >> 0) & 1023) << 6) >> 6); + VertexCoords[1] += (short)((((cmd.Param >> 10) & 1023) << 6) >> 6); + VertexCoords[2] += (short)((((cmd.Param >> 20) & 1023) << 6) >> 6); + + TransformAndAddVertex(); + break; + case 0x29: // TODO: Polygon Attributes + Debug("Polygon Attributes"); + PopCommand(); + break; + case 0x2A: // TODO: Texture Parameters + Debug("Texture Parameters"); + PopCommand(); + break; + case 0x2B: // TODO: Set Texture Palette Base Address + PopCommand(); + break; + case 0x30: // TODO: Diffise/Ambient Reflections + PopCommand(); + break; + case 0x31: // TODO: Specular Reflections & Emission + PopCommand(); + break; + case 0x32: // TODO: Light Vector + PopCommand(); + break; + case 0x33: // TODO: Set Light Color + PopCommand(); + break; + case 0x34: // TODO: Shininess + for (uint i = 0; i < 32; i++) + { + PopCommand(); + } + break; + case 0x40: // Begin Vertex List + PopCommand(); + PrimitiveType = (PrimitiveType)(cmd.Param & 0b11); + // Console.WriteLine("Begin Vertex List: " + PrimitiveType); + break; + case 0x41: // End Vertex List - essentially a NOP + Debug("End Vertex List"); + PopCommand(); + break; + case 0x50: // Swap Buffers + Debug("Swap Buffers"); + PopCommand(); + // TODO: swap buffers parameters + Render(); + Swap(ref VertexQueueFront, ref VertexQueueBack); + break; + case 0x60: // Set Viewport + Debug("Set Viewport"); + PopCommand(); + Viewport1[0] = (byte)cmd.Param; + Viewport1[1] = (byte)(cmd.Param >> 8); + Viewport2[0] = (byte)(cmd.Param >> 16); + Viewport2[1] = (byte)(cmd.Param >> 24); + break; + case 0x70: // TODO: Box Test + PopCommand(); + PopCommand(); + PopCommand(); + break; + case 0x71: // TODO: Position Test + PopCommand(); + PopCommand(); + break; + default: + throw new Exception(Hex(cmd.Cmd, 2)); + + if (CommandParamLengths[cmd.Cmd] == 0) + { + PopCommand(); + } + else + { + for (uint i = 0; i < CommandParamLengths[cmd.Cmd]; i++) + { + PopCommand(); + } + } + break; + } + } + + public void CheckCommands() + { + while (CommandFifo.Entries > 0) + { + Command cmd = PeekCommand(); + + if (CommandFifo.Entries >= CommandParamLengths[cmd.Cmd]) + { + RunCommand(cmd); + } + else + { + break; + } + } + } + + public void Run() + { + if ( + (CommandFifo.Entries == 0 && CommandFifoIrqMode == 2) || + (CommandFifo.Entries < 128 && CommandFifoIrqMode == 1) + ) + { + Nds.HwControl9.FlagInterrupt((uint)InterruptNds.GeometryFifo); + } + } + + public Command PopCommand() + { + if (CommandFifo.Entries == 0) Console.Error.WriteLine("3D: Tried popping with no more commands left!"); + + return CommandFifo.Pop(); + } + + public Command PeekCommand() + { + return CommandFifo.Peek(0); + } + + public void LoadCurrentMatrix(ref Matrix m) + { + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Current = m; + break; + case MatrixMode.Position: + PositionStack.Current = m; + break; + case MatrixMode.PositionDirection: + PositionStack.Current = m; + DirectionStack.Current = m; + break; + case MatrixMode.Texture: + TextureStack.Current = m; + break; + } + } + + public void MultiplyCurrentMatrixBy(ref Matrix m) + { + switch (MatrixMode) + { + case MatrixMode.Projection: + ProjectionStack.Current = ProjectionStack.Current.Multiply(m); + // ProjectionStack.Current.Print("Projection after multiply"); + break; + case MatrixMode.Position: + PositionStack.Current = PositionStack.Current.Multiply(m); + break; + case MatrixMode.PositionDirection: + PositionStack.Current.Print("Before translation mul"); + PositionStack.Current = PositionStack.Current.Multiply(m); + DirectionStack.Current = DirectionStack.Current.Multiply(m); + PositionStack.Current.Print("After translation mul"); + break; + case MatrixMode.Texture: + TextureStack.Current = TextureStack.Current.Multiply(m); + break; + } + } + + public void TransformAndAddVertex() + { + var v = new Vertex(); + + for (int i = 0; i < 3; i++) + { + v.Color[i] = VertexColor[i]; + v.Pos.Data[i] = VertexCoords[i]; + } + + v.Pos.Data[3] = 0x1000; // Set W coordinate to 1 + v.Pos = PositionStack.Current.Multiply(v.Pos); + v.Pos = ProjectionStack.Current.Multiply(v.Pos); + // PositionStack.Current.Print("Position Matrix"); + VertexQueueBack.Insert(v); + + } + + public void Render() + { + // Fill screen with black for now + for (uint i = 0; i < Screen.Length; i++) + { + Screen[i] = 0; + } + + Span vertices = stackalloc Vertex[3]; + while (VertexQueueFront.Entries >= 1) + { + var v = VertexQueueFront.Pop(); + + int x = v.Pos.Data[0]; + int y = v.Pos.Data[1]; + int z = v.Pos.Data[2]; + int w = v.Pos.Data[3]; + + if (w == 0) + { + continue; + } + + // int screenX = (((x * (Viewport2[0] - Viewport1[0] + 1)) >> 12) / 8) + Viewport2[0] / 2; + // int screenY = (((y * (Viewport2[1] - Viewport1[1] + 1)) >> 12) / 8) + Viewport2[1] / 2; + // int screenX = (x * (Viewport2[0] - Viewport1[0] + 1)) / (2 * w) + Viewport2[0] / 2; + // int screenY = (y * (Viewport2[1] - Viewport1[1] + 1)) / (2 * w) + Viewport2[1] / 2; + + // int screenX = (x * 256) / (2 * w) + 128; + // int screenY = (y * 192) / (2 * w) + 96; + + int screenX = x * 128 / w + 128; + int screenY = -y * 96 / w + 96; + + // Console.WriteLine($"{screenX} {screenY}"); + + // 0,0 is bottom left on NDS + // screenY = 191 - screenY; + + SetPixel(screenX, screenY); + } + + // Console.WriteLine(VertexQueueFront.Entries); + + // while (VertexQueue.Entries >= 3) + // { + // for (int i = 0; i < 3; i++) + // { + // vertices[i] = VertexQueue.Pop(); + + // int x = vertices[i].Pos.Data[0]; + // int y = vertices[i].Pos.Data[1]; + // int z = vertices[i].Pos.Data[2]; + // int w = vertices[i].Pos.Data[3]; + + // if (w == 0) + // { + // w = 0x1000; + // } + + // // int screenX = (((x * (Viewport2[0] - Viewport1[0] + 1)) >> 12) / 8) + Viewport2[0] / 2; + // // int screenY = (((y * (Viewport2[1] - Viewport1[1] + 1)) >> 12) / 8) + Viewport2[1] / 2; + // int screenX = (x * (Viewport2[0] - Viewport1[0] + 1)) / (2 * w) + Viewport2[0] / 2; + // int screenY = (y * (Viewport2[1] - Viewport1[1] + 1)) / (2 * w) + Viewport2[1] / 2; + + // // 0,0 is bottom left on NDS + // screenY = 191 - screenY; + + // // Console.WriteLine($"{screenX}, {screenY}, {z}, {w}"); + + // vertices[i].Pos.Data[0] = screenX; + // vertices[i].Pos.Data[1] = screenY; + + // SetPixel(screenX, screenY); + // } + + // DrawLine(vertices[0].Pos.Data[0], vertices[0].Pos.Data[1], vertices[1].Pos.Data[0], vertices[1].Pos.Data[1]); + // DrawLine(vertices[1].Pos.Data[0], vertices[1].Pos.Data[1], vertices[2].Pos.Data[0], vertices[2].Pos.Data[1]); + // DrawLine(vertices[2].Pos.Data[0], vertices[2].Pos.Data[1], vertices[0].Pos.Data[0], vertices[0].Pos.Data[1]); + // } + + // var m = PositionStack.Current; + var m = ProjectionStack.Current; + + } + + void DrawLine(int x0, int y0, int x1, int y1) + { + bool low; + bool swap; + int dx0; + int dy0; + int dx1; + int dy1; + + if (Math.Abs(y1 - y0) < Math.Abs(x1 - x0)) + { + low = true; + swap = x0 > x1; + } + else + { + low = false; + swap = y0 > y1; + } + + if (swap) + { + dx0 = x1; + dy0 = y1; + dx1 = x0; + dy1 = y0; + } + else + { + dx0 = x0; + dy0 = y0; + dx1 = x1; + dy1 = y1; + } + + if (low) + { + int dx = dx1 - dx0; + int dy = dy1 - dy0; + + int yi = 1; + + if (dy < 0) + { + yi = -1; + dy = -dy; + } + + int d = (2 * dy) - dx; + int y = dy0; + + for (int x = dx0; x <= dx1; x++) + { + SetPixel(x, y); + if (d > 0) + { + y = y + yi; + d = d + (2 * (dy - dx)); + } + else + { + d = d + 2 * dy; + } + } + } + else + { + int dx = dx1 - dx0; + int dy = dy1 - dy0; + + int xi = 1; + + if (dx < 0) + { + xi = -1; + dx = -dx; + } + + int d = (2 * dx) - dy; + int x = dx0; + + for (int y = dy0; y <= dy1; y++) + { + SetPixel(x, y); + if (d > 0) + { + x = x + xi; + d = d + (2 * (dx - dy)); + } + else + { + d = d + 2 * dx; + } + } + } + } + + void SetPixel(int x, int y) + { + if ((uint)x >= 256 || (uint)y >= 192) + { + return; + } + + var screenIndex = y * 256 + x; + + Screen[screenIndex] = 0x7FFF; + } + + [Conditional("NEVER")] + public static void Debug(string s) + { + Console.WriteLine("3D: " + s); + } + + [Conditional("NEVER")] + public void Debug2(string s) + { + Console.WriteLine("[" + Hex(Nds.Cpu9.GetCurrentInstrAddr(), 8) + "] " + s); + } + } +} diff --git a/Assets/emulator/PpuNds3D.cs.meta b/Assets/emulator/PpuNds3D.cs.meta new file mode 100644 index 0000000..f8cfdf0 --- /dev/null +++ b/Assets/emulator/PpuNds3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5c1d666a4057424428483e53a56bdc1c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/PpuRenderer.cs b/Assets/emulator/PpuRenderer.cs new file mode 100644 index 0000000..aee9b29 --- /dev/null +++ b/Assets/emulator/PpuRenderer.cs @@ -0,0 +1,1707 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; +using static MyStruct; +using Avx2 = MyStruct; +using static OptimeGBA.Bits; +using static OptimeGBA.CoreUtil; +using static OptimeGBA.MemoryUtil; +namespace OptimeGBA +{ + public sealed unsafe class PpuRenderer + { + public int Width; + public int Height; + public Nds Nds; + public PpuRenderer(Nds nds, int width, int height) + { + Width = width; + Height = height; + Nds = nds; + + Backgrounds = new Background[4] { + new Background(Nds != null, 0), + new Background(Nds != null, 1), + new Background(Nds != null, 2), + new Background(Nds != null, 3), + }; + + Array.Fill(DebugEnableBg, true); + + int ScreenBufferSize = Width * Height; +#if UNSAFE + ScreenFront = MemoryUtil.AllocateUnmanagedArray16(ScreenBufferSize); + ScreenBack = MemoryUtil.AllocateUnmanagedArray16(ScreenBufferSize); + + // 16 wide to allow tiles to poke out on each side for efficiency + WinMasks = MemoryUtil.AllocateUnmanagedArray(Width + 16); + BgLo = MemoryUtil.AllocateUnmanagedArray32(width + 16); + BgHi = MemoryUtil.AllocateUnmanagedArray32(width + 16); +#else + ScreenFront = new ushort[ScreenBufferSize]; + ScreenBack = new ushort[ScreenBufferSize]; + + WinMasks = new byte[width + 16]; + BgLo = new uint[width + 16]; + BgHi = new uint[width + 16]; +#endif + ObjBuffer = new ObjPixel[Width]; + ObjWindowBuffer = new byte[Width]; + + for (uint i = 0; i < ScreenBufferSize; i++) + { + ScreenFront[i] = 0x7FFF; + ScreenBack[i] = 0x7FFF; + } + + if (nds == null) + { + DisplayMode = 1; + } + + // Load 3D placeholder + // Why do I waste time on useless crap like this + Stream img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-OpenTK.resources.3d-placeholder.raw"); + if (img == null) + { + img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-SDL.resources.3d-placeholder.raw"); + } + PlaceholderFor3D = new ushort[img.Length / 2]; + int val = 0; + int index = 0; + while (val != -1) + { + val = img.ReadByte(); + byte r = (byte)val; + val = img.ReadByte(); + byte g = (byte)val; + val = img.ReadByte(); + byte b = (byte)val; + val = img.ReadByte(); + byte a = (byte)val; + + // Crush it to RGB555 + r >>= 3; + g >>= 3; + b >>= 3; + + PlaceholderFor3D[index++] = (ushort)((b << 10) | (g << 5) | r); + } + + for (uint i = 0; i < Width; i++) + WinMasks[i + 8] = 0b111111; + } + + // RGB555 + public static ushort[] PlaceholderFor3D; + + // Internal State + public const int BYTES_PER_PIXEL = 4; + + public bool DebugForce3DLayer = false; + public bool RenderingDone = false; + + // RGB, 24-bit +#if UNSAFE + public ushort* ScreenFront; + public ushort* ScreenBack; + + public byte* WinMasks; + + public uint* BgLo; + public uint* BgHi; + + ~PpuRenderer() + { + MemoryUtil.FreeUnmanagedArray(ScreenFront); + MemoryUtil.FreeUnmanagedArray(ScreenBack); + + MemoryUtil.FreeUnmanagedArray(WinMasks); + MemoryUtil.FreeUnmanagedArray(BgLo); + MemoryUtil.FreeUnmanagedArray(BgHi); + } +#else + public ushort[] ScreenFront; + public ushort[] ScreenBack; + + public byte[] WinMasks; + + // Bytes 0-1: Color + // Byte 2: Flag + // Byte 3: Priority + public uint[] BgLo; + public uint[] BgHi; +#endif + + public byte[] Palettes = new byte[1024]; + public byte[] Oam = new byte[1024]; + + public ObjPixel[] ObjBuffer; + public byte[] ObjWindowBuffer; + + public uint TotalFrames; + + const uint CoarseBlockSize = 65536; + const uint CharBlockSize = 16384; + const uint MapBlockSize = 2048; + const uint MapBlockSizeAffineNds = 16384; + + public bool[] DebugEnableBg = new bool[4]; + public bool DebugEnableObj = true; + public bool DebugEnableRendering = true; + + public static uint[] ColorLut = GenerateRgb555To888Lut(false); + public static uint[] ColorLutCorrected = GenerateRgb555To888Lut(true); + + public static uint[] GenerateRgb555To888Lut(bool colorCorrection) + { + uint[] lut = new uint[32768]; + for (uint i = 0; i < 32768; i++) + { + lut[i] = Rgb555To888(i, colorCorrection); + } + return lut; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Rgb555To888(uint data, bool colorCorrection) + { + byte r = (byte)((data >> 0) & 0b11111); + byte g = (byte)((data >> 5) & 0b11111); + byte b = (byte)((data >> 10) & 0b11111); + + if (colorCorrection) + { + // byuu color correction, customized for my tastes + double ppuGamma = 4.0, outGamma = 3.0; + + double lb = Math.Pow(b / 31.0, ppuGamma); + double lg = Math.Pow(g / 31.0, ppuGamma); + double lr = Math.Pow(r / 31.0, ppuGamma); + + byte fr = (byte)(Math.Pow((0 * lb + 10 * lg + 245 * lr) / 255, 1 / outGamma) * 0xFF); + byte fg = (byte)(Math.Pow((20 * lb + 230 * lg + 5 * lr) / 255, 1 / outGamma) * 0xFF); + byte fb = (byte)(Math.Pow((230 * lb + 5 * lg + 20 * lr) / 255, 1 / outGamma) * 0xFF); + + return (uint)((0xFF << 24) | (fb << 16) | (fg << 8) | (fr << 0)); + } + else + { + byte fr = (byte)((255 / 31) * r); + byte fg = (byte)((255 / 31) * g); + byte fb = (byte)((255 / 31) * b); + + return (uint)((0xFF << 24) | (fb << 16) | (fg << 8) | (fr << 0)); + } + } + + + // BGCNT + public Background[] Backgrounds; + + // DISPCNT + public uint BgMode; + public bool CgbMode; + public bool DisplayFrameSelect; + public bool HBlankIntervalFree; + public bool ObjCharOneDimensional; + public bool ForcedBlank; + public bool[] ScreenDisplayBg = new bool[4]; + public bool ScreenDisplayObj; + public bool Window0DisplayFlag; + public bool Window1DisplayFlag; + public bool ObjWindowDisplayFlag; + public bool AnyWindowEnabled = false; + + // DISPCNT - NDS Exclusive + public bool Bg0Is3D; + public bool BitmapObjShape; + public bool BitmapObjMapping; + public uint DisplayMode; + public uint LcdcVramBlock; + public uint TileObj1DBoundary; + public bool BitmapObj1DBoundary; + public uint CharBaseBlockCoarse; + public uint MapBaseBlockCoarse; + public bool BgExtendedPalettes; + public bool ObjExtendedPalettes; + + // WIN0H + public byte Win0HRight; + public byte Win0HLeft; + // WIN1H + public byte Win1HRight; + public byte Win1HLeft; + + // WIN0V + public byte Win0VBottom; + public byte Win0VTop; + + // WIN1V + public byte Win1VBottom; + public byte Win1VTop; + + + // WININ + public byte Win0InEnable; + public byte Win1InEnable; + + // WINOUT + public byte WinOutEnable; + public byte WinObjEnable; + + // BLDCNT + public BlendEffect BlendEffect = 0; + public uint Target1Flags; + public uint Target2Flags; + + // BLDALPHA + public uint BlendACoeff; + public uint BlendBCoeff; + + // BLDY + public uint BlendBrightness; + + // MOSAIC + public uint BgMosaicX; + public uint BgMosaicY; + public uint ObjMosaicX; + public uint ObjMosaicY; + + public uint BgMosaicYCounter; + public uint ObjMosaicYCounter; + + // Raw register values + public ushort WININValue; + public ushort WINOUTValue; + public ushort BLDCNTValue; + public uint BLDALPHAValue; + + public void RenderScanlineGba(uint vcount, byte[] vramArr) + { + if (!ForcedBlank) + { + fixed (byte* vram = vramArr) + { + if (BgMode <= 2) + { + PrepareBackgroundAndWindow(vcount); + } + + switch (BgMode) + { + case 0: + case 1: + case 2: + RenderBgModes(vcount, vram); + break; + case 3: + RenderMode3(vcount, vram); + break; + case 4: + RenderMode4(vcount, vram); + break; + } + + if (BgMode <= 2) + { + Composite(vcount); + if (DebugEnableObj && ScreenDisplayObj && vcount != 159) RenderObjs(vcount + 1, vram); + } + } + } + else + { + RenderWhiteScanline(vcount); + } + } + + public void RenderScanlineNds(uint vcount, byte[] bgVramArr, byte[] objVramArr) + { + if (!ForcedBlank) + { + fixed (byte* bgVram = bgVramArr, objVram = objVramArr) + { + switch (DisplayMode) + { + case 1: // Regular rendering + PrepareBackgroundAndWindow(vcount); + RenderBgModes(vcount, bgVram); + + if (DebugForce3DLayer) { + uint srcBase = (uint)(vcount * Width); + + for (uint i = 0; i < Width; i++) + { + BgLo[i + 8] = BgHi[i + 8]; + BgHi[i + 8] = (uint)(Nds.Ppu3D.Screen[srcBase + i]); + } + } + + Composite(vcount); + if (DebugEnableObj && ScreenDisplayObj && vcount != 191) RenderObjs(vcount + 1, objVram); + break; + case 2: // LCDC Mode + RenderMode3(vcount, bgVram); + break; + } + } + } + else + { + RenderWhiteScanline(vcount); + } + } + + public void RunVblankOperations() + { + Backgrounds[2].CopyAffineParams(); + Backgrounds[3].CopyAffineParams(); + + BgMosaicYCounter = BgMosaicY; + ObjMosaicYCounter = ObjMosaicY; + +#if OPENTK_DEBUGGER + PrepareBackground(); +#endif + } + + public void IncrementMosaicCounters() + { + if (++BgMosaicYCounter > BgMosaicY) + { + BgMosaicYCounter = 0; + } + + if (++ObjMosaicYCounter > ObjMosaicY) + { + ObjMosaicYCounter = 0; + } + } + + public void SwapBuffers() + { + var temp = ScreenBack; + ScreenBack = ScreenFront; + ScreenFront = temp; + } + + public int[] BgList = new int[4]; + public Background[] BgRefList = new Background[4]; + public uint BgCount = 0; + public bool BackgroundSettingsDirty = true; + + public void PrepareBackgroundAndWindow(uint vcount) + { + if (BackgroundSettingsDirty) + { + PrepareBackground(); + } + + bool win0InsideY = (byte)(vcount - Win0VTop) < (byte)(Win0VBottom - Win0VTop) && Window0DisplayFlag; + bool win1InsideY = (byte)(vcount - Win1VTop) < (byte)(Win1VBottom - Win1VTop) && Window1DisplayFlag; + + byte win0ThresholdX = (byte)(Win0HRight - Win0HLeft); + byte win1ThresholdX = (byte)(Win1HRight - Win1HLeft); + + if (!win0InsideY) win0ThresholdX = 0; + if (!win1InsideY) win1ThresholdX = 0; + + byte win0HPos = (byte)(-Win0HLeft); + byte win1HPos = (byte)(-Win1HLeft); + + // Erase with priority 4, backdrop flag, and color 0; + uint eraseColor = (uint)((4 << 24) | ((byte)BlendFlag.Backdrop << 16) | LookupPalette(0)); + if (AnyWindowEnabled) + { + for (uint i = 0; i < Width; i++) + { + byte val = WinOutEnable; + + if (win0HPos < win0ThresholdX) + { + val = Win0InEnable; + } + else if (win1HPos < win1ThresholdX) + { + val = Win1InEnable; + } + else if (ObjWindowBuffer[i] != 0) + { + val = WinObjEnable; + } + + win0HPos++; + win1HPos++; + + WinMasks[i + 8] = val; + + // Also prepare backgrounds arrays in this loop + BgHi[i + 8] = eraseColor; + } + } + else + { + for (uint i = 0; i < Width; i++) + { + WinMasks[i + 8] = 0b111111; + + BgHi[i + 8] = eraseColor; + } + } + } + + public void PrepareBackground() + { + BgCount = 0; + for (int bg = 0; bg < 4; bg++) + { + // -1 means disabled + // Look up backgrounds in reverse order to ensure backgrounds are in correct order + // (backgrounds carry a specific render order even if they are the same priority) + int invBg = 3 - bg; + BgList[bg] = -1; + BgList[BgCount] = invBg; + if (BgIsEnabled(invBg)) + { + BgCount++; + } + } + + // Insertion sort backgrounds according to priority + int key; + int j; + for (int i = 1; i < BgCount; i++) + { + key = (int)Backgrounds[BgList[i]].Priority; + j = i - 1; + + while (j >= 0 && Backgrounds[BgList[j]].Priority < key) + { + Swap(ref BgList[j + 1], ref BgList[j]); + j--; + } + } + + // Look up references for each background + for (int i = 0; i < BgCount; i++) + { + BgRefList[i] = Backgrounds[BgList[i]]; + } + + Backgrounds[0].Mode = BackgroundMode.Char; + Backgrounds[1].Mode = BackgroundMode.Char; + Backgrounds[2].Mode = BackgroundMode.Char; + Backgrounds[3].Mode = BackgroundMode.Char; + if (Nds == null) + { + switch (BgMode) + { + case 1: + Backgrounds[2].Mode = BackgroundMode.Affine; + break; + case 2: + Backgrounds[2].Mode = BackgroundMode.Affine; + Backgrounds[3].Mode = BackgroundMode.Affine; + break; + } + } + else + { + if (Bg0Is3D) + { + Backgrounds[0].Mode = BackgroundMode.Display3D; + } + + switch (BgMode) + { + case 1: + Backgrounds[3].Mode = BackgroundMode.Affine; + break; + case 2: + Backgrounds[2].Mode = BackgroundMode.Affine; + Backgrounds[3].Mode = BackgroundMode.Affine; + break; + case 3: + Backgrounds[3].Mode = BackgroundMode.Extended; + break; + case 4: + Backgrounds[2].Mode = BackgroundMode.Affine; + Backgrounds[3].Mode = BackgroundMode.Extended; + break; + case 5: + Backgrounds[2].Mode = BackgroundMode.Extended; + Backgrounds[3].Mode = BackgroundMode.Extended; + break; + case 6: + Backgrounds[0].Mode = BackgroundMode.Display3D; + Backgrounds[2].Mode = BackgroundMode.Large; + break; + } + } + + // Extended mode backgrounds have extra options + for (int i = 2; i < 4; i++) + { + var bg = Backgrounds[i]; + if (bg.Mode == BackgroundMode.Extended) + { + if (bg.AffineBitmap) + { + if (bg.AffineBitmapFullColor) + { + bg.Mode = BackgroundMode.AffineFullColorBitmap; + } + else + { + bg.Mode = BackgroundMode.Affine256ColorBitmap; + } + } + else + { + // TODO: implement affine BGs with 16-bit BG map entries + } + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort LookupPalette(uint index) + { + return GetUshort(Palettes, index * 2); + } + + public readonly static uint[] CharBlockHeightTable = { + 0, 0, // Size 0 - 256x256 + 0, 0, // Size 1 - 512x256 + 0, 1, // Size 2 - 256x512 + 0, 2, // Size 3 - 512x512 + }; + public readonly static uint[] CharBlockWidthTable = { + 0, 0, // Size 0 - 256x256 + 0, 1, // Size 1 - 512x256 + 0, 0, // Size 2 - 256x512 + 0, 1, // Size 3 - 512x512 + }; + + public readonly static uint[] CharWidthTable = { 256, 512, 256, 512 }; + public readonly static uint[] CharHeightTable = { 256, 256, 512, 512 }; + + public void RenderCharBackground(uint vcount, byte* vram, Background bg) + { + bool enableMosaicX = bg.EnableMosaic && BgMosaicX != 0; + fixed (byte* palettes = Palettes) + { +#if UNSAFE + if (enableMosaicX) + { + _RenderCharBackground(vcount, vram, palettes, WinMasks, BgHi, BgLo, bg, true); + } + else + { + _RenderCharBackground(vcount, vram, palettes, WinMasks, BgHi, BgLo, bg, false); + } +#else + fixed (byte* winMasks = WinMasks) + { + fixed (uint* hi = BgHi, lo = BgLo) + { + if (enableMosaicX) + { + _RenderCharBackground(vcount, vram, palettes, winMasks, hi, lo, bg, true); + } + else + { + _RenderCharBackground(vcount, vram, palettes, winMasks, hi, lo, bg, false); + } + } + } +#endif + } + } + + //[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + //private void _RenderCharBackground( + // uint vcount, byte* vram, + // byte* palettes, + // byte* winMasks, + // uint* hi, uint* lo, + // Background bg, bool mosaicX + // ) + //{ + // uint charBase = bg.CharBaseBlock * CharBlockSize + CharBaseBlockCoarse * CoarseBlockSize; + // uint mapBase = bg.MapBaseBlock * MapBlockSize + MapBaseBlockCoarse * CoarseBlockSize; + + // uint pixelY = bg.VerticalOffset + vcount; + // if (bg.EnableMosaic) + // { + // pixelY -= BgMosaicYCounter; + // } + // uint pixelYWrapped = pixelY & 255; + + // uint screenSizeBase = bg.ScreenSize * 2; + // uint verticalOffsetBlocks = CharBlockHeightTable[screenSizeBase + ((pixelY & 511) >> 8)]; + // uint mapVertOffset = MapBlockSize * verticalOffsetBlocks; + + // uint tileY = pixelYWrapped >> 3; + // uint intraTileY = pixelYWrapped & 7; + + // uint pixelX = bg.HorizontalOffset; + // uint intraTileX = bg.HorizontalOffset & 7; + // uint lineIndex = 8 - intraTileX; + + // uint tilesToRender = (uint)(Width / 8); + // if (lineIndex < 8) tilesToRender++; + + // uint mosaicXCounter = BgMosaicX; + + // // Every byte of these vectors are filled + // Vector256 metaVec = Vector256.Create((bg.Priority << 8) | (1 << bg.Id)); + + // for (uint tile = 0; tile < tilesToRender; tile++) + // { + // uint pixelXWrapped = pixelX & 255; + + // // 2 bytes per tile + // uint tileX = pixelXWrapped >> 3; + // uint horizontalOffsetBlocks = CharBlockWidthTable[screenSizeBase + ((pixelX & 511) >> 8)]; + // uint mapHoriOffset = MapBlockSize * horizontalOffsetBlocks; + // uint mapEntryIndex = mapBase + mapVertOffset + mapHoriOffset + tileY * 64 + tileX * 2; + // uint mapEntry = GetUshort(vram, mapEntryIndex); + + // uint tileNumber = mapEntry & 1023; // 10 bits + // bool xFlip = BitTest(mapEntry, 10); + // bool yFlip = BitTest(mapEntry, 11); + + // uint effectiveIntraTileY = intraTileY; + // if (yFlip) + // { + // effectiveIntraTileY ^= 7; + // } + + // Vector256 clearMaskVec; + // Vector256 indicesVec = Vector256.Zero; + // uint paletteRow = 0; + + // if (bg.Use8BitColor) + // { + // clearMaskVec = Vector256.Create(0xFFU); + + // uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8; + // ulong data = GetUlong(vram, vramTileAddr); + + // if (data != 0) + // { + // indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32(); + // if (xFlip) + // { + // // First, reverse within 128-bit lanes + // indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11); + // // Then, swap upper and lower halves + // indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1); + // } + // indicesVec = Avx2.And(indicesVec, clearMaskVec); + // } + // else + // { + // pixelX += 8; + // lineIndex += 8; + // continue; + // } + // } + // else + // { + // clearMaskVec = Vector256.Create(0xFU); + + // paletteRow = (mapEntry >> 12) & 0xF; + // uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4; + + // uint data = GetUint(vram, vramTileAddr); + + // if (data != 0) + // { + // Vector256 shifts; + // if (xFlip) + // { + // shifts = Vector256.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U); + // } + // else + // { + // shifts = Vector256.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U); + // } + // indicesVec = Vector256.Create(data); + // indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts); + // indicesVec = Avx2.And(indicesVec, clearMaskVec); + // } + // else + // { + // pixelX += 8; + // lineIndex += 8; + // continue; + // } + // } + + // Vector256 color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort)); + // color = Avx2.And(color, Vector256.Create(0xFFFF)); + // // Weave metadata (priority, ID) into color data + // color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16)); + + // Vector256 winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex)); + // winMask = Avx2.And(winMask, metaVec); + // winMask = Avx2.CompareEqual(winMask, Vector256.Zero); + // // Get important color bits + // Vector256 clear = Avx2.And(indicesVec, clearMaskVec).AsInt32(); + // // Are those bits clear? + // clear = Avx2.CompareEqual(clear, Vector256.Zero); + // // Merge with window mask + // winMask = Avx2.Or(winMask, clear); + // winMask = Avx2.Xor(winMask, Vector256.Create(0xFFFFFFFF).AsInt32()); + + // // Push back covered pixels from hi to lo + // Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex))); + // Avx2.MaskStore((int*)(hi + lineIndex), winMask, color); + + // pixelX += 8; + // lineIndex += 8; + // } + //} + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void _RenderCharBackground( + uint vcount, byte* vram, + byte* palettes, + byte* winMasks, + uint* hi, uint* lo, + Background bg, bool mosaicX + ) + { + uint charBase = bg.CharBaseBlock * CharBlockSize + CharBaseBlockCoarse * CoarseBlockSize; + uint mapBase = bg.MapBaseBlock * MapBlockSize + MapBaseBlockCoarse * CoarseBlockSize; + + uint pixelY = bg.VerticalOffset + vcount; + if (bg.EnableMosaic) + { + pixelY -= BgMosaicYCounter; + } + uint pixelYWrapped = pixelY & 255; + + uint screenSizeBase = bg.ScreenSize * 2; + uint verticalOffsetBlocks = CharBlockHeightTable[screenSizeBase + ((pixelY & 511) >> 8)]; + uint mapVertOffset = MapBlockSize * verticalOffsetBlocks; + + uint tileY = pixelYWrapped >> 3; + uint intraTileY = pixelYWrapped & 7; + + uint pixelX = bg.HorizontalOffset; + uint intraTileX = bg.HorizontalOffset & 7; + uint lineIndex = 8 - intraTileX; + + uint tilesToRender = (uint)(Width / 8); + if (lineIndex < 8) tilesToRender++; + + uint mosaicXCounter = BgMosaicX; + + // Every byte of these vectors are filled + Vector256 metaVec = Vector256.Create((bg.Priority << 8) | (1 << bg.Id)); + + for (uint tile = 0; tile < tilesToRender; tile++) + { + uint pixelXWrapped = pixelX & 255; + + // 2 bytes per tile + uint tileX = pixelXWrapped >> 3; + uint horizontalOffsetBlocks = CharBlockWidthTable[screenSizeBase + ((pixelX & 511) >> 8)]; + uint mapHoriOffset = MapBlockSize * horizontalOffsetBlocks; + uint mapEntryIndex = mapBase + mapVertOffset + mapHoriOffset + tileY * 64 + tileX * 2; + uint mapEntry = GetUshort(vram, mapEntryIndex); + + uint tileNumber = mapEntry & 1023; // 10 bits + bool xFlip = BitTest(mapEntry, 10); + bool yFlip = BitTest(mapEntry, 11); + + uint effectiveIntraTileY = intraTileY; + if (yFlip) + { + effectiveIntraTileY ^= 7; + } + + Vector256 clearMaskVec; + Vector256 indicesVec = Vector256.Zero; + uint paletteRow = 0; + + if (bg.Use8BitColor) + { + clearMaskVec = Vector256.Create(0xFFU); + + uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8; + ulong data = GetUlong(vram, vramTileAddr); + + if (data != 0) + { + //indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32(); + indicesVec = Avx2.ConvertToVector256Int32(data).AsUInt32(); + if (xFlip) + { + // First, reverse within 128-bit lanes + indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11); + // Then, swap upper and lower halves + indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1); + } + indicesVec = Avx2.And(indicesVec, clearMaskVec); + } + else + { + pixelX += 8; + lineIndex += 8; + continue; + } + } + else + { + clearMaskVec = Vector256.Create(0xFU); + + paletteRow = (mapEntry >> 12) & 0xF; + uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4; + + uint data = GetUint(vram, vramTileAddr); + + if (data != 0) + { + Vector256 shifts; + if (xFlip) + { + shifts = Vector256.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U); + } + else + { + shifts = Vector256.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U); + } + indicesVec = Vector256.Create(data); + indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts); + indicesVec = Avx2.And(indicesVec, clearMaskVec); + } + else + { + pixelX += 8; + lineIndex += 8; + continue; + } + } + + Vector256 color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort)); + color = Avx2.And(color, Vector256.Create(0xFFFF)); + // Weave metadata (priority, ID) into color data + color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16)); + + Vector256 winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex)); + winMask = Avx2.And(winMask, metaVec); + winMask = Avx2.CompareEqual(winMask, Vector256.Zero); + // Get important color bits + Vector256 clear = Avx2.And(indicesVec, clearMaskVec).AsInt32(); + // Are those bits clear? + clear = Avx2.CompareEqual(clear, Vector256.Zero); + // Merge with window mask + winMask = Avx2.Or(winMask, clear); + winMask = Avx2.Xor(winMask, Vector256.Create(0xFFFFFFFF).AsInt32()); + + // Push back covered pixels from hi to lo + Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex))); + Avx2.MaskStore((int*)(hi + lineIndex), winMask, color); + + pixelX += 8; + lineIndex += 8; + } + } + + public readonly static int[] AffineSizeShiftTable = { 7, 8, 9, 10 }; + public readonly static uint[] AffineSizeTable = { 128, 256, 512, 1024 }; + public readonly static uint[] AffineTileSizeTable = { 16, 32, 64, 128 }; + public readonly static uint[] AffineSizeMask = { 127, 255, 511, 1023 }; + + public void RenderAffineBackground(uint vcount, byte* vram, Background bg) + { + uint charBase = bg.CharBaseBlock * CharBlockSize; + uint mapBase = bg.MapBaseBlock * MapBlockSize; + + ushort meta = bg.GetMeta(); + + int posX = bg.AffinePosX; + int posY = bg.AffinePosY; + + uint size = AffineSizeTable[bg.ScreenSize]; + uint sizeMask = AffineSizeMask[bg.ScreenSize]; + uint tileSize = AffineTileSizeTable[bg.ScreenSize]; + + for (uint p = 0; p < Width; p++) + { + uint pixelX = (uint)((posX >> 8) & 0x7FFFF); + uint pixelY = (uint)((posY >> 8) & 0x7FFFF); + + posX += bg.AffineA; + posY += bg.AffineC; + + if (!bg.OverflowWrap && (pixelX >= size || pixelY >= size)) + { + continue; + } + + pixelX &= sizeMask; + pixelY &= sizeMask; + + uint tileX = pixelX >> 3; + uint intraTileX = pixelX & 7; + + uint tileY = pixelY >> 3; + uint intraTileY = pixelY & 7; + + // 1 byte per tile + uint mapEntryIndex = mapBase + (tileY * tileSize) + (tileX * 1); + uint tileNumber = vram[mapEntryIndex]; + + // Always 256color + // 256 color, 64 bytes per tile, 8 bytes per row + uint vramAddr = charBase + (tileNumber * 64) + (intraTileY * 8) + (intraTileX / 1); + byte vramValue = vram[vramAddr]; + + if (vramValue != 0) + { + PlaceBgPixel(p + 8, LookupPalette(vramValue), meta); + } + } + + bg.AffinePosX += bg.AffineB; + bg.AffinePosY += bg.AffineD; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PlaceBgPixel(uint lineIndex, ushort color, ushort meta) + { + if ((WinMasks[lineIndex] & meta) != 0) + { + BgLo[lineIndex] = BgHi[lineIndex]; + BgHi[lineIndex] = (uint)(color | ((uint)meta << 16)); + } + } + + public readonly static uint[] ObjSizeTable = { + // Square + 8, 16, 32, 64, + 8, 16, 32, 64, + + // Rectangular 1 + 16, 32, 32, 64, + 8, 8, 16, 32, + + // Rectangular 2 + 8, 8, 16, 32, + 16, 32, 32, 64, + + // Invalid + 0, 0, 0, 0, + 0, 0, 0, 0, + }; + + public void RenderObjs(uint vcount, byte* vram) + { + // OAM address for the last sprite + uint oamBase = 0; + for (int s = 0; s < 128; s++, oamBase += 8) + { + uint attr0 = (uint)(Oam[oamBase + 1] << 8 | Oam[oamBase + 0]); + uint attr1 = (uint)(Oam[oamBase + 3] << 8 | Oam[oamBase + 2]); + uint attr2 = (uint)(Oam[oamBase + 5] << 8 | Oam[oamBase + 4]); + + uint yPos = attr0 & 255; + bool affine = BitTest(attr0, 8); + ObjMode mode = (ObjMode)((attr0 >> 10) & 0b11); + bool mosaic = BitTest(attr0, 12); + bool use8BitColor = BitTest(attr0, 13); + ObjShape shape = (ObjShape)((attr0 >> 14) & 0b11); + + uint xPos = attr1 & 511; + bool xFlip = BitTest(attr1, 12) && !affine; + bool yFlip = BitTest(attr1, 13) && !affine; + + uint objSize = (attr1 >> 14) & 0b11; + + uint tileNumber = attr2 & 1023; + uint palette = (attr2 >> 12) & 15; + + uint xSize = ObjSizeTable[((int)shape * 8) + 0 + objSize]; + uint ySize = ObjSizeTable[((int)shape * 8) + 4 + objSize]; + + int yEnd = ((int)yPos + (int)ySize) & 255; + uint screenLineBase = xPos; + + bool disabled = BitTest(attr0, 9); + + byte priority = (byte)((attr2 >> 10) & 0b11); + + bool render = false; + if (!disabled && !affine) + { + if ((vcount >= yPos && vcount < yEnd) || (yEnd < yPos && vcount < yEnd)) + { + render = true; + } + } + else if (affine) + { + if (disabled) + { + yEnd += (int)ySize; + } + + if ((vcount >= yPos && vcount < yEnd) || (yEnd < yPos && vcount < yEnd)) + { + render = true; + } + } + + if ((byte)mode == 3 || (byte)shape == 3) render = false; + + if (!render) continue; + + // y relative to the object itself + int objPixelY = (int)(vcount - yPos) & 255; + + if (yFlip) + { + objPixelY = (int)ySize - objPixelY - 1; + } + + + // Tile numbers are halved in 256-color mode + if (use8BitColor) tileNumber >>= 1; + + if (!affine) + { + for (uint x = 0; x < xSize; x++) + { + if (screenLineBase < Width) + { + int objPixelX = (int)x; + + if (xFlip) + { + objPixelX = (int)(xSize - objPixelX - 1); + } + + RenderObjPixel(vram, objPixelX, objPixelY, tileNumber, xSize, use8BitColor, screenLineBase, palette, priority, mode); + } + screenLineBase = (screenLineBase + 1) % 512; + } + } + else + { + uint renderXSize = xSize; + + bool doubleSize = BitTest(attr0, 9); + if (doubleSize) + { + renderXSize *= 2; + } + + uint parameterId = (attr1 >> 9) & 0b11111; + uint pBase = parameterId * 32; + + short pA = (short)GetUshort(Oam, pBase + 6); + short pB = (short)GetUshort(Oam, pBase + 14); + short pC = (short)GetUshort(Oam, pBase + 22); + short pD = (short)GetUshort(Oam, pBase + 30); + + uint xofs; + uint yofs; + + int xfofs; + int yfofs; + + if (!doubleSize) + { + xofs = xSize / 2; + yofs = ySize / 2; + + xfofs = 0; + yfofs = 0; + } + else + { + xofs = xSize; + yofs = ySize; + + xfofs = -(int)xofs / 2; + yfofs = -(int)yofs / 2; + } + + // Left edge + int origXEdge0 = (int)(0 - xofs); + int origY = (int)(objPixelY - yofs); + + // Calculate starting parameters for matrix multiplications + int shiftedXOfs = (int)(xofs + xfofs << 8); + int shiftedYOfs = (int)(yofs + yfofs << 8); + int pBYOffset = pB * origY + shiftedXOfs; + int pDYOffset = pD * origY + shiftedYOfs; + + int objPixelXEdge0 = (int)(pA * origXEdge0 + pBYOffset); + int objPixelYEdge0 = (int)(pC * origXEdge0 + pDYOffset); + + for (int x = 0; x < renderXSize; x++) + { + if (screenLineBase < Width) + { + uint lerpedObjPixelX = (uint)(objPixelXEdge0 >> 8); + uint lerpedObjPixelY = (uint)(objPixelYEdge0 >> 8); + + if (lerpedObjPixelX < xSize && lerpedObjPixelY < ySize) + { + RenderObjPixel(vram, (int)lerpedObjPixelX, (int)lerpedObjPixelY, tileNumber, xSize, use8BitColor, screenLineBase, palette, priority, mode); + } + } + objPixelXEdge0 += pA; + objPixelYEdge0 += pC; + + screenLineBase = (screenLineBase + 1) % 512; + } + } + } + } + + public readonly ushort[] NdsCharObjBoundary = new ushort[] { 32, 64, 128, 256 }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RenderObjPixel(byte* vram, int objX, int objY, uint tile, uint width, bool use8BitColor, uint x, uint palette, byte priority, ObjMode mode) + { + uint intraTileX = (uint)(objX & 7); + uint intraTileY = (uint)(objY & 7); + + uint tileX = (uint)(objX / 8); + uint tileY = (uint)(objY / 8); + + uint charBase = Nds != null ? 0U : 0x10000U; + + tile <<= (int)TileObj1DBoundary; + uint effectiveTileNumber = (uint)(tile + tileX); + + + if (ObjCharOneDimensional) + { + effectiveTileNumber += tileY * (width / 8); + } + else + { + if (use8BitColor) + { + effectiveTileNumber += 16 * tileY; + } + else + { + effectiveTileNumber += 32 * tileY; + } + } + + if (use8BitColor) + { + // 256 color, 64 bytes per tile, 8 bytes per row + uint vramAddr = charBase + (effectiveTileNumber * 64) + (intraTileY * 8) + (intraTileX / 1); + uint vramValue = vram[vramAddr]; + + byte finalColor = (byte)vramValue; + + if (finalColor != 0) + { + PlaceObjPixel(x, LookupPalette(finalColor), finalColor, priority, mode); + } + } + else + { + // 16 color, 32 bytes per tile, 4 bytes per row + uint vramAddr = charBase + (effectiveTileNumber * 32) + (intraTileY * 4) + (intraTileX / 2); + uint vramValue = vram[vramAddr]; + // Lower 4 bits is left pixel, upper 4 bits is right pixel + uint color = (vramValue >> (int)((intraTileX & 1) * 4)) & 0xF; + byte finalColor = (byte)(palette * 16 + color); + + if (color != 0) + { + PlaceObjPixel(x, LookupPalette(finalColor), finalColor, priority, mode); + } + + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PlaceObjPixel(uint x, ushort color, byte paletteIndex, byte priority, ObjMode mode) + { + switch (mode) + { + case ObjMode.Normal: + if (priority < ObjBuffer[x].Priority) + { + ObjBuffer[x] = new ObjPixel(color, paletteIndex, priority, mode); + } + break; + case ObjMode.Translucent: + if (priority < ObjBuffer[x].Priority) + { + ObjBuffer[x] = new ObjPixel(color, paletteIndex, priority, mode); + } + ObjBuffer[x].Priority = priority; + break; + default: + if (ObjWindowDisplayFlag) + { + ObjWindowBuffer[x] = 1; + } + break; + } + } + + public void Composite(uint vcount) + { + uint screenBase = (uint)(vcount * Width); + + for (int i = 0; i < Width; i++) + { + uint winMask = WinMasks[i + 8]; + ObjPixel objPixel = ObjBuffer[i]; + + uint hi = BgHi[i + 8]; + uint lo = BgLo[i + 8]; + ushort hiColor = (ushort)hi; + ushort loColor = (ushort)lo; + BlendFlag hiPixelFlag = (BlendFlag)((byte)(hi >> 16)); + BlendFlag loPixelFlag = (BlendFlag)((byte)(lo >> 16)); + uint objPaletteIndex = objPixel.PaletteIndex + 256U; + + uint effectiveTarget1Flags = Target1Flags; + BlendEffect effectiveBlendEffect = BlendEffect; + + if (objPaletteIndex != 256 && (winMask & (uint)WindowFlag.Obj) != 0) + { + byte hiPrio = (byte)(hi >> 24); + byte loPrio = (byte)(lo >> 24); + + if (objPixel.Priority <= hiPrio) + { + loColor = hiColor; + loPixelFlag = hiPixelFlag; + + hiColor = LookupPalette(objPaletteIndex); + hiPixelFlag = BlendFlag.Obj; + } + else if (objPixel.Priority <= loPrio) + { + loColor = LookupPalette(objPaletteIndex); + loPixelFlag = BlendFlag.Obj; + } + + if (objPixel.Mode == ObjMode.Translucent) + { + effectiveTarget1Flags |= (uint)BlendFlag.Obj; + effectiveBlendEffect = BlendEffect.Blend; + winMask |= (uint)WindowFlag.ColorMath; + } + } + + if ( + effectiveBlendEffect != BlendEffect.None && + (effectiveTarget1Flags & (uint)hiPixelFlag) != 0 && + (winMask & (uint)WindowFlag.ColorMath) != 0 + ) + { + byte r1 = (byte)((hiColor >> 0) & 0x1F); + byte g1 = (byte)((hiColor >> 5) & 0x1F); + byte b1 = (byte)((hiColor >> 10) & 0x1F); + + byte fr = r1; + byte fg = g1; + byte fb = b1; + switch (BlendEffect) + { + case BlendEffect.Blend: + if ((Target2Flags & (uint)loPixelFlag) != 0) + { + byte r2 = (byte)((loColor >> 0) & 0x1F); + byte g2 = (byte)((loColor >> 5) & 0x1F); + byte b2 = (byte)((loColor >> 10) & 0x1F); + + fr = (byte)((Math.Min(511U, r1 * BlendACoeff + r2 * BlendBCoeff) >> 4) & 0x1FU); + fg = (byte)((Math.Min(511U, g1 * BlendACoeff + g2 * BlendBCoeff) >> 4) & 0x1FU); + fb = (byte)((Math.Min(511U, b1 * BlendACoeff + b2 * BlendBCoeff) >> 4) & 0x1FU); + } + break; + case BlendEffect.Lighten: + fr = (byte)((r1 + (((31 - r1) * BlendBrightness) >> 4)) & 0x1FU); + fg = (byte)((g1 + (((31 - g1) * BlendBrightness) >> 4)) & 0x1FU); + fb = (byte)((b1 + (((31 - b1) * BlendBrightness) >> 4)) & 0x1FU); + break; + case BlendEffect.Darken: + fr = (byte)((r1 - ((r1 * BlendBrightness) >> 4)) & 0x1FU); + fg = (byte)((g1 - ((g1 * BlendBrightness) >> 4)) & 0x1FU); + fb = (byte)((b1 - ((b1 * BlendBrightness) >> 4)) & 0x1FU); + break; + } + + ScreenBack[screenBase++] = (ushort)((fb << 10) | (fg << 5) | fr); + } + else + { + ScreenBack[screenBase++] = hiColor; + } + + // It's the frontend's responsibility to convert rgb555 to rgb888 + + // Use this loop as an opportunity to clear the sprite buffer + ObjBuffer[i].Color = 0; + ObjBuffer[i].PaletteIndex = 0; + ObjBuffer[i].Priority = 4; + ObjWindowBuffer[i] = 0; + } + } + + public bool BgIsEnabled(int id) + { + if (Nds == null) + { + switch (BgMode) + { + case 1: + if (id == 3) return false; + break; + case 2: + if (id == 0) return false; + if (id == 1) return false; + break; + } + } + else + { + if (BgMode == 6 && (id == 0 || id == 2)) return false; + } + + return ScreenDisplayBg[id] && DebugEnableBg[id]; + } + + public void RenderBgModes(uint vcount, byte* vram) + { + for (uint i = 0; i < BgCount; i++) + { + var bg = BgRefList[i]; + switch (bg.Mode) + { + case BackgroundMode.Char: + RenderCharBackground(vcount, vram, bg); + break; + case BackgroundMode.Affine: + RenderAffineBackground(vcount, vram, bg); + break; + case BackgroundMode.AffineFullColorBitmap: + RenderAffineBitmapBackground(vcount, vram, bg, true); + break; + case BackgroundMode.Affine256ColorBitmap: + RenderAffineBitmapBackground(vcount, vram, bg, false); + break; + case BackgroundMode.Display3D: + Render3DBackground(vcount, vram, bg); + break; + } + } + } + + public void Render3DBackground(uint vcount, byte* vram, Background bg) + { + uint srcBase = (uint)(vcount * Width); + + ushort meta = bg.GetMeta(); + + for (uint i = 0; i < Width; i++) + { + if (Nds.Ppu3D.Screen[srcBase + i] != 0) + PlaceBgPixel(i + 8, Nds.Ppu3D.Screen[srcBase + i], meta); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RenderAffineBitmapBackground(uint vcount, byte* vram, Background bg, bool fullColor) + { + // TODO: actually implement rendering affine here + // TODO: implement 256-color mode (no fullColor) + uint screenBase = (uint)(vcount * Width); + uint vramBase = (uint)(vcount * Width * 2) + bg.MapBaseBlock * MapBlockSizeAffineNds; + + ushort meta = bg.GetMeta(); + + for (uint p = 0; p < Width; p++) + { + ushort data = GetUshort(vram, vramBase); + + PlaceBgPixel(p + 8, data, meta); + + screenBase++; + vramBase += 2; + } + } + + public void RenderMode4(uint vcount, byte* vram) + { + uint screenBase = (uint)(vcount * Width); + uint vramBase = (uint)(0x0 + vcount * Width); + + for (uint p = 0; p < Width; p++) + { + uint vramVal = vram[vramBase]; + + ScreenBack[screenBase] = LookupPalette(vramVal); + + vramBase++; + screenBase++; + } + } + + public void RenderMode3(uint vcount, byte* vram) + { + uint screenBase = (uint)(vcount * Width); + uint vramBase = (uint)(vcount * Width * 2) + LcdcVramBlock * 131072; + + for (uint p = 0; p < Width; p++) + { + byte b0 = vram[vramBase + 0]; + byte b1 = vram[vramBase + 1]; + + ushort data = (ushort)((b1 << 8) | b0); + + ScreenBack[screenBase] = data; + + screenBase++; + vramBase += 2; + } + } + + public void RenderWhiteScanline(uint vcount) + { + // Render white + uint screenBase = (uint)(vcount * Width); + + for (uint p = 0; p < Width; p++) + { + ScreenBack[screenBase] = 0x7FFF; + screenBase++; + } + } + + public byte ReadHwio8(uint addr) + { + switch (addr) + { + case 0x08: // BG0CNT B0 + case 0x09: // BG0CNT B1 + case 0x0A: // BG1CNT B0 + case 0x0B: // BG1CNT B1 + case 0x0C: // BG2CNT B0 + case 0x0D: // BG2CNT B1 + case 0x0E: // BG3CNT B0 + case 0x0F: // BG3CNT B1 + return Backgrounds[(addr >> 1) & 3].ReadBGCNT(addr & 1); + + case 0x48: // WININ B0 + return (byte)((WININValue >> 0) & 0x3F); + case 0x49: // WININ B1 + return (byte)((WININValue >> 8) & 0x3F); + + case 0x4A: // WINOUT B0 + return (byte)((WINOUTValue >> 0) & 0x3F); + case 0x4B: // WINOUT B1 + return (byte)((WINOUTValue >> 8) & 0x3F); + + case 0x50: // BLDCNT B0 + return (byte)((BLDCNTValue >> 0) & 0xFF); + case 0x51: // BLDCNT B1 + return (byte)((BLDCNTValue >> 8) & 0x3F); + + case 0x52: // BLDALPHA B0 + return (byte)(BLDALPHAValue >> 0); + case 0x53: // BLDALPHA B1 + return (byte)(BLDALPHAValue >> 8); + } + + return 0; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x08: // BG0CNT B0 + case 0x09: // BG0CNT B1 + case 0x0A: // BG1CNT B0 + case 0x0B: // BG1CNT B1 + case 0x0C: // BG2CNT B0 + case 0x0D: // BG2CNT B1 + case 0x0E: // BG3CNT B0 + case 0x0F: // BG3CNT B1 + Backgrounds[(addr >> 1) & 3].WriteBGCNT(addr & 1, val); + BackgroundSettingsDirty = true; + break; + + case 0x10: // BG0HOFS B0 + case 0x11: // BG0HOFS B1 + case 0x12: // BG0VOFS B0 + case 0x13: // BG0VOFS B1 + Backgrounds[0].WriteBGOFS(addr & 3, val); + break; + case 0x14: // BG1HOFS B0 + case 0x15: // BG1HOFS B1 + case 0x16: // BG1VOFS B0 + case 0x17: // BG1VOFS B1 + Backgrounds[1].WriteBGOFS(addr & 3, val); + break; + case 0x18: // BG2HOFS B0 + case 0x19: // BG2HOFS B1 + case 0x1A: // BG2VOFS B0 + case 0x1B: // BG2VOFS B1 + Backgrounds[2].WriteBGOFS(addr & 3, val); + break; + case 0x1C: // BG3HOFS B0 + case 0x1D: // BG3HOFS B1 + case 0x1E: // BG3VOFS B0 + case 0x1F: // BG3VOFS B1 + Backgrounds[3].WriteBGOFS(addr & 3, val); + break; + + case 0x20: // BG2PA B0 + case 0x21: // BG2PA B1 + case 0x22: // BG2PB B0 + case 0x23: // BG2PB B1 + case 0x24: // BG2PC B0 + case 0x25: // BG2PC B1 + case 0x26: // BG2PD B0 + case 0x27: // BG2PD B1 + Backgrounds[2].WriteBGPX(addr & 7, val); + break; + case 0x28: // BG2X B0 + case 0x29: // BG2X B1 + case 0x2A: // BG2X B2 + case 0x2B: // BG2X B3 + case 0x2C: // BG2Y B0 + case 0x2D: // BG2Y B1 + case 0x2E: // BG2Y B2 + case 0x2F: // BG2Y B3 + Backgrounds[2].WriteBGXY(addr & 7, val); + break; + + case 0x30: // BG3PA B0 + case 0x31: // BG3PA B1 + case 0x32: // BG3PB B0 + case 0x33: // BG3PB B1 + case 0x34: // BG3PC B0 + case 0x35: // BG3PC B1 + case 0x36: // BG3PD B0 + case 0x37: // BG3PD B1 + Backgrounds[3].WriteBGPX(addr & 7, val); + break; + case 0x38: // BG3X B0 + case 0x39: // BG3X B1 + case 0x3A: // BG3X B2 + case 0x3B: // BG3X B3 + case 0x3C: // BG3Y B0 + case 0x3D: // BG3Y B1 + case 0x3E: // BG3Y B2 + case 0x3F: // BG3Y B3 + Backgrounds[3].WriteBGXY(addr & 7, val); + break; + + case 0x40: // WIN0H B0 + Win0HRight = val; + break; + case 0x41: // WIN0H B1 + Win0HLeft = val; + break; + case 0x42: // WIN1H B0 + Win1HRight = val; + break; + case 0x43: // WIN1H B1 + Win1HLeft = val; + break; + + case 0x44: // WIN0V B0 + Win0VBottom = val; + break; + case 0x45: // WIN0V B1 + Win0VTop = val; + break; + case 0x46: // WIN1V B0 + Win1VBottom = val; + break; + case 0x47: // WIN1V B1 + Win1VTop = val; + break; + + case 0x48: // WININ B0 + Win0InEnable = (byte)(val & 0b111111U); + + WININValue &= 0x7F00; + WININValue |= (ushort)(val << 0); + break; + case 0x49: // WININ B1 + Win1InEnable = (byte)(val & 0b111111U); + + WININValue &= 0x007F; + WININValue |= (ushort)(val << 8); + break; + + case 0x4A: // WINOUT B0 + WinOutEnable = (byte)(val & 0b111111U); + + WINOUTValue &= 0x7F00; + WINOUTValue |= (ushort)(val << 0); + break; + case 0x4B: // WINOUT B1 + WinObjEnable = (byte)(val & 0b111111U); + + WINOUTValue &= 0x007F; + WINOUTValue |= (ushort)(val << 8); + break; + + case 0x4C: // MOSAIC B0 + BgMosaicX = (byte)((val >> 0) & 0xF); + BgMosaicY = (byte)((val >> 4) & 0xF); + break; + case 0x4D: // MOSAIC B1 + ObjMosaicX = (byte)((val >> 0) & 0xF); + ObjMosaicY = (byte)((val >> 4) & 0xF); + break; + + case 0x50: // BLDCNT B0 + Target1Flags = val & 0b111111U; + + BlendEffect = (BlendEffect)((val >> 6) & 0b11U); + + BLDCNTValue &= 0x7F00; + BLDCNTValue |= (ushort)(val << 0); + break; + case 0x51: // BLDCNT B1 + Target2Flags = val & 0b111111U; + + BLDCNTValue &= 0x00FF; + BLDCNTValue |= (ushort)(val << 8); + break; + + case 0x52: // BLDALPHA B0 + BlendACoeff = val & 0b11111U; + BLDALPHAValue &= 0x7F00; + BLDALPHAValue |= (ushort)(val << 0); + break; + case 0x53: // BLDALPHA B1 + BlendBCoeff = val & 0b11111U; + BLDALPHAValue &= 0x00FF; + BLDALPHAValue |= (ushort)(val << 8); + break; + + case 0x54: // BLDY + BlendBrightness = (byte)(val & 0b11111); + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/PpuRenderer.cs.meta b/Assets/emulator/PpuRenderer.cs.meta new file mode 100644 index 0000000..af7427c --- /dev/null +++ b/Assets/emulator/PpuRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d6510d22717f6584b81af5ea97b0569a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Provider.cs b/Assets/emulator/Provider.cs new file mode 100644 index 0000000..8fb5b1a --- /dev/null +++ b/Assets/emulator/Provider.cs @@ -0,0 +1,10 @@ +namespace OptimeGBA +{ + public delegate void AudioCallback(short[] stereo16BitInterleavedData); + + public abstract class Provider { + public bool OutputAudio = true; + public AudioCallback AudioCallback; + public string SavPath; + } +} \ No newline at end of file diff --git a/Assets/emulator/Provider.cs.meta b/Assets/emulator/Provider.cs.meta new file mode 100644 index 0000000..fbac89c --- /dev/null +++ b/Assets/emulator/Provider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 73818cdc5ac0e0647a35b1b78a717b70 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/ProviderGba.cs b/Assets/emulator/ProviderGba.cs new file mode 100644 index 0000000..c1dcd63 --- /dev/null +++ b/Assets/emulator/ProviderGba.cs @@ -0,0 +1,28 @@ +using System.Text; + + +namespace OptimeGBA +{ + public sealed class ProviderGba : Provider + { + public bool BootBios = false; + + public byte[] Bios; + public byte[] Rom; + + public string RomId; + + public ProviderGba(byte[] bios, byte[] rom, string savPath, AudioCallback audioCallback) + { + Bios = bios; + Rom = rom; + AudioCallback = audioCallback; + SavPath = savPath; + + if (rom.Length >= 0xAC + 4) + { + RomId = Encoding.ASCII.GetString(Rom, 0xAC, 4); + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/ProviderGba.cs.meta b/Assets/emulator/ProviderGba.cs.meta new file mode 100644 index 0000000..277f577 --- /dev/null +++ b/Assets/emulator/ProviderGba.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b60a85ffb5daaa4aa83e8999b108e9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/ProviderNds.cs b/Assets/emulator/ProviderNds.cs new file mode 100644 index 0000000..0fac2e4 --- /dev/null +++ b/Assets/emulator/ProviderNds.cs @@ -0,0 +1,26 @@ +using System.Text; + +namespace OptimeGBA +{ + public sealed class ProviderNds : Provider + { + public bool DirectBoot = true; + + public byte[] Bios7; + public byte[] Bios9; + public byte[] Firmware; + public byte[] Rom; + + public string RomId; + + public ProviderNds(byte[] bios7, byte[] bios9, byte[] firmware, byte[] rom, string savPath, AudioCallback audioCallback) + { + Bios7 = bios7; + Bios9 = bios9; + Firmware = firmware; + Rom = rom; + AudioCallback = audioCallback; + SavPath = savPath; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/ProviderNds.cs.meta b/Assets/emulator/ProviderNds.cs.meta new file mode 100644 index 0000000..4fac925 --- /dev/null +++ b/Assets/emulator/ProviderNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f149b75fc4a1c4b4881e9d79fc7a1edc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/RtcNds.cs b/Assets/emulator/RtcNds.cs new file mode 100644 index 0000000..870acf5 --- /dev/null +++ b/Assets/emulator/RtcNds.cs @@ -0,0 +1,162 @@ +using System; +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + + public enum RtcNdsState + { + ReceivingCommand, + CommandEntered + } + + public class RtcNds + { + public void UpdateTime() + { + var now = DateTime.Now; + + DateAndTime[0] = ConvertToBcd((byte)(now.Year % 100)); + DateAndTime[1] = ConvertToBcd((byte)now.Month); + DateAndTime[2] = ConvertToBcd((byte)now.Day); + DateAndTime[3] = ConvertToBcd((byte)now.DayOfWeek); + DateAndTime[4] = ConvertToBcd((byte)now.Hour); + DateAndTime[5] = ConvertToBcd((byte)now.Minute); + DateAndTime[6] = ConvertToBcd((byte)now.Second); + } + + public static byte ConvertToBcd(byte val) + { + uint upper = val / 10U; + uint lower = val % 10U; + + return (byte)((upper << 4) | lower); + } + + public byte ReadHwio8(uint addr) + { + switch (addr) + { + case 0x4000138: + return Rtc; + } + + return 0; + } + + byte Rtc; + byte Command; + int BitsWritten; + byte Status1; + + byte[] DateAndTime = new byte[7]; + + RtcNdsState State; + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x4000138: + if (BitTest(val, 2)) // CS + { + if (BitTest(Rtc, 1) && !BitTest(val, 1)) // /SC to low + { + switch (State) + { + case RtcNdsState.ReceivingCommand: + Command |= (byte)((val & 1) << (7 - BitsWritten)); + if (++BitsWritten == 8) + { + State = RtcNdsState.CommandEntered; + // Console.WriteLine("RTC: command set " + Util.Hex(Command, 2)); + BitsWritten = 0; + + switch ((Command >> 1) & 0b111) + { + case 0: + // Console.WriteLine("RTC status 1"); + break; + + case 1: + // Console.WriteLine("RTC status 2"); + break; + + case 2: + // Console.WriteLine("RTC date and time"); + UpdateTime(); + break; + + case 3: + // Console.WriteLine("RTC time"); + UpdateTime(); + break; + } + } + break; + case RtcNdsState.CommandEntered: + if (!BitTest(val, 4)) // Read + { + val &= 0xFE; // Erase bit 0 + int commandBits = (Command >> 1) & 0b111; + + int byteNum = BitsWritten / 8; + int bitNum = (BitsWritten % 8); + switch (commandBits) + { + case 0: // Status 1 + // Console.WriteLine("status 1 read"); + val |= (byte)((Status1 >> BitsWritten) & 1); + break; + + case 2: // Date & Time (7 bytes) + val |= (byte)((DateAndTime[byteNum] >> bitNum) & 1); + break; + + case 3: // Time (3 bytes); + val |= (byte)((DateAndTime[byteNum + 4] >> bitNum) & 1); + break; + + default: + // Console.WriteLine("RTC: unknown command read " + commandBits); + break; + } + } + else + { + byte bit = (byte)(val & 1U); + switch ((Command >> 1) & 0b111) + { + case 0: // Status 1 + if (BitsWritten >= 1 && BitsWritten <= 3) + { + // Console.WriteLine("status 1 write "); + Status1 &= (byte)(~(1 << BitsWritten)); + Status1 |= (byte)(bit << BitsWritten); + } + break; + } + } + BitsWritten++; + break; + } + } + else if (!BitTest(Rtc, 1) && BitTest(val, 1) && !BitTest(val, 4)) + { + val &= 0xFE; + val |= (byte)(Rtc & 1); + } + } + else + { + Command = 0; + BitsWritten = 0; + State = RtcNdsState.ReceivingCommand; + } + + Rtc = val; + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/RtcNds.cs.meta b/Assets/emulator/RtcNds.cs.meta new file mode 100644 index 0000000..7310f7c --- /dev/null +++ b/Assets/emulator/RtcNds.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f19637330f5677d4fa311abfed7f81f5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Scheduler.cs b/Assets/emulator/Scheduler.cs new file mode 100644 index 0000000..5f4202f --- /dev/null +++ b/Assets/emulator/Scheduler.cs @@ -0,0 +1,198 @@ +using System; + +namespace OptimeGBA +{ + public delegate void SchedulerCallback(long cyclesLate); + + public enum SchedulerId : byte + { + None = 255, + RootNode = 254, + Ppu = 0, + ApuSample = 1, + HaltSkip = 6, + Timer90 = 7, + Timer91 = 8, + Timer92 = 9, + Timer93 = 10, + Timer70 = 11, + Timer71 = 12, + Timer72 = 13, + Timer73 = 14, + GxFifo = 15, + } + + public class SchedulerEvent + { + public SchedulerId Id; + public long Ticks; + public SchedulerCallback Callback; + public SchedulerEvent NextEvent; + public SchedulerEvent PrevEvent; + + public SchedulerEvent(SchedulerId id, long ticks, SchedulerCallback callback) + { + this.Id = id; + this.Ticks = ticks; + this.Callback = callback; + } + } + + public sealed class Scheduler + { + public Scheduler() + { + for (uint i = 0; i < 64; i++) + { + FreeEventStack[i] = new SchedulerEvent(SchedulerId.None, 0, (long ticks) => { }); + } + + var evt = PopStack(); + evt.NextEvent = null; + evt.PrevEvent = null; + evt.Id = SchedulerId.RootNode; + evt.Ticks = 0; + RootEvent = evt; + } + + public long CurrentTicks = 0; + public long NextEventTicks = long.MaxValue; + + public uint FreeEventStackIndex = 0; + public SchedulerEvent[] FreeEventStack = new SchedulerEvent[64]; + + public SchedulerEvent PopStack() + { + return FreeEventStack[FreeEventStackIndex++]; + } + + public void PushStack(SchedulerEvent schedulerEvent) + { + FreeEventStack[--FreeEventStackIndex] = schedulerEvent; + } + + public SchedulerEvent RootEvent; + public uint EventsQueued = 0; + + static SchedulerEvent createEmptyEvent() + { + return new SchedulerEvent(SchedulerId.None, 0, (long ticks) => { }); + } + + public static string ResolveId(SchedulerId id) + { + switch (id) + { + case SchedulerId.None: return "None"; + case SchedulerId.Ppu: return "PPU Event"; + case SchedulerId.ApuSample: return "APU Sample"; + case SchedulerId.Timer70: return "Timer7 0 Overflow"; + case SchedulerId.Timer71: return "Timer7 1 Overflow"; + case SchedulerId.Timer72: return "Timer7 2 Overflow"; + case SchedulerId.Timer73: return "Timer7 3 Overflow"; + case SchedulerId.Timer90: return "Timer9 0 Overflow"; + case SchedulerId.Timer91: return "Timer9 1 Overflow"; + case SchedulerId.Timer92: return "Timer9 2 Overflow"; + case SchedulerId.Timer93: return "Timer9 3 Overflow"; + case SchedulerId.RootNode: return ""; + default: + return ""; + } + } + + public void AddEventRelative(SchedulerId id, long ticks, SchedulerCallback callback) + { + var origTicks = ticks; + ticks += CurrentTicks; + + var newEvt = PopStack(); + newEvt.Id = id; + newEvt.Ticks = ticks; + newEvt.Callback = callback; + + var prevEvt = RootEvent; + // Traverse linked list and splice at correct location + while (prevEvt.NextEvent != null) + { + if (ticks >= prevEvt.Ticks && ticks <= prevEvt.NextEvent?.Ticks) + { + break; + } + prevEvt = prevEvt.NextEvent; + } + + var nextEvt = prevEvt.NextEvent; + if (nextEvt != null) + { + nextEvt.PrevEvent = newEvt; + } + prevEvt.NextEvent = newEvt; + newEvt.NextEvent = nextEvt; + newEvt.PrevEvent = prevEvt; + + EventsQueued++; + UpdateNextEvent(); + } + + public CircularBuffer EventsToRemove = new CircularBuffer(64, null); + public void CancelEventsById(SchedulerId id) + { + EventsToRemove.Reset(); + + SchedulerEvent evt = RootEvent.NextEvent; + while (evt != null) + { + if (evt.Id == id) + { + EventsToRemove.Insert(evt); + } + evt = evt.NextEvent; + } + + while (EventsToRemove.Entries > 0) + { + RemoveEvent(EventsToRemove.Pop()); + } + } + + public void UpdateNextEvent() + { + if (EventsQueued > 0) + { + NextEventTicks = RootEvent.NextEvent.Ticks; + } + } + + public SchedulerEvent GetFirstEvent() + { + return RootEvent.NextEvent; + } + + public SchedulerEvent PopFirstEvent() + { + var evt = RootEvent.NextEvent; + RemoveEvent(evt); + return evt; + } + + public void RemoveEvent(SchedulerEvent schedulerEvent) + { + if (schedulerEvent == RootEvent) + { + throw new Exception("Cannot remove root event!"); + } + var prev = schedulerEvent.PrevEvent; + var next = schedulerEvent.NextEvent; + if (schedulerEvent.NextEvent != null) + { + next.PrevEvent = prev; + } + prev.NextEvent = next; + schedulerEvent.NextEvent = null; + schedulerEvent.PrevEvent = null; + EventsQueued--; + UpdateNextEvent(); + PushStack(schedulerEvent); + } + } +} diff --git a/Assets/Iris/Iris.Common/Scheduler.cs.meta b/Assets/emulator/Scheduler.cs.meta similarity index 83% rename from Assets/Iris/Iris.Common/Scheduler.cs.meta rename to Assets/emulator/Scheduler.cs.meta index e9fcd36..6982443 100644 --- a/Assets/Iris/Iris.Common/Scheduler.cs.meta +++ b/Assets/emulator/Scheduler.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b12f8d8d769413c4bbeca054bf9013fe +guid: 7a3ce826f3cf2804588653e63ff30378 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/emulator/Soundgoodizer.cs b/Assets/emulator/Soundgoodizer.cs new file mode 100644 index 0000000..3f04364 --- /dev/null +++ b/Assets/emulator/Soundgoodizer.cs @@ -0,0 +1,176 @@ +using System; +//using NAudio.Dsp; +using static OptimeGBA.CoreUtil; + +namespace OptimeGBA +{ + + public class SoundgoodizerFilterChannel + { + // Each biquad filter has a slope of 12db/oct so 2 biquads chained gets us 24db/oct + //BiQuadFilter[] LowFilters = new BiQuadFilter[2]; + //BiQuadFilter[] MidFilters = new BiQuadFilter[4]; + //BiQuadFilter[] HighFilters = new BiQuadFilter[2]; + + public float OutLow = 0; + public float OutMid = 0; + public float OutHigh = 0; + + public bool DbPerOct24; + + public SoundgoodizerFilterChannel(bool dbPerOct24, float sampleRate, float lowHz, float highHz) + { + DbPerOct24 = dbPerOct24; + + // q = 1/sqrt(2) maximally flat "butterworth" filter + float q = 1F/(float)Math.Sqrt(2); + //LowFilters[0] = BiQuadFilter.LowPassFilter(sampleRate, lowHz, q); + //LowFilters[1] = BiQuadFilter.LowPassFilter(sampleRate, lowHz, q); + + //MidFilters[0] = BiQuadFilter.HighPassFilter(sampleRate, lowHz, q); + //MidFilters[1] = BiQuadFilter.LowPassFilter(sampleRate, highHz, q); + //MidFilters[2] = BiQuadFilter.HighPassFilter(sampleRate, lowHz, q); + //MidFilters[3] = BiQuadFilter.LowPassFilter(sampleRate, highHz, q); + + //HighFilters[0] = BiQuadFilter.HighPassFilter(sampleRate, highHz, q); + //HighFilters[1] = BiQuadFilter.HighPassFilter(sampleRate, highHz, q); + } + + public void ChangeFilterParams(bool dbPerOct24, float sampleRate, float lowHz, float highHz) + { + DbPerOct24 = dbPerOct24; + + float q = 1F/(float)Math.Sqrt(2); + //LowFilters[0].SetLowPassFilter(sampleRate, lowHz, q); + //LowFilters[1].SetLowPassFilter(sampleRate, lowHz, q); + + //MidFilters[0].SetHighPassFilter(sampleRate, lowHz, q); + //MidFilters[1].SetLowPassFilter(sampleRate, highHz, q); + //MidFilters[2].SetHighPassFilter(sampleRate, lowHz, q); + //MidFilters[3].SetLowPassFilter(sampleRate, highHz, q); + + //HighFilters[0].SetHighPassFilter(sampleRate, highHz, q); + //HighFilters[1].SetHighPassFilter(sampleRate, highHz, q); + } + + public void Process(float inVal) + { + OutLow = inVal; + OutMid = inVal; + OutHigh = inVal; + + //for (int i = 0; i < (DbPerOct24 ? 2 : 1); i++) + //{ + // OutLow = LowFilters[i].Transform(OutLow); + //} + + //for (int i = 0; i < (DbPerOct24 ? 4 : 2); i++) + //{ + // OutMid = MidFilters[i].Transform(OutMid); + //} + + //for (int i = 0; i < (DbPerOct24 ? 2 : 1); i++) + //{ + // OutHigh = HighFilters[i].Transform(OutHigh); + //} + } + } + public class Soundgoodizer + { + public float MixLevel = 0.6F; + + public SoundgoodizerFilterChannel L; + public SoundgoodizerFilterChannel R; + + public float OutL = 0; + public float OutR = 0; + + //SimpleCompressor CompressorLow; + //SimpleCompressor CompressorMid; + //SimpleCompressor CompressorHigh; + //SimpleCompressor CompressorMaster; + + public float PreGainLow = 1.78F; + public float PreGainMid = 2.09F; + public float PreGainHigh = 2.20F; + public float PreGainMaster = 1; + + public float PostGainLow = 1.91F; + public float PostGainMid = 1.00F; + public float PostGainHigh = 1.40F; + + public bool DbPerOct24; + public float SampleRate; + public float LowHz; + public float HighHz; + + // Default filter cutoffs based on Soundgoodizer Preset A from FL Studio + public Soundgoodizer(float sampleRate) : this(true, sampleRate, 200, 3000) { } + + public Soundgoodizer(bool dbPerOct24, float sampleRate, float lowHz, float highHz) + { + DbPerOct24 = dbPerOct24; + SampleRate = sampleRate; + LowHz = lowHz; + HighHz = highHz; + + L = new SoundgoodizerFilterChannel(dbPerOct24, sampleRate, lowHz, highHz); + R = new SoundgoodizerFilterChannel(dbPerOct24, sampleRate, lowHz, highHz); + + // Compressor parameters also taken from Soundgoodizer Preset A + //CompressorLow = new SimpleCompressor(2.0, 137.48, sampleRate); + //CompressorMid = new SimpleCompressor(2.0, 85.53, sampleRate); + //CompressorHigh = new SimpleCompressor(2.0, 85.53, sampleRate); + //CompressorMaster = new SimpleCompressor(2.0, 85.53, sampleRate); + } + + public void ChangeFilterParams(bool dbPerOct24, float sampleRate, float lowHz, float highHz) + { + DbPerOct24 = dbPerOct24; + SampleRate = sampleRate; + LowHz = lowHz; + HighHz = highHz; + + if (lowHz > highHz) Swap(ref highHz, ref lowHz); + L.ChangeFilterParams(dbPerOct24, sampleRate, lowHz, highHz); + R.ChangeFilterParams(dbPerOct24, sampleRate, lowHz, highHz); + } + + public void Process(float inL, float inR) + { + L.Process(inL); + R.Process(inR); + + // Apply pre-gain (Soundgoodizer Preset A) + double outLowL = L.OutLow * PreGainLow; + double outLowR = R.OutLow * PreGainLow; + double outMidL = L.OutMid * PreGainMid; + double outMidR = R.OutMid * PreGainMid; + double outHighL = L.OutHigh * PreGainHigh; + double outHighR = R.OutHigh * PreGainHigh; + + //CompressorLow.Process(ref outLowL, ref outLowR); + //CompressorMid.Process(ref outMidL, ref outMidR); + //CompressorHigh.Process(ref outHighL, ref outHighR); + + // Apply post-gain (Soundgoodizer Preset A) + outLowL *= PostGainLow; + outLowR *= PostGainLow; + outMidL *= PostGainMid; + outMidR *= PostGainMid; + outHighL *= PostGainHigh; + outHighR *= PostGainHigh; + + double outL = outLowL + outMidL + outHighL; + double outR = outLowR + outMidR + outHighR; + + outL *= PreGainMaster; + outR *= PreGainMaster; + + //CompressorMaster.Process(ref outL, ref outR); + + OutL = (float)(MixLevel * outL + (1 - MixLevel) * inL); + OutR = (float)(MixLevel * outR + (1 - MixLevel) * inR); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Soundgoodizer.cs.meta b/Assets/emulator/Soundgoodizer.cs.meta new file mode 100644 index 0000000..6fbe438 --- /dev/null +++ b/Assets/emulator/Soundgoodizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4244c1f833ecf894b8ad5d41c8277cda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Spi.cs b/Assets/emulator/Spi.cs new file mode 100644 index 0000000..2cd1ec6 --- /dev/null +++ b/Assets/emulator/Spi.cs @@ -0,0 +1,183 @@ +using static OptimeGBA.Bits; +using static OptimeGBA.MemoryUtil; + +namespace OptimeGBA +{ + public enum SpiDevice : byte + { + PowerManager = 0, + Firmware = 1, + Touchscreen = 2 + } + + public enum SpiTouchscreenState + { + Ready, + Command, + } + + public unsafe sealed class Spi + { + public Nds Nds; + + public Spi(Nds nds) + { + Nds = nds; + + Flash = new SpiFlash(Nds.Provider.Firmware); + } + + // From Nocash's original DS + byte[] Id = new byte[] { 0x20, 0x40, 0x12 }; + + // SPICNT + byte BaudRate; + SpiDevice DeviceSelect; + bool TransferSize; + bool ChipSelHold; + bool EnableIrq; + bool EnableSpi; + bool Busy; + + // Flash + public SpiFlash Flash; + + // Touchscreen state + public SpiTouchscreenState TouchscreenState; + public byte TouchscreenCommand; + public byte TouchscreenDataByte; + + public ushort TouchAdcX; + public ushort TouchAdcY; + + public void SetTouchPos(uint x, uint y) + { + ushort adcX1 = GetUshort(Flash.Data, 0x3FF58); + ushort adcY1 = GetUshort(Flash.Data, 0x3FF5A); + byte scrX1 = Flash.Data[0x3FF5C]; + byte scrY1 = Flash.Data[0x3FF5D]; + ushort adcX2 = GetUshort(Flash.Data, 0x3FF5E); + ushort adcY2 = GetUshort(Flash.Data, 0x3FF60); + byte scrX2 = Flash.Data[0x3FF62]; + byte scrY2 = Flash.Data[0x3FF63]; + + // Convert screen coords to calibrated ADC touchscreen coords + TouchAdcX = (ushort)((x - (scrX1 - 1)) * (adcX2 - adcX1) / (scrX2 - scrX1) + adcX1); + TouchAdcY = (ushort)((y - (scrY1 - 1)) * (adcY2 - adcY1) / (scrY2 - scrY1) + adcY1); + } + + public void ClearTouchPos() + { + TouchAdcX = 0; + TouchAdcY = 0xFFF; + } + + public byte OutData; + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x40001C0: // SPICNT B0 + val |= BaudRate; + if (Busy) val = BitSet(val, 7); + break; + case 0x40001C1: // SPICNT B1 + val |= (byte)DeviceSelect; + if (TransferSize) val = BitSet(val, 2); + if (ChipSelHold) val = BitSet(val, 3); + if (EnableIrq) val = BitSet(val, 6); + if (EnableSpi) val = BitSet(val, 7); + break; + + case 0x40001C2: // SPIDATA + // Console.WriteLine("SPI: Read! " + Hex(InData, 2)); + if (!EnableSpi) return 0; + return OutData; + } + + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x40001C0: // SPICNT B0 + BaudRate = (byte)(val & 0b11); + break; + case 0x40001C1: // SPICNT B1 + DeviceSelect = (SpiDevice)(val & 0b11); + TransferSize = BitTest(val, 2); + bool oldChipSelHold = ChipSelHold; + ChipSelHold = BitTest(val, 3); + EnableIrq = BitTest(val, 6); + EnableSpi = BitTest(val, 7); + + if (!EnableSpi) + { + ChipSelHold = false; + } + break; + + case 0x40001C2: // SPIDATA + TransferTo(val); + break; + } + } + + public void TransferTo(byte val) + { + if (EnableSpi) + { + switch (DeviceSelect) + { + case SpiDevice.Firmware: + OutData = Flash.TransferTo(val, TransferSize); + break; + case SpiDevice.Touchscreen: + TransferToTouchscreen(val); + break; + case SpiDevice.PowerManager: + // Console.WriteLine("Power manager access"); + break; + + } + } + + if (!ChipSelHold) + { + Flash.Deselect(); + TouchscreenState = SpiTouchscreenState.Ready; + } + } + + public void TransferToTouchscreen(byte val) + { + switch (TouchscreenState) + { + case SpiTouchscreenState.Ready: + TouchscreenState = SpiTouchscreenState.Command; + OutData = 0; + TouchscreenCommand = val; + TouchscreenDataByte = 0; + break; + case SpiTouchscreenState.Command: + switch ((TouchscreenCommand >> 4) & 0b111) + { + case 1: // Y position + // Shift 12-byte up left three to get start with 1-bit dummy + OutData = (byte)((TouchAdcY << 3) >> (8 * (1 - (TouchscreenDataByte & 1)))); + break; + case 5: // X position + OutData = (byte)((TouchAdcX << 3) >> (8 * (1 - (TouchscreenDataByte & 1)))); + // Console.WriteLine("Y"); + break; + } + TouchscreenDataByte++; + break; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Spi.cs.meta b/Assets/emulator/Spi.cs.meta new file mode 100644 index 0000000..f0b8d9d --- /dev/null +++ b/Assets/emulator/Spi.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc3ada11b34fabd45b3e4e141afd9b7b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/SpiFlash.cs b/Assets/emulator/SpiFlash.cs new file mode 100644 index 0000000..c04c9dd --- /dev/null +++ b/Assets/emulator/SpiFlash.cs @@ -0,0 +1,110 @@ +using System; +using static Util; + +namespace OptimeGBA +{ + public enum SpiFlashState + { + Ready, + Identification, + ReceiveAddress, + Reading, + Status, + TakePrefix, // For cartridges with IR and Flash + } + + public unsafe sealed class SpiFlash + { + public byte[] Data; + + public SpiFlash(byte[] data) { + Data = data; + } + + // Firmware flash state + public SpiFlashState FlashState; + public bool EnableWrite; + public byte IdIndex; + public uint Address; + public byte AddressByteNum = 0; + + // From Nocash's original DS + byte[] Id = new byte[] { 0x20, 0x40, 0x12 }; + + public byte OutData; + + public byte TransferTo(byte val, bool transferSize) + { + switch (FlashState) + { + case SpiFlashState.Ready: + // Console.WriteLine("SPI: Receive command! " + Hex(val, 2)); + OutData = 0x00; + switch (val) + { + case 0x06: + EnableWrite = true; + break; + case 0x04: + EnableWrite = false; + break; + case 0x9F: + FlashState = SpiFlashState.Identification; + IdIndex = 0; + break; + case 0x03: + FlashState = SpiFlashState.ReceiveAddress; + Address = 0; + AddressByteNum = 0; + break; + case 0x05: // Identification + // Console.WriteLine("SPI ID"); + OutData = 0x00; + break; + case 0x00: + break; + default: + throw new NotImplementedException("SPI: Unimplemented command: " + Hex(val, 2)); + } + break; + case SpiFlashState.ReceiveAddress: + // Console.WriteLine("SPI: Address byte write: " + Hex(val, 2)); + Address |= (uint)(val << ((2 - AddressByteNum) * 8)); + AddressByteNum++; + if (AddressByteNum > 2) + { + AddressByteNum = 0; + FlashState = SpiFlashState.Reading; + // Console.WriteLine("SPI: Address written: " + Hex(Address, 6)); + } + break; + case SpiFlashState.Reading: + // Console.WriteLine("SPI: Read from address: " + Hex(Address, 6)); + // Nds7.Cpu.Error("SPI"); + if (Address < 0x40000) + { + OutData = Data[Address]; + } + else + { + OutData = 0; + } + Address += transferSize ? 2U : 1U; + Address &= 0xFFFFFF; + break; + case SpiFlashState.Identification: + OutData = Id[IdIndex]; + IdIndex++; + IdIndex %= 3; + break; + } + + return OutData; + } + + public void Deselect() + { + FlashState = SpiFlashState.Ready; + } + } +} \ No newline at end of file diff --git a/Assets/emulator/SpiFlash.cs.meta b/Assets/emulator/SpiFlash.cs.meta new file mode 100644 index 0000000..e562332 --- /dev/null +++ b/Assets/emulator/SpiFlash.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f7e39ea1a758aa4890a88f1d00e7703 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/Timers.cs b/Assets/emulator/Timers.cs new file mode 100644 index 0000000..0edbd68 --- /dev/null +++ b/Assets/emulator/Timers.cs @@ -0,0 +1,264 @@ +using System; +using static OptimeGBA.Bits; + +namespace OptimeGBA +{ + public sealed class Timer + { + public uint Id = 0; + public Timers Timers; + + public uint CounterVal = 0; + public uint ReloadVal = 0; + + public long EnableCycles = 0; + public long Interval; + + public static readonly int[] PrescalerShifts = { + 0, 6, 8, 10 + }; + public static readonly uint[] PrescalerDivs = { + 1, 64, 256, 1024 + }; + + public uint PrescalerSel = 0; + public bool CountUpTiming = false; + public bool EnableIrq = false; + public bool Enabled = false; + + public Timer(Timers timers, uint id) + { + Id = id; + Timers = timers; + } + + public byte ReadHwio8(uint addr) + { + byte val = 0; + switch (addr) + { + case 0x00: // TMCNT_L B0 + val = (byte)(CalculateCounter() >> 0); + break; + case 0x01: // TMCNT_L B1 + val = (byte)(CalculateCounter() >> 8); + break; + case 0x02: // TMCNT_H B0 + val |= (byte)(PrescalerSel & 0b11); + if (CountUpTiming) val = BitSet(val, 2); + if (EnableIrq) val = BitSet(val, 6); + if (Enabled) val = BitSet(val, 7); + break; + case 0x03: // TMCNT_H B1 + break; + } + return val; + } + + public void WriteHwio8(uint addr, byte val) + { + switch (addr) + { + case 0x00: // TMCNT_L B0 + ReloadVal &= 0xFF00; + ReloadVal |= ((uint)val << 0); + RecalculateInterval(); + break; + case 0x01: // TMCNT_L B1 + ReloadVal &= 0x00FF; + ReloadVal |= ((uint)val << 8); + RecalculateInterval(); + break; + case 0x02: // TMCNT_H B0 + PrescalerSel = (uint)(val & 0b11); + CountUpTiming = BitTest(val, 2); + EnableIrq = BitTest(val, 6); + if (BitTest(val, 7)) + { + Enable(); + } + else + { + Disable(); + } + RecalculateInterval(); + break; + case 0x03: // TMCNT_H B1 + break; + } + } + + public SchedulerId GetSchedulerId() + { + uint id = (uint)SchedulerId.Timer90 + Id; + if (Timers.IsNds7) id += 4; + + return (SchedulerId)id; + } + + public void Enable() + { + if (!Enabled) + { + Reload(); + Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles(), TimerOverflow); + EnableCycles = CalculateAlignedCurrentTicks(); + // Console.WriteLine($"[Timer] {Id} Enable"); + } + + Enabled = true; + } + + public uint CalculateCounter() + { + long diff = Timers.Scheduler.CurrentTicks - EnableCycles; + diff >>= PrescalerShifts[PrescalerSel]; + + if (Enabled) + { + return (ushort)(CounterVal + diff); + } + else + { + return (ushort)CounterVal; + } + } + + public void Reschedule() + { + if (Enabled) + { + CounterVal = ReloadVal; + EnableCycles = CalculateAlignedCurrentTicks(); + + Timers.Scheduler.CancelEventsById(GetSchedulerId()); + Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles(), TimerOverflow); + } + } + + public void RecalculateInterval() + { + uint max = 0x10000; + uint diff = max - ReloadVal; + + Interval = diff * PrescalerDivs[PrescalerSel]; + } + + public long CalculateOverflowCycles() + { + uint max = 0xFFFF; + uint diff = max - CounterVal; + + // Align to the master clock + uint prescalerMod = diff % PrescalerDivs[PrescalerSel]; + diff -= prescalerMod; + diff += PrescalerDivs[PrescalerSel]; + + return diff * PrescalerDivs[PrescalerSel]; + } + + public long CalculateAlignedCurrentTicks() + { + long ticks = Timers.Scheduler.CurrentTicks; + long prescalerMod = Timers.Scheduler.CurrentTicks % PrescalerDivs[PrescalerSel]; + ticks -= prescalerMod; + ticks += PrescalerDivs[PrescalerSel]; + + return ticks; + } + + public void Disable() + { + if (Enabled) + { + CounterVal = CalculateCounter(); + Enabled = false; + + Timers.Scheduler.CancelEventsById(GetSchedulerId()); + } + } + + public void Reload() + { + CounterVal = ReloadVal; + } + + public void TimerOverflow(long cyclesLate) + { + // On overflow, refill with reload value + CounterVal = ReloadVal; + + if (Id < 2 && Timers.GbaAudio != null) + { + Timers.GbaAudio.TimerOverflow(cyclesLate, Id); + } + + if (EnableIrq) + { + Timers.HwControl.FlagInterrupt((uint)InterruptGba.Timer0Overflow + Id); + } + + if (Id < 3) + { + if (Timers.T[Id + 1].CountUpTiming) + { + UnscheduledTimerIncrement(); + } + } + + if (!CountUpTiming) + { + Timers.Scheduler.AddEventRelative(GetSchedulerId(), CalculateOverflowCycles() - cyclesLate, TimerOverflow); + } + + EnableCycles = CalculateAlignedCurrentTicks() - cyclesLate; + // Console.WriteLine($"[Timer] {Id} Overflow"); + } + + public void UnscheduledTimerIncrement() + { + CounterVal++; + if (CounterVal > 0xFFFF) + { + TimerOverflow(0); + } + } + } + + public sealed class Timers + { + public HwControl HwControl; + public GbaAudio GbaAudio; + public bool NdsMode; + public bool IsNds7; + public Scheduler Scheduler; + + public Timers(GbaAudio gbaAudio, HwControl hwControl, Scheduler scheduler, bool ndsMode, bool isNds7) + { + GbaAudio = gbaAudio; + HwControl = hwControl; + NdsMode = ndsMode; + IsNds7 = isNds7; + Scheduler = scheduler; + + T = new Timer[4] { + new Timer(this, 0), + new Timer(this, 1), + new Timer(this, 2), + new Timer(this, 3), + }; + } + + public Timer[] T; + + public byte ReadHwio8(uint addr) + { + return T[(addr >> 2) & 3].ReadHwio8(addr & 3); + } + + public void WriteHwio8(uint addr, byte val) + { + T[(addr >> 2) & 3].WriteHwio8(addr & 3, val); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/Timers.cs.meta b/Assets/emulator/Timers.cs.meta new file mode 100644 index 0000000..ca3103c --- /dev/null +++ b/Assets/emulator/Timers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 298e97a62e33e8c428f0dd465f1382f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/WavWriter.cs b/Assets/emulator/WavWriter.cs new file mode 100644 index 0000000..13c98b1 --- /dev/null +++ b/Assets/emulator/WavWriter.cs @@ -0,0 +1,106 @@ +using System.IO; +using System.Collections.Generic; + +public class WavWriter { + public const int BitsPerSample = 16; + public const int Channels = 2; + public int SampleRate; + + public int RecordBufferAt; + + public List RecordBuffer = new List(); + + public WavWriter(int sampleRate) { + SampleRate = sampleRate; + } + + public void AddSample(short valL, short valR) { + RecordBuffer.Add(valL); + RecordBuffer.Add(valR); + RecordBufferAt += 2; + } + + public void Save(string path) { + var file = File.OpenWrite(path); + + // RIFF header + file.WriteByte(0x52); + file.WriteByte(0x49); + file.WriteByte(0x46); + file.WriteByte(0x46); + + int size = RecordBuffer.Count * Channels * (BitsPerSample / 2) - 8 + 44; + file.WriteByte((byte)(size >> 0)); + file.WriteByte((byte)(size >> 8)); + file.WriteByte((byte)(size >> 16)); + file.WriteByte((byte)(size >> 24)); + + // WAVE + file.WriteByte(0x57); + file.WriteByte(0x41); + file.WriteByte(0x56); + file.WriteByte(0x45); + + // Subchunk1ID "fmt " + file.WriteByte(0x66); + file.WriteByte(0x6d); + file.WriteByte(0x74); + file.WriteByte(0x20); + + // Subchunk1Size + file.WriteByte(16); + file.WriteByte(0); + file.WriteByte(0); + file.WriteByte(0); + + // AudioFormat + file.WriteByte(1); + file.WriteByte(0); + + // 2 channels + file.WriteByte(Channels); + file.WriteByte(0); + + // Sample rate + file.WriteByte((byte)(SampleRate >> 0)); + file.WriteByte((byte)(SampleRate >> 8)); + file.WriteByte((byte)(SampleRate >> 16)); + file.WriteByte((byte)(SampleRate >> 24)); + + // ByteRate + // SampleRate * NumChannels * BitsPerSample/8 + int byteRate = SampleRate * Channels * (BitsPerSample / 8); + file.WriteByte((byte)(byteRate >> 0)); + file.WriteByte((byte)(byteRate >> 8)); + file.WriteByte((byte)(byteRate >> 16)); + file.WriteByte((byte)(byteRate >> 24)); + + // BlockAlign + // NumChannels * BitsPerSample / 8 + int blockAlign = Channels * (BitsPerSample / 8); + file.WriteByte((byte)(blockAlign >> 0)); + file.WriteByte((byte)(blockAlign >> 8)); + + // BitsPerSample + file.WriteByte(16); + file.WriteByte(0); + + // Subchunk2ID "data" + file.WriteByte(0x64); + file.WriteByte(0x61); + file.WriteByte(0x74); + file.WriteByte(0x61); + + // NumSamples * NumChannels * BitsPerSample/8 + int subchunk2Size = RecordBufferAt * 2 * (BitsPerSample / 8); + file.WriteByte((byte)(subchunk2Size >> 0)); + file.WriteByte((byte)(subchunk2Size >> 8)); + file.WriteByte((byte)(subchunk2Size >> 16)); + file.WriteByte((byte)(subchunk2Size >> 24)); + + for (int i = 0; i < RecordBufferAt; i++) { + file.WriteByte((byte)(RecordBuffer[i] >> 0)); + file.WriteByte((byte)(RecordBuffer[i] >> 8)); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/WavWriter.cs.meta b/Assets/emulator/WavWriter.cs.meta new file mode 100644 index 0000000..16e4a8e --- /dev/null +++ b/Assets/emulator/WavWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3268f3abb89a2bb448bf321f561a1c51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.CPU.meta b/Assets/emulator/cpu.meta similarity index 77% rename from Assets/Iris/Iris.CPU.meta rename to Assets/emulator/cpu.meta index 31e9ced..2425e9e 100644 --- a/Assets/Iris/Iris.CPU.meta +++ b/Assets/emulator/cpu.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a8eb82c88394f8a409faf989c85e64b4 +guid: a84b3ac2aebc05e42b619bc3aa18d958 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/emulator/cpu/Arm.cs b/Assets/emulator/cpu/Arm.cs new file mode 100644 index 0000000..94b940e --- /dev/null +++ b/Assets/emulator/cpu/Arm.cs @@ -0,0 +1,1924 @@ +using static OptimeGBA.Bits; +using static Util; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace OptimeGBA +{ + public delegate void ArmExecutor(Arm7 arm7, uint ins); + + public unsafe static class Arm + { + public static void SWI(Arm7 arm7, uint ins) + { + arm7.SPSR_svc = arm7.GetCPSR(); + arm7.SetMode(Arm7Mode.SVC); // Go into SVC / Supervisor mode + arm7.R[14] = arm7.R[15] - 4; + // arm7.ThumbState = false; // Back to ARM state + arm7.IRQDisable = true; + + arm7.R[15] = arm7.VectorSoftwareInterrupt; + arm7.FlushPipeline(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _LDMSTM(Arm7 arm7, uint ins, bool L) + { + arm7.LineDebug("LDM/STM"); + + bool P = BitTest(ins, 24); // post-indexed / offset addressing + bool U = BitTest(ins, 23); // invert + bool S = BitTest(ins, 22); + bool W = BitTest(ins, 21); + + bool loadsPc = BitTest(ins, 15); + + uint oldMode = 0; + if (S && (!L || !loadsPc)) + { + oldMode = arm7.GetMode(); + arm7.SetMode(Arm7Mode.USR); + } + + // if (U && P && W) Error("U & P & W"); + + arm7.LineDebug(L ? "Load" : "Store"); + arm7.LineDebug(P ? "No Include Base" : "Include Base"); + arm7.LineDebug(U ? "Upwards" : "Downwards"); + + uint rn = (ins >> 16) & 0xF; + + uint addr = arm7.R[rn]; + + // String regs = ""; + + uint bitsSet = (uint)BitOperations.PopCount(ins & 0xFFFF); + uint writebackValue; + if (U) + { + if (W) + { + writebackValue = addr + bitsSet * 4; + } + else + { + writebackValue = addr; + } + } + else + { + if (W) + { + writebackValue = addr - bitsSet * 4; + } + else + { + writebackValue = addr; + } + if (P) + { + addr = addr - bitsSet * 4 - 4; + } + else + { + addr = addr - bitsSet * 4 + 4; + } + } + + if (L) + { + if (W) + { + arm7.R[rn] = writebackValue; + } + } + else + { + arm7.R[15] += 4; + } + + for (byte r = 0; r < 16; r++) + { + if (BitTest(ins, r)) + { + if (L) + { + if (P) addr += 4; + + if (r != 15) + { + arm7.R[r] = arm7.Read32(addr & ~3U); + } + else + { + arm7.R[15] = arm7.Read32(addr & ~3U) & ~3U; + arm7.FlushPipeline(); + } + + if (!P) addr += 4; + } + else + { + + if (P) addr += 4; + + arm7.Write32(addr & ~3U, arm7.R[r]); + + if (!P) addr += 4; + + arm7.R[rn] = writebackValue; + } + } + } + + bool emptyRlist = (ins & 0xFFFF) == 0; + if (emptyRlist) + { + if (L) + { + arm7.R[15] = arm7.Read32(addr & ~3U); + arm7.FlushPipeline(); + if (U) + { + arm7.R[rn] += 0x40; + } + else + { + arm7.R[rn] -= 0x40; + } + } + else + { + arm7.LineDebug("Empty Rlist!"); + if (P) + { + if (U) + { + arm7.Write32(arm7.R[rn] + 4, arm7.R[15]); + arm7.R[rn] += 0x40; + } + else + { + arm7.R[rn] -= 0x40; + arm7.Write32(arm7.R[rn], arm7.R[15]); + } + } + else + { + if (U) + { + arm7.Write32(arm7.R[rn], arm7.R[15]); + arm7.R[rn] += 0x40; + } + else + { + arm7.R[rn] -= 0x40; + arm7.Write32(arm7.R[rn] + 4, arm7.R[15]); + } + } + } + } + + if (!L) + { + arm7.R[15] -= 4; + } + + if (S) + { + if (L && loadsPc) + { + arm7.SetCPSR(arm7.GetSPSR()); + } + else + { + arm7.SetMode((Arm7Mode)oldMode); + } + } + + // arm7.LineDebug(regs); + + arm7.ICycle(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _LDMSTM_V5(Arm7 arm7, uint ins, bool L) + { + arm7.LineDebug("LDM/STM ARMv5"); + + bool P = BitTest(ins, 24); // post-indexed / offset addressing + bool U = BitTest(ins, 23); // invert + bool S = BitTest(ins, 22); + bool W = BitTest(ins, 21); + + bool loadsPc = BitTest(ins, 15); + + uint oldMode = 0; + if (S && (!L || !loadsPc)) + { + oldMode = arm7.GetMode(); + arm7.SetMode(Arm7Mode.USR); + } + + // if (U && P && W) Error("U & P & W"); + + arm7.LineDebug(L ? "Load" : "Store"); + arm7.LineDebug(P ? "No Include Base" : "Include Base"); + arm7.LineDebug(U ? "Upwards" : "Downwards"); + + uint rn = (ins >> 16) & 0xF; + + uint addr = arm7.R[rn]; + + // String regs = ""; + + uint bitsSet = (uint)BitOperations.PopCount(ins & 0xFFFF); + uint writebackValue; + if (U) + { + if (W) + { + writebackValue = addr + bitsSet * 4; + } + else + { + writebackValue = addr; + } + } + else + { + if (W) + { + writebackValue = addr - bitsSet * 4; + } + else + { + writebackValue = addr; + } + if (P) + { + addr = addr - bitsSet * 4 - 4; + } + else + { + addr = addr - bitsSet * 4 + 4; + } + } + + if (!L) + { + arm7.R[15] += 4; + } + + for (byte r = 0; r < 16; r++) + { + if (BitTest(ins, r)) + { + if (L) + { + if (W) + { + arm7.R[rn] = writebackValue; + } + + if (P) addr += 4; + + if (r != 15) + { + arm7.R[r] = arm7.Read32(addr & ~3U); + } + else + { + arm7.R[15] = arm7.Read32(addr & ~3U); + arm7.ThumbState = BitTest(arm7.R[15], 0); + arm7.FlushPipeline(); + } + + if (!P) addr += 4; + } + else + { + + if (P) addr += 4; + + arm7.Write32(addr & ~3U, arm7.R[r]); + + if (!P) addr += 4; + } + } + } + + if (!L) + { + arm7.R[15] -= 4; + } + + // ARMv5: When Rn is in Rlist, writeback happens if Rn is the only register, or not the last + // I can't figure out the order of operations so I'll just hack the only register case + if (!L || bitsSet == 1) + { + arm7.R[rn] = writebackValue; + } + + bool emptyRlist = (ins & 0xFFFF) == 0; + if (emptyRlist) + { + if (U) + { + arm7.R[rn] += 0x40; + } + else + { + arm7.R[rn] -= 0x40; + } + } + + if (S) + { + if (L && loadsPc) + { + arm7.SetCPSR(arm7.GetSPSR()); + } + else + { + arm7.SetMode((Arm7Mode)oldMode); + } + } + + // arm7.LineDebug(regs); + + arm7.ICycle(); + } + + public static void LDM(Arm7 arm7, uint ins) { _LDMSTM(arm7, ins, true); } + public static void STM(Arm7 arm7, uint ins) { _LDMSTM(arm7, ins, false); } + public static void LDM_V5(Arm7 arm7, uint ins) { _LDMSTM_V5(arm7, ins, true); } + public static void STM_V5(Arm7 arm7, uint ins) { _LDMSTM_V5(arm7, ins, false); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _B(Arm7 arm7, uint ins, bool link) + { + arm7.LineDebug("B | Branch"); + // B + int offset = (int)(ins & 0b111111111111111111111111) << 2; + // Signed with Two's Complement + // Cheap and easy sign-extend + offset = (offset << 6) >> 6; + + // BL - store return address in R14 + if (link) + { + arm7.R[14] = arm7.R[15] - 4; + } + + // BLX immediate + if ((ins >> 28) == 0b1111) + { + int halfwordOffset = (int)(ins >> 24) & 0b1; + arm7.ThumbState = true; + offset += halfwordOffset * 2; + + arm7.R[14] = arm7.R[15] - 4; + } + + arm7.R[15] = (uint)(arm7.R[15] + offset); + arm7.FlushPipeline(); + } + + public static void B(Arm7 arm7, uint ins) { _B(arm7, ins, false); } + public static void BL(Arm7 arm7, uint ins) { _B(arm7, ins, true); } + + public static void BX(Arm7 arm7, uint ins) + { + // BX - branch and optional switch to Thumb state + arm7.LineDebug("BX"); + + uint rm = ins & 0xF; + uint rmValue = arm7.R[rm]; + + arm7.ThumbState = BitTest(rmValue, 0); + if (arm7.ThumbState) + { + arm7.StateChange(); + arm7.LineDebug("Switch to THUMB State"); + } + else + { + arm7.LineDebug("Switch to ARM State"); + } + + // BLX register + uint opcode = (ins >> 4) & 0xF; + if (opcode == 0b0011) + { + arm7.R[14] = arm7.R[15] - 4; + } + + arm7.R[15] = rmValue & ~1U; + arm7.FlushPipeline(); + } + + public static void SWP(Arm7 arm7, uint ins) + { + uint rm = (ins >> 0) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint rn = (ins >> 16) & 0xF; + + uint addr = arm7.R[rn]; + uint storeValue = arm7.R[rm]; + + arm7.LineDebug("SWP"); + uint readVal = RotateRight32(arm7.Read32(addr & ~3u), (byte)((addr & 3u) * 8)); + arm7.Write32(addr & ~3u, storeValue); + arm7.R[rd] = readVal; + + arm7.ICycle(); + } + + public static void SWPB(Arm7 arm7, uint ins) + { + uint rm = (ins >> 0) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint rn = (ins >> 16) & 0xF; + + uint addr = arm7.R[rn]; + uint storeValue = arm7.R[rm]; + + arm7.LineDebug("SWPB"); + byte readVal = arm7.Read8(addr); + arm7.Write8(addr, (byte)storeValue); + arm7.R[rd] = readVal; + + arm7.ICycle(); + } + + public static void MSR(Arm7 arm7, uint ins) + { + arm7.LineDebug("MSR"); + // MSR + + bool useSPSR = BitTest(ins, 22); + + bool setControl = BitTest(ins, 16); + bool setExtension = BitTest(ins, 17); + bool setStatus = BitTest(ins, 18); + bool setFlags = BitTest(ins, 19); + + bool useImmediate = BitTest(ins, 25); + + uint operand; + + if (useImmediate) + { + uint rotateBits = ((ins >> 8) & 0xF) * 2; + uint constant = ins & 0xFF; + + operand = RotateRight32(constant, (byte)rotateBits); + } + else + { + operand = arm7.R[ins & 0xF]; + } + + uint byteMask = + (setControl ? 0x000000FFu : 0) | + (setExtension ? 0x0000FF00u : 0) | + (setStatus ? 0x00FF0000u : 0) | + (setFlags ? 0xFF000000u : 0); + + arm7.LineDebug($"Set Control: {setControl}"); + arm7.LineDebug($"Set Extension: {setExtension}"); + arm7.LineDebug($"Set Status: {setStatus}"); + arm7.LineDebug($"Set Flags: {setFlags}"); + + if (!useSPSR) + { + // TODO: Fix privileged mode functionality in CPSR MSR + if (arm7.Mode == Arm7Mode.USR) + { + // Privileged + arm7.LineDebug("Privileged"); + byteMask &= 0xFF000000; + } + arm7.SetCPSR((arm7.GetCPSR() & ~byteMask) | (operand & byteMask)); + } + else + { + // TODO: Add SPSR functionality to MSR + arm7.SetSPSR((arm7.GetSPSR() & ~byteMask) | (operand & byteMask)); + } + } + + public static void MRS(Arm7 arm7, uint ins) + { + arm7.LineDebug("MRS"); + + bool useSPSR = BitTest(ins, 22); + + uint rd = (ins >> 12) & 0xF; + + if (useSPSR) + { + arm7.LineDebug("Rd from SPSR"); + arm7.R[rd] = arm7.GetSPSR(); + } + else + { + arm7.LineDebug("Rd from CPSR"); + arm7.R[rd] = arm7.GetCPSR(); + } + } + + public static void MUL(Arm7 arm7, uint ins) + { + uint rd = (ins >> 16) & 0xF; + uint rs = (ins >> 8) & 0xF; + uint rm = (ins >> 0) & 0xF; + uint rsValue = arm7.R[rs]; + uint rmValue = arm7.R[rm]; + + arm7.LineDebug($"R{rm} * R{rs}"); + arm7.LineDebug($"${Util.HexN(rmValue, 8)} * ${Util.HexN(rsValue, 8)}"); + + bool setFlags = BitTest(ins, 20); + + uint final; + if (BitTest(ins, 21)) + { + uint rnValue = arm7.R[(ins >> 12) & 0xF]; + arm7.LineDebug("Multiply Accumulate"); + final = (rsValue * rmValue) + rnValue; + } + else + { + arm7.LineDebug("Multiply Regular"); + final = rsValue * rmValue; + } + arm7.R[rd] = final; + + if (setFlags) + { + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + } + } + + public static void MULL(Arm7 arm7, uint ins) + { + bool signed = BitTest(ins, 22); + bool accumulate = BitTest(ins, 21); + bool setFlags = BitTest(ins, 20); + + uint rdHi = (ins >> 16) & 0xF; + uint rdLo = (ins >> 12) & 0xF; + uint rs = (ins >> 8) & 0xF; + uint rm = (ins >> 0) & 0xF; + ulong rsVal = arm7.R[rs]; + ulong rmVal = arm7.R[rm]; + + arm7.LineDebug("Multiply Long"); + + ulong longLo; + ulong longHi; + if (accumulate) + { + arm7.LineDebug("Accumulate"); + + if (signed) + { + // SMLAL + long rmValExt = (long)(rmVal << 32) >> 32; + long rsValExt = (long)(rsVal << 32) >> 32; + + longLo = (ulong)(((rsValExt * rmValExt) & 0xFFFFFFFF) + arm7.R[rdLo]); + longHi = (ulong)((rsValExt * rmValExt) >> 32) + arm7.R[rdHi] + (longLo > 0xFFFFFFFF ? 1U : 0); + } + else + { + // UMLAL + longLo = ((rsVal * rmVal) & 0xFFFFFFFF) + arm7.R[rdLo]; + longHi = ((rsVal * rmVal) >> 32) + arm7.R[rdHi] + (longLo > 0xFFFFFFFF ? 1U : 0); + } + } + else + { + arm7.LineDebug("No Accumulate"); + + if (signed) + { + // SMULL + long rmValExt = (long)(rmVal << 32) >> 32; + long rsValExt = (long)(rsVal << 32) >> 32; + + longLo = (ulong)((rsValExt * rmValExt)); + longHi = (ulong)((rsValExt * rmValExt) >> 32); + } + else + { + // UMULL + longLo = (rmVal * rsVal); + longHi = ((rmVal * rsVal) >> 32); + } + } + + arm7.LineDebug($"RdLo: R{rdLo}"); + arm7.LineDebug($"RdHi: R{rdHi}"); + arm7.LineDebug($"Rm: R{rm}"); + arm7.LineDebug($"Rs: R{rs}"); + + arm7.R[rdLo] = (uint)longLo; + arm7.R[rdHi] = (uint)longHi; + + if (setFlags) + { + arm7.Negative = BitTest((uint)longHi, 31); + arm7.Zero = arm7.R[rdLo] == 0 && arm7.R[rdHi] == 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _RegularLDRSTR(Arm7 arm7, uint ins, bool L, bool useRegister) + { + // LDR (Load Register) + arm7.LineDebug("LDR (Load Register)"); + + uint rn = (ins >> 16) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint rnValue = arm7.R[rn]; + + bool P = BitTest(ins, 24); // post-indexed / offset addressing + bool U = BitTest(ins, 23); // invert + bool B = BitTest(ins, 22); + bool W = BitTest(ins, 21); + + uint offset = 0; + + if (useRegister) + { + // Register offset + arm7.LineDebug($"Register Offset"); + uint rmVal = arm7.R[ins & 0xF]; + + if ((ins & 0b111111110000) == 0b000000000000) + { + arm7.LineDebug($"Non-scaled"); + offset = rmVal; + } + else + { + arm7.LineDebug($"Scaled"); + + uint shiftType = (ins >> 5) & 0b11; + byte shiftBits = (byte)((ins >> 7) & 0b11111); + switch (shiftType) + { + case 0b00: + offset = LogicalShiftLeft32(rmVal, shiftBits); + break; + case 0b01: + if (shiftBits == 0) + { + offset = 0; + } + else + { + offset = LogicalShiftRight32(rmVal, shiftBits); + } + break; + case 0b10: + if (shiftBits == 0) + { + // if (BitTest(rmVal, 31)) + // { + // return 0xFFFFFFFF; + // } + // else + // { + // return 0; + // } + offset = (uint)((int)rmVal >> 31); + } + else + { + offset = ArithmeticShiftRight32(rmVal, shiftBits); + } + break; + default: + case 0b11: + if (shiftBits == 0) + { + offset = LogicalShiftLeft32(arm7.Carry ? 1U : 0, 31) | (LogicalShiftRight32(rmVal, 1)); + } + else + { + offset = RotateRight32(rmVal, shiftBits); + } + break; + } + } + + } + else + { + // Immediate offset + arm7.LineDebug($"Immediate Offset"); + + // if (L && U && !registerOffset && rd == 0 && (ins & 0b111111111111) == 0) Error("sdfsdf"); + + // This IS NOT A SHIFTED 32-BIT IMMEDIATE, IT'S PLAIN 12-BIT! + offset = ins & 0b111111111111; + } + + uint addr = rnValue; + if (P) + { + if (U) + { + addr += offset; + } + else + { + addr -= offset; + } + } + + if (L) + { + arm7.LineDebug($"Rn: R{rn}"); + arm7.LineDebug($"Rd: R{rd}"); + + uint loadVal = 0; + if (B) + { + loadVal = arm7.Read8(addr); + } + else + { + + if ((addr & 0b11) != 0) + { + + // If the address isn't word-aligned + uint data = arm7.Read32(addr & ~3U); + loadVal = RotateRight32(data, (byte)(8 * (addr & 0b11))); + + // Error("Misaligned LDR"); + } + else + { + loadVal = arm7.Read32(addr); + } + } + + arm7.LineDebug($"LDR Addr: {Util.Hex(addr, 8)}"); + arm7.LineDebug($"LDR Value: {Util.Hex(loadVal, 8)}"); + + if (!P) + { + if (U) + { + addr += offset; + } + else + { + addr -= offset; + } + + arm7.R[rn] = addr; + } + else if (W) + { + arm7.R[rn] = addr; + } + + // Register loading happens after writeback, so if writeback register and Rd are the same, + // the writeback value would be overwritten by Rd. + arm7.R[rd] = loadVal; + + if (rd == 15) + { + if (arm7.Armv5) + { + arm7.ThumbState = BitTest(loadVal, 0); + } + arm7.FlushPipeline(); + } + + arm7.ICycle(); + } + else + { + arm7.R[15] += 4; + + uint storeVal = arm7.R[rd]; + if (B) + { + arm7.Write8(addr, (byte)storeVal); + } + else + { + arm7.Write32(addr & 0xFFFFFFFC, storeVal); + } + + arm7.LineDebug($"STR Addr: {Util.Hex(addr, 8)}"); + arm7.LineDebug($"STR Value: {Util.Hex(storeVal, 8)}"); + + arm7.R[15] -= 4; + + if (!P) + { + if (U) + { + addr += offset; + } + else + { + addr -= offset; + } + + arm7.R[rn] = addr; + } + else if (W) + { + arm7.R[rn] = addr; + } + } + } + + public static void RegularLDR_Reg(Arm7 arm7, uint ins) { _RegularLDRSTR(arm7, ins, true, true); } + public static void RegularSTR_Reg(Arm7 arm7, uint ins) { _RegularLDRSTR(arm7, ins, false, true); } + public static void RegularLDR_Imm(Arm7 arm7, uint ins) { _RegularLDRSTR(arm7, ins, true, false); } + public static void RegularSTR_Imm(Arm7 arm7, uint ins) { _RegularLDRSTR(arm7, ins, false, false); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _SpecialLDRSTR(Arm7 arm7, uint ins, bool L, bool S, bool H) + { + arm7.LineDebug("Halfword, Signed Byte, Doubleword Loads & Stores"); + arm7.LineDebug("LDR|STR H|SH|SB|D"); + + bool W = BitTest(ins, 21); // Writeback to base register + bool immediateOffset = BitTest(ins, 22); + bool U = BitTest(ins, 23); // Add / Subtract offset + bool P = BitTest(ins, 24); // Use post-indexed / offset or pre-indexed + + uint rd = (ins >> 12) & 0xF; + uint rn = (ins >> 16) & 0xF; + + uint baseAddr = arm7.R[rn]; + + uint offset; + if (immediateOffset) + { + arm7.LineDebug("Immediate Offset"); + uint immed = (ins & 0xF) | ((ins >> 4) & 0xF0); + offset = immed; + } + else + { + arm7.LineDebug("Register Offset"); + uint rm = ins & 0xF; + offset = arm7.R[rm]; + } + + uint addr = baseAddr; + if (P) + { + if (U) + { + addr += offset; + } + else + { + addr -= offset; + } + } + + uint loadVal = 0; + if (L) + { + if (S) + { + if (H) + { + arm7.LineDebug("Load signed halfword"); + + int readVal; + if ((addr & 1) != 0) + { + // Misaligned, read byte instead. + // Sign extend + readVal = (sbyte)arm7.Read8(addr); + } + else + { + // Sign extend + readVal = (short)arm7.Read16(addr); + } + loadVal = (uint)readVal; + } + else + { + arm7.LineDebug("Load signed byte"); + + int val = (sbyte)arm7.Read8(addr); + + loadVal = (uint)val; + } + } + else + { + if (H) + { + arm7.LineDebug("Load unsigned halfword"); + // Force halfword aligned, and rotate if unaligned + loadVal = RotateRight32(arm7.Read16(addr & ~1u), (byte)((addr & 1) * 8)); + } + } + } + else + { + if (S) + { + if (arm7.Armv5) + { + if (H) + { + arm7.LineDebug("Store doubleword"); + arm7.Error($"UNIMPLEMENTED R15:{Hex(arm7.R[15], 8)} OPCODE:{Hex(ins, 8)} STRD"); + } + else + { + arm7.LineDebug("Load doubleword"); + arm7.Error($"UNIMPLEMENTED R15:{Hex(arm7.R[15], 8)} OPCODE:{Hex(ins, 8)} LDRD"); + } + } + else + { + return; + } + } + else + { + if (H) + { + arm7.LineDebug("Store halfword"); + arm7.Write16(addr & ~1u, (ushort)arm7.R[rd]); + } + } + } + + if (!P) + { + if (U) + { + addr = baseAddr + offset; + } + else + { + addr = baseAddr - offset; + } + } + + if (W || !P) + { + arm7.R[rn] = addr; + } + + if (L) + { + arm7.R[rd] = loadVal; + arm7.ICycle(); + } + + arm7.LineDebug($"Writeback: {(W ? "Yes" : "No")}"); + arm7.LineDebug($"Offset / pre-indexed addressing: {(P ? "Yes" : "No")}"); + } + + // In order mentioned in ARM architecture reference manual for ARMv5 + public static void STRH(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, false, false, true); } + public static void LDRD(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, false, true, false); } + public static void STRD(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, false, true, true); } + public static void LDRH(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, true, false, true); } + public static void LDRSB(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, true, true, false); } + public static void LDRSH(Arm7 arm7, uint ins) { _SpecialLDRSTR(arm7, ins, true, true, true); } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (uint shifterOperand, bool shifterCarryOut, uint rnVal, uint rd) DataDecode(Arm7 arm7, uint ins, bool useImmediate32) + { + uint rd = (ins >> 12) & 0xF; // Rd, SBZ for CMP + + // ----- When using register as 2nd operand ----- + // Shift by immediate or shift by register + uint shifterOperand = 0; + bool shifterCarryOut = false; + + if (useImmediate32) + { + uint rn = (ins >> 16) & 0xF; // Rn + // uint rs = (ins >> 8) & 0xF; + // uint rm = ins & 0xF; + uint rnVal = arm7.R[rn]; + // uint rsVal = R[rs]; + // uint rmVal = R[rm]; + + uint rotateBits = ((ins >> 8) & 0xF) * 2; + uint constant = ins & 0xFF; + + shifterOperand = RotateRight32(constant, (byte)rotateBits); + if (rotateBits == 0) + { + shifterCarryOut = arm7.Carry; + } + else + { + shifterCarryOut = BitTest(shifterOperand, 31); + } + + arm7.LineDebug($"Immediate32: {Util.Hex(shifterOperand, 8)}"); + + return (shifterOperand, shifterCarryOut, rnVal, rd); + } + else + { + bool regShift = (ins & BIT_4) != 0; + + byte shiftBits; + uint shiftType = (ins >> 5) & 0b11; + + if (!regShift) + { + // Immediate Shift + arm7.LineDebug("Immediate Shift"); + shiftBits = (byte)((ins >> 7) & 0b11111); + + uint rn = (ins >> 16) & 0xF; // Rn + // uint rs = (ins >> 8) & 0xF; + uint rm = ins & 0xF; + uint rnVal = arm7.R[rn]; + // uint rsVal = R[rs]; + uint rmVal = arm7.R[rm]; + + switch (shiftType) + { + case 0b00: // LSL + if (shiftBits == 0) + { + shifterOperand = rmVal; + shifterCarryOut = arm7.Carry; + } + else + { + shifterOperand = LogicalShiftLeft32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(32 - shiftBits)); + } + break; + case 0b01: // LSR + if (shiftBits == 0) + { + shifterOperand = 0; + shifterCarryOut = BitTest(rmVal, 31); + } + else + { + shifterOperand = LogicalShiftRight32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(shiftBits - 1)); + } + break; + case 0b10: // ASR + if (shiftBits == 0) + { + shifterOperand = (uint)((int)rmVal >> 31); + shifterCarryOut = BitTest(rmVal, 31); + } + else + { + shifterOperand = ArithmeticShiftRight32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(shiftBits - 1)); + } + break; + case 0b11: // ROR + if (shiftBits == 0) + { + shifterOperand = LogicalShiftLeft32(arm7.Carry ? 1U : 0, 31) | LogicalShiftRight32(rmVal, 1); + shifterCarryOut = BitTest(rmVal, 0); + } + else + { + shifterOperand = RotateRight32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(shiftBits - 1)); + } + break; + } + + return (shifterOperand, shifterCarryOut, rnVal, rd); + } + else + { + // Register shift + arm7.LineDebug("Register Shift"); + + uint rn = (ins >> 16) & 0xF; // Rn + uint rs = (ins >> 8) & 0xF; + uint rm = ins & 0xF; + arm7.LineDebug("RS: " + rs); + + arm7.ICycle(); + + arm7.R[15] += 4; + uint rnVal = arm7.R[rn]; + uint rsVal = arm7.R[rs]; + uint rmVal = arm7.R[rm]; + arm7.R[15] -= 4; + + shiftBits = (byte)rsVal; + + switch (shiftType) + { + case 0b00: + if (shiftBits == 0) + { + shifterOperand = rmVal; + shifterCarryOut = arm7.Carry; + break; + } + + if (shiftBits >= 32) + { + if (shiftBits > 32) + { + shifterCarryOut = false; + } + else + { + shifterCarryOut = BitTest(rmVal, 0); + } + shifterOperand = 0; + break; + } + + shifterOperand = rmVal << shiftBits; + shifterCarryOut = BitTest(rmVal, (byte)(32 - shiftBits)); + break; + case 0b01: + if (shiftBits == 0) + { + shifterOperand = rmVal; + shifterCarryOut = arm7.Carry; + } + else if (shiftBits < 32) + { + shifterOperand = LogicalShiftRight32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(shiftBits - 1)); + } + else if (shiftBits == 32) + { + shifterOperand = 0; + shifterCarryOut = BitTest(rmVal, 31); + } + else + { + shifterOperand = 0; + shifterCarryOut = false; + } + break; + case 0b10: + if (shiftBits == 0) + { + shifterOperand = rmVal; + shifterCarryOut = arm7.Carry; + } + else if (shiftBits < 32) + { + shifterOperand = ArithmeticShiftRight32(rmVal, shiftBits); + shifterCarryOut = BitTest(rmVal, (byte)(shiftBits - 1)); + } + else if (shiftBits >= 32) + { + shifterOperand = (uint)((int)rmVal >> 31); + shifterCarryOut = BitTest(rmVal, 31); + } + break; + case 0b11: + if (shiftBits == 0) + { + shifterOperand = rmVal; + shifterCarryOut = arm7.Carry; + } + else + { + shifterOperand = RotateRight32(rmVal, (byte)(shiftBits & 0b11111)); + shifterCarryOut = BitTest(rmVal, (byte)((shiftBits & 0b11111) - 1)); + } + break; + } + + return (shifterOperand, shifterCarryOut, rnVal, rd); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataAND(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("AND"); + + uint final = rnValue & shifterOperand; + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = shifterCarryOut; + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataEOR(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("EOR"); + + uint final = rnValue ^ shifterOperand; + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = shifterCarryOut; + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataSUB(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("SUB"); + + uint aluOut = rnValue - shifterOperand; + + arm7.R[rd] = aluOut; + if (setFlags) + { + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = shifterOperand <= rnValue; // C + arm7.Overflow = Arm7.CheckOverflowSub(rnValue, shifterOperand, aluOut); // V + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataRSB(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("RSB"); + + uint aluOut = shifterOperand - rnValue; + + arm7.R[rd] = aluOut; + if (setFlags) + { + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = rnValue <= shifterOperand; // C + arm7.Overflow = Arm7.CheckOverflowSub(shifterOperand, rnValue, aluOut); // V + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataADD(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("ADD"); + + uint final = rnValue + shifterOperand; + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); // N + arm7.Zero = final == 0; // Z + arm7.Carry = (long)rnValue + (long)shifterOperand > 0xFFFFFFFFL; // C + arm7.Overflow = Arm7.CheckOverflowAdd(rnValue, shifterOperand, final); // C + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataADC(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("ADC"); + + uint final = rnValue + shifterOperand + (arm7.Carry ? 1U : 0); + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); // N + arm7.Zero = final == 0; // Z + arm7.Carry = (long)rnValue + (long)shifterOperand + (arm7.Carry ? 1U : 0) > 0xFFFFFFFFL; // C + arm7.Overflow = Arm7.CheckOverflowAdd(rnValue, shifterOperand, final); // V + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataSBC(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("SBC"); + + uint aluOut = rnValue - shifterOperand - (!arm7.Carry ? 1U : 0U); + + arm7.R[rd] = aluOut; + if (setFlags) + { + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = (long)shifterOperand + (arm7.Carry ? 0 : 1L) <= rnValue; // C + arm7.Overflow = Arm7.CheckOverflowSub(rnValue, shifterOperand, aluOut); // V + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataRSC(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("RSC"); + + uint aluOut = shifterOperand - rnValue - (!arm7.Carry ? 1U : 0U); + + arm7.R[rd] = aluOut; + if (setFlags) + { + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = (long)rnValue + (long)(!arm7.Carry ? 1U : 0) <= shifterOperand; // C + arm7.Overflow = Arm7.CheckOverflowSub(shifterOperand, rnValue, aluOut); // V + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataTST(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("TST"); + + uint final = rnValue & shifterOperand; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = shifterCarryOut; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataTEQ(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("TEQ"); + + uint aluOut = rnValue ^ shifterOperand; + + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = shifterCarryOut; // C + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataCMP(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + // SBZ means should be zero, not relevant to the current code, just so you know + arm7.LineDebug("CMP"); + + uint aluOut = rnValue - shifterOperand; + + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = rnValue >= shifterOperand; // C + arm7.Overflow = Arm7.CheckOverflowSub(rnValue, shifterOperand, aluOut); // V + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataCMN(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("CMN"); + + uint aluOut = rnValue + shifterOperand; + + arm7.Negative = BitTest(aluOut, 31); // N + arm7.Zero = aluOut == 0; // Z + arm7.Carry = (long)rnValue + (long)shifterOperand > 0xFFFFFFFF; // C + arm7.Overflow = Arm7.CheckOverflowAdd(rnValue, shifterOperand, aluOut); // V + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataORR(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("ORR"); + + uint final = rnValue | shifterOperand; + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = shifterCarryOut; + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataMOV(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("MOV"); + + arm7.R[rd] /*Rd*/ = shifterOperand; + if (setFlags) + { + arm7.Negative = BitTest(shifterOperand, 31); // N + arm7.Zero = shifterOperand == 0; // Z + arm7.Carry = shifterCarryOut; // C + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataBIC(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("BIC"); + + uint final = rnValue & ~shifterOperand; + arm7.R[rd] = final; + if (setFlags) + { + arm7.Negative = BitTest(final, 31); // N + arm7.Zero = final == 0; // Z + arm7.Carry = shifterCarryOut; // C + + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void _DataMVN(Arm7 arm7, uint ins, bool useImmediate32, bool setFlags) + { + (uint shifterOperand, bool shifterCarryOut, uint rnValue, uint rd) = DataDecode(arm7, ins, useImmediate32); + + arm7.LineDebug("MVN"); + + arm7.R[rd] /*Rd*/ = ~shifterOperand; + if (setFlags) + { + arm7.Negative = BitTest(~shifterOperand, 31); // N + arm7.Zero = ~shifterOperand == 0; // Z + arm7.Carry = shifterCarryOut; ; // C + if (rd == 15) + { + arm7.SetCPSR(arm7.GetSPSR()); + arm7.FlushPipeline(); + } + } + else + { + if (rd == 15) arm7.FlushPipeline(); + } + } + + // Reducing branches during runtime, the private functions beginning + // with an underline are marked for inlining + public static void DataAND_Reg(Arm7 arm7, uint ins) { _DataAND(arm7, ins, false, false); } + public static void DataEOR_Reg(Arm7 arm7, uint ins) { _DataEOR(arm7, ins, false, false); } + public static void DataSUB_Reg(Arm7 arm7, uint ins) { _DataSUB(arm7, ins, false, false); } + public static void DataRSB_Reg(Arm7 arm7, uint ins) { _DataRSB(arm7, ins, false, false); } + public static void DataADD_Reg(Arm7 arm7, uint ins) { _DataADD(arm7, ins, false, false); } + public static void DataADC_Reg(Arm7 arm7, uint ins) { _DataADC(arm7, ins, false, false); } + public static void DataSBC_Reg(Arm7 arm7, uint ins) { _DataSBC(arm7, ins, false, false); } + public static void DataRSC_Reg(Arm7 arm7, uint ins) { _DataRSC(arm7, ins, false, false); } + public static void DataTST_Reg(Arm7 arm7, uint ins) { _DataTST(arm7, ins, false, false); } + public static void DataTEQ_Reg(Arm7 arm7, uint ins) { _DataTEQ(arm7, ins, false, false); } + public static void DataCMP_Reg(Arm7 arm7, uint ins) { _DataCMP(arm7, ins, false, false); } + public static void DataCMN_Reg(Arm7 arm7, uint ins) { _DataCMN(arm7, ins, false, false); } + public static void DataORR_Reg(Arm7 arm7, uint ins) { _DataORR(arm7, ins, false, false); } + public static void DataMOV_Reg(Arm7 arm7, uint ins) { _DataMOV(arm7, ins, false, false); } + public static void DataBIC_Reg(Arm7 arm7, uint ins) { _DataBIC(arm7, ins, false, false); } + public static void DataMVN_Reg(Arm7 arm7, uint ins) { _DataMVN(arm7, ins, false, false); } + + public static void DataAND_Imm(Arm7 arm7, uint ins) { _DataAND(arm7, ins, true, false); } + public static void DataEOR_Imm(Arm7 arm7, uint ins) { _DataEOR(arm7, ins, true, false); } + public static void DataSUB_Imm(Arm7 arm7, uint ins) { _DataSUB(arm7, ins, true, false); } + public static void DataRSB_Imm(Arm7 arm7, uint ins) { _DataRSB(arm7, ins, true, false); } + public static void DataADD_Imm(Arm7 arm7, uint ins) { _DataADD(arm7, ins, true, false); } + public static void DataADC_Imm(Arm7 arm7, uint ins) { _DataADC(arm7, ins, true, false); } + public static void DataSBC_Imm(Arm7 arm7, uint ins) { _DataSBC(arm7, ins, true, false); } + public static void DataRSC_Imm(Arm7 arm7, uint ins) { _DataRSC(arm7, ins, true, false); } + public static void DataTST_Imm(Arm7 arm7, uint ins) { _DataTST(arm7, ins, true, false); } + public static void DataTEQ_Imm(Arm7 arm7, uint ins) { _DataTEQ(arm7, ins, true, false); } + public static void DataCMP_Imm(Arm7 arm7, uint ins) { _DataCMP(arm7, ins, true, false); } + public static void DataCMN_Imm(Arm7 arm7, uint ins) { _DataCMN(arm7, ins, true, false); } + public static void DataORR_Imm(Arm7 arm7, uint ins) { _DataORR(arm7, ins, true, false); } + public static void DataMOV_Imm(Arm7 arm7, uint ins) { _DataMOV(arm7, ins, true, false); } + public static void DataBIC_Imm(Arm7 arm7, uint ins) { _DataBIC(arm7, ins, true, false); } + public static void DataMVN_Imm(Arm7 arm7, uint ins) { _DataMVN(arm7, ins, true, false); } + + public static void DataANDS_Reg(Arm7 arm7, uint ins) { _DataAND(arm7, ins, false, true); } + public static void DataEORS_Reg(Arm7 arm7, uint ins) { _DataEOR(arm7, ins, false, true); } + public static void DataSUBS_Reg(Arm7 arm7, uint ins) { _DataSUB(arm7, ins, false, true); } + public static void DataRSBS_Reg(Arm7 arm7, uint ins) { _DataRSB(arm7, ins, false, true); } + public static void DataADDS_Reg(Arm7 arm7, uint ins) { _DataADD(arm7, ins, false, true); } + public static void DataADCS_Reg(Arm7 arm7, uint ins) { _DataADC(arm7, ins, false, true); } + public static void DataSBCS_Reg(Arm7 arm7, uint ins) { _DataSBC(arm7, ins, false, true); } + public static void DataRSCS_Reg(Arm7 arm7, uint ins) { _DataRSC(arm7, ins, false, true); } + public static void DataTSTS_Reg(Arm7 arm7, uint ins) { _DataTST(arm7, ins, false, true); } + public static void DataTEQS_Reg(Arm7 arm7, uint ins) { _DataTEQ(arm7, ins, false, true); } + public static void DataCMPS_Reg(Arm7 arm7, uint ins) { _DataCMP(arm7, ins, false, true); } + public static void DataCMNS_Reg(Arm7 arm7, uint ins) { _DataCMN(arm7, ins, false, true); } + public static void DataORRS_Reg(Arm7 arm7, uint ins) { _DataORR(arm7, ins, false, true); } + public static void DataMOVS_Reg(Arm7 arm7, uint ins) { _DataMOV(arm7, ins, false, true); } + public static void DataBICS_Reg(Arm7 arm7, uint ins) { _DataBIC(arm7, ins, false, true); } + public static void DataMVNS_Reg(Arm7 arm7, uint ins) { _DataMVN(arm7, ins, false, true); } + + public static void DataANDS_Imm(Arm7 arm7, uint ins) { _DataAND(arm7, ins, true, true); } + public static void DataEORS_Imm(Arm7 arm7, uint ins) { _DataEOR(arm7, ins, true, true); } + public static void DataSUBS_Imm(Arm7 arm7, uint ins) { _DataSUB(arm7, ins, true, true); } + public static void DataRSBS_Imm(Arm7 arm7, uint ins) { _DataRSB(arm7, ins, true, true); } + public static void DataADDS_Imm(Arm7 arm7, uint ins) { _DataADD(arm7, ins, true, true); } + public static void DataADCS_Imm(Arm7 arm7, uint ins) { _DataADC(arm7, ins, true, true); } + public static void DataSBCS_Imm(Arm7 arm7, uint ins) { _DataSBC(arm7, ins, true, true); } + public static void DataRSCS_Imm(Arm7 arm7, uint ins) { _DataRSC(arm7, ins, true, true); } + public static void DataTSTS_Imm(Arm7 arm7, uint ins) { _DataTST(arm7, ins, true, true); } + public static void DataTEQS_Imm(Arm7 arm7, uint ins) { _DataTEQ(arm7, ins, true, true); } + public static void DataCMPS_Imm(Arm7 arm7, uint ins) { _DataCMP(arm7, ins, true, true); } + public static void DataCMNS_Imm(Arm7 arm7, uint ins) { _DataCMN(arm7, ins, true, true); } + public static void DataORRS_Imm(Arm7 arm7, uint ins) { _DataORR(arm7, ins, true, true); } + public static void DataMOVS_Imm(Arm7 arm7, uint ins) { _DataMOV(arm7, ins, true, true); } + public static void DataBICS_Imm(Arm7 arm7, uint ins) { _DataBIC(arm7, ins, true, true); } + public static void DataMVNS_Imm(Arm7 arm7, uint ins) { _DataMVN(arm7, ins, true, true); } + + public static void MCR(Arm7 arm7, uint ins) + { + uint opcode1 = (ins >> 21) & 0x7; + uint cRn = (ins >> 16) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint coproc = (ins >> 8) & 0xF; + uint opcode2 = (ins >> 5) & 0x7; + uint cRm = (ins >> 0) & 0xF; + + if (coproc == 15) + { + uint rdVal = arm7.R[rd]; + arm7.Cp15.TransferTo(opcode1, rdVal, cRn, cRm, opcode2); + } + } + + public static void MRC(Arm7 arm7, uint ins) + { + uint opcode1 = (ins >> 21) & 0x7; + uint cRn = (ins >> 16) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint coproc = (ins >> 8) & 0xF; + uint opcode2 = (ins >> 5) & 0x7; + uint cRm = (ins >> 0) & 0xF; + + if (coproc == 15) + { + uint data = arm7.Cp15.TransferFrom(opcode1, cRn, cRm, opcode2); + if (rd == 15) + { + arm7.Negative = BitTest(data, 31); + arm7.Zero = BitTest(data, 30); + arm7.Carry = BitTest(data, 29); + arm7.Overflow = BitTest(data, 28); + } + else + { + arm7.R[rd] = data; + } + } + } + + public static void CLZ(Arm7 arm7, uint ins) + { + uint rd = (ins >> 12) & 0xF; + uint rm = (ins >> 0) & 0xF; + + uint rmVal = arm7.R[rm]; + + if (rmVal == 0) + { + arm7.R[rd] = 32; + } + else + { + arm7.R[rd] = (uint)BitOperations.LeadingZeroCount(rmVal); + } + } + + public static void QADD(Arm7 arm7, uint ins) + { + uint rm = (ins >> 0) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint rn = (ins >> 16) & 0xF; + + long rmVal = (int)arm7.R[rm]; + long rnVal = (int)arm7.R[rn]; + + bool doubling = BitTest(ins, 22); + if (doubling) + { + rnVal *= 2; + + if (rnVal < int.MinValue) + { + rnVal = int.MinValue; + arm7.Sticky = true; + } + if (rnVal > int.MaxValue) + { + rnVal = int.MaxValue; + arm7.Sticky = true; + } + } + + long result = rmVal + rnVal; + + if (result < int.MinValue) + { + result = int.MinValue; + arm7.Sticky = true; + } + if (result > int.MaxValue) + { + result = int.MaxValue; + arm7.Sticky = true; + } + + arm7.R[rd] = (uint)result; + } + + public static void QSUB(Arm7 arm7, uint ins) + { + uint rm = (ins >> 0) & 0xF; + uint rd = (ins >> 12) & 0xF; + uint rn = (ins >> 16) & 0xF; + + long rmVal = (int)arm7.R[rm]; + long rnVal = (int)arm7.R[rn]; + + bool doubling = BitTest(ins, 22); + if (doubling) + { + rnVal *= 2; + + if (rnVal < int.MinValue) + { + rnVal = int.MinValue; + arm7.Sticky = true; + } + if (rnVal > int.MaxValue) + { + rnVal = int.MaxValue; + arm7.Sticky = true; + } + } + + long result = rmVal - rnVal; + + if (result < int.MinValue) + { + result = int.MinValue; + arm7.Sticky = true; + } + if (result > int.MaxValue) + { + result = int.MaxValue; + arm7.Sticky = true; + } + + arm7.R[rd] = (uint)result; + } + + public static void SMLALxy(Arm7 arm7, uint ins) + { + bool x = BitTest(ins, 5); + bool y = BitTest(ins, 6); + + uint rm = (ins >> 0) & 0xFU; + uint rs = (ins >> 8) & 0xFU; + uint rmVal = arm7.R[rm]; + uint rsVal = arm7.R[rs]; + + uint rdLo = (ins >> 12) & 0xFU; + uint rdHi = (ins >> 16) & 0xFU; + uint rdLoVal = arm7.R[rdLo]; + uint rdHiVal = arm7.R[rdHi]; + + short op1; + if (!x) + op1 = (short)rmVal; + else + op1 = (short)(rmVal >> 16); + + short op2; + if (!y) + op2 = (short)rsVal; + else + op2 = (short)(rsVal >> 16); + + long finalVal = (((long)rdHiVal << 32) | rdLoVal) + op1 * op2; + arm7.R[rdLo] = (uint)finalVal; + arm7.R[rdHi] = (uint)(finalVal >> 32); + } + + public static void SMULxy(Arm7 arm7, uint ins) + { + bool x = BitTest(ins, 5); + bool y = BitTest(ins, 6); + + uint rm = (ins >> 0) & 0xFU; + uint rs = (ins >> 8) & 0xFU; + uint rmVal = arm7.R[rm]; + uint rsVal = arm7.R[rs]; + + uint rd = (ins >> 16) & 0xFU; + + short op1; + if (!x) + op1 = (short)rmVal; + else + op1 = (short)(rmVal >> 16); + + short op2; + if (!y) + op2 = (short)rsVal; + else + op2 = (short)(rsVal >> 16); + + arm7.R[rd] = (uint)(op1 * op2); + } + + public static void SMLAxy(Arm7 arm7, uint ins) + { + bool x = BitTest(ins, 5); + bool y = BitTest(ins, 6); + + uint rm = (ins >> 0) & 0xFU; + uint rs = (ins >> 8) & 0xFU; + uint rn = (ins >> 12) & 0xFU; + uint rmVal = arm7.R[rm]; + uint rsVal = arm7.R[rs]; + uint rnVal = arm7.R[rn]; + + uint rd = (ins >> 16) & 0xFU; + + short op1; + if (!x) + op1 = (short)rmVal; + else + op1 = (short)(rmVal >> 16); + + short op2; + if (!y) + op2 = (short)rsVal; + else + op2 = (short)(rsVal >> 16); + + uint mulVal = (uint)(op1 * op2); + uint finalVal = mulVal + rnVal; + arm7.R[rd] = finalVal; + + if (Arm7.CheckOverflowAdd(mulVal, rnVal, finalVal)) arm7.Sticky = true; + + // arm7.Error("test"); + } + + + public static void Invalid(Arm7 arm7, uint ins) + { + arm7.Error($"Invalid ARM Instruction: {Hex(ins, 8)}"); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/cpu/Arm.cs.meta b/Assets/emulator/cpu/Arm.cs.meta new file mode 100644 index 0000000..05ba135 --- /dev/null +++ b/Assets/emulator/cpu/Arm.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fd0e52258ac1ba48ba82b603fbc5462 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/cpu/Arm7.cs b/Assets/emulator/cpu/Arm7.cs new file mode 100644 index 0000000..452e8a2 --- /dev/null +++ b/Assets/emulator/cpu/Arm7.cs @@ -0,0 +1,1265 @@ +using System; +using System.Runtime.CompilerServices; +using System.Diagnostics; +using static OptimeGBA.Bits; +using static Util; +using static OptimeGBA.MemoryUtil; + +namespace OptimeGBA +{ + // ARM DDI 0100I manual and GBATek used for implementation of this CPU + + public enum Arm7Mode + { + OldUSR = 0x00, + OldFIQ = 0x01, + OldIRQ = 0x02, + OldSVC = 0x03, + + USR = 0x10, // User + FIQ = 0x11, // Fast Interrupt Request + IRQ = 0x12, // Interrupt Request + SVC = 0x13, // Supervisor Call + ABT = 0x17, // Abort + UND = 0x1B, // Undefined Instruction + SYS = 0x1F, // System + } + + public unsafe sealed class Arm7 + { + + // 1024 functions, taking the top 10 bits of THUMB + public ThumbExecutor[] ThumbDispatch; + public ThumbExecutor[] GenerateThumbDispatch() + { + ThumbExecutor[] table = new ThumbExecutor[1024]; + + for (ushort i = 0; i < 1024; i++) + { + ushort opcode = (ushort)(i << 6); + table[i] = GetInstructionThumb(opcode); + } + + return table; + } + + public ArmExecutor[] ArmDispatch; + public ArmExecutor[] GenerateArmDispatch() + { + ArmExecutor[] table = new ArmExecutor[4096]; + + for (uint i = 0; i < 4096; i++) + { + uint opcode = ((i & 0xFF0) << 16) | ((i & 0xF) << 4); + table[i] = GetInstructionArm(opcode); + } + + return table; + } + + public uint VectorReset; + public uint VectorUndefined; + public uint VectorSoftwareInterrupt; + public uint VectorPrefetchAbort; + public uint VectorDataAbort; + public uint VectorAddrGreaterThan26Bit; + public uint VectorIRQ; + public uint VectorFIQ; + + public Memory Mem; + + public Action PreExecutionCallback; + + public bool Armv5; + + public uint* R = MemoryUtil.AllocateUnmanagedArray32(16); + + ~Arm7() + { + MemoryUtil.FreeUnmanagedArray(R); + + MemoryUtil.FreeUnmanagedArray(Timing8And16); + MemoryUtil.FreeUnmanagedArray(Timing32); + MemoryUtil.FreeUnmanagedArray(Timing8And16InstrFetch); + MemoryUtil.FreeUnmanagedArray(Timing32InstrFetch); + } + + public uint[] Rusr = new uint[7]; + public uint[] Rfiq = new uint[7]; + public uint[] Rsvc = new uint[2]; + public uint[] Rabt = new uint[2]; + public uint[] Rirq = new uint[2]; + public uint[] Rund = new uint[2]; + + public uint SPSR_fiq; + public uint SPSR_svc; + public uint SPSR_abt; + public uint SPSR_irq; + public uint SPSR_und; + + public bool Negative = false; + public bool Zero = false; + public bool Carry = false; + public bool Overflow = false; + public bool Sticky = false; + public bool IRQDisable = false; + public bool FIQDisable = false; + public bool ThumbState = false; + public Arm7Mode Mode = Arm7Mode.SYS; + + public bool Halted; + public bool PipelineDirty = false; + + // DEBUG INFO + public long InstructionsRan = 0; + public uint LastIns; + public uint LastLastIns; + public bool LastThumbState; + public bool LastLastThumbState; + public bool InterruptServiced; + + public bool Errored = false; + + public static uint[] ThumbExecutorProfile = new uint[1024]; + public static uint[] ArmExecutorProfile = new uint[4096]; + + public bool FlagInterrupt; + + public Action StateChange; + + public Cp15 Cp15; + public Arm7(Action stateChange, Memory mem, bool vectorMode, bool armv5, Cp15 cp15) + { + StateChange = stateChange; + Mem = mem; + Armv5 = armv5; + Cp15 = cp15; + + ThumbDispatch = GenerateThumbDispatch(); + ArmDispatch = GenerateArmDispatch(); + + // Default Mode + Mode = Arm7Mode.SYS; + + SetVectorMode(vectorMode); + R[15] = VectorReset; + } + + public void SetTimingsTable(byte* table, params byte[] list) + { + for (uint i = 0; i < 16; i++) + { + table[i] = list[i]; + } + } + + public void SetVectorMode(bool high) + { + if (high) + { + VectorReset = 0xFFFF0000; + VectorUndefined = 0xFFFF0004; + VectorSoftwareInterrupt = 0xFFFF0008; + VectorPrefetchAbort = 0xFFFF000C; + VectorDataAbort = 0xFFFF0010; + VectorAddrGreaterThan26Bit = 0xFFFF0014; + VectorIRQ = 0xFFFF0018; + VectorFIQ = 0xFFFF001C; + } + else + { + VectorReset = 0x00; + VectorUndefined = 0x04; + VectorSoftwareInterrupt = 0x08; + VectorPrefetchAbort = 0x0C; + VectorDataAbort = 0x10; + VectorAddrGreaterThan26Bit = 0x14; + VectorIRQ = 0x18; + VectorFIQ = 0x1C; + } + } + + public void BiosInit() + { + Zero = true; + Carry = true; + + R[0] = 0x08000000; + R[1] = 0x000000EA; + } + + public void InitFlushPipeline() + { + if (ThumbState) + { + R[15] += 4; + InstructionCycles += Timing8And16InstrFetch[(R[15] >> 24) & 0xF] * 2U; + } + else + { + R[15] += 8; + InstructionCycles += Timing32InstrFetch[(R[15] >> 24) & 0xF] * 2U; + } + } + + public void FlushPipeline() + { + if (ThumbState) + { + R[15] &= ~1U; + R[15] += 2; + InstructionCycles += Timing8And16InstrFetch[(R[15] >> 24) & 0xF] * 2U; + } + else + { + R[15] &= ~3U; + R[15] += 4; + InstructionCycles += Timing32InstrFetch[(R[15] >> 24) & 0xF] * 2U; + } + } + + public uint InstructionCycles = 0; + + public uint Execute() + { + CheckInterrupts(); + if (!ThumbState) // ARM mode + { + ExecuteArm(); + } + else // THUMB mode + { + ExecuteThumb(); + } + + + return InstructionCycles; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ExecuteArm() + { + InstructionsRan++; + InstructionCycles = 0; + + LineDebug($"R15: ${Util.HexN(R[15], 4)}"); + + uint ins = Read32InstrFetch(R[15] - 8); + +#if OPENTK_DEBUGGER + LastLastIns = LastIns; + LastIns = ins; + LastLastThumbState = LastThumbState; + LastThumbState = ThumbState; + + if (PreExecutionCallback != null) + PreExecutionCallback(); +#endif + + LineDebug($"Ins: ${Util.HexN(ins, 8)} InsBin:{Util.Binary(ins, 32)}"); + LineDebug($"Cond: ${ins >> 28:X}"); + + uint condition = (ins >> 28) & 0xF; + + bool conditionMet = CheckCondition(condition); + + if (conditionMet) + { + uint decodeBits = ((ins >> 16) & 0xFF0) | ((ins >> 4) & 0xF); +#if OPENTK_DEBUGGER + ArmExecutorProfile[decodeBits]++; +#endif + ArmDispatch[decodeBits](this, ins); + } + + if (!ThumbState) + { + R[15] += 4; + } + else + { + R[15] += 2; + } + + return InstructionCycles; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + public uint ExecuteThumb() + { + InstructionsRan++; + InstructionCycles = 0; + + LineDebug($"R15: ${Util.HexN(R[15], 4)}"); + + ushort ins = (ushort)Read16InstrFetch(R[15] - 4); + + int decodeBits = ins >> 6; + +#if OPENTK_DEBUGGER + LastLastIns = LastIns; + LastIns = ins; + LastLastThumbState = LastThumbState; + LastThumbState = ThumbState; + ThumbExecutorProfile[decodeBits]++; + + if (PreExecutionCallback != null) + PreExecutionCallback(); +#endif + LineDebug($"Ins: ${Util.HexN(ins, 4)} InsBin:{Util.Binary(ins, 16)}"); + + ThumbDispatch[decodeBits](this, ins); + + if (ThumbState) + { + R[15] += 2; + } + else + { + R[15] += 4; + } + + return InstructionCycles; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CheckInterrupts() + { + if (FlagInterrupt && !IRQDisable) + { + DispatchInterrupt(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void DispatchInterrupt() + { + // Error("sdfkjadfdjsjklfads interupt lol"); +#if OPENTK_DEBUGGER + InterruptServiced = true; +#endif + + SPSR_irq = GetCPSR(); + + SetMode(Arm7Mode.IRQ); // Go into SVC / Supervisor mode + + if (ThumbState) + { + R[14] = R[15] - 0; + } + else + { + R[14] = R[15] - 4; + } + + ThumbState = false; // Back to ARM state + IRQDisable = true; + // FIQDisable = true; + + R[15] = VectorIRQ; + InitFlushPipeline(); + + // Error("IRQ, ENTERING IRQ MODE!"); + } + + public ArmExecutor GetInstructionArm(uint ins) + { + if ((ins & 0b1110000000000000000000000000) == 0b1010000000000000000000000000) // B + { + if (BitTest(ins, 24)) + { + return Arm.BL; + } + else + { + return Arm.B; + } + } // id mask 0b1111111100000000000011010000 0b1111111100000000000011110000 + else if ((ins & 0b1111111100000000000011010000) == 0b0001001000000000000000010000) // BX + { + return Arm.BX; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111100100000000000011110000) == 0b0001000000000000000001010000) // QADD/QSUB/QDADD/QDSUB + { + if (Armv5) + { + if (BitTest(ins, 21)) + { + return Arm.QSUB; + } + else + { + return Arm.QADD; + } + } + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111100100000000000010010000) == 0b0001000000000000000010000000) // ARMv5 signed multiply + { + if (Armv5) + { + uint id = ((ins >> 20) & 0b11111111); + if (id == 0b00010100) + { + return Arm.SMLALxy; + } + else if (id == 0b00010110) + { + return Arm.SMULxy; + } + else if (id == 0b00010000) + { + return Arm.SMLAxy; + } + } + + return Arm.Invalid; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111111100000000000011110000) == 0b0001011000000000000000010000) // Count Leading Zeros + { + return Arm.CLZ; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111101100000000000011110000) == 0b0001000000000000000010010000) // SWP / SWPB + { + bool useByte = BitTest(ins, 22); + if (useByte) + { + return Arm.SWPB; + } + else + { + return Arm.SWP; + } + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111101100000000000000000000) == 0b0011001000000000000000000000) // MSR - Immediate Operand + { + return Arm.MSR; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111101100000000000011110000) == 0b0001001000000000000000000000) // MSR - Register Operand + { + return Arm.MSR; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111101100000000000011110000) == 0b0001000000000000000000000000) // MRS + { + return Arm.MRS; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111110000000000000011110000) == 0b0000000000000000000010010000) // Multiply Regular + { + return Arm.MUL; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111100000000000000011110000) == 0b0000100000000000000010010000) // Multiply Long + { + return Arm.MULL; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1110000000000000000010010000) == 0b0000000000000000000010010000) // Halfword, Signed Byte, Doubleword Loads and Stores + { + + bool L = BitTest(ins, 20); + bool S = BitTest(ins, 6); + bool H = BitTest(ins, 5); + if (!L && !S && H) return Arm.STRH; + if (!L && S && !H && Armv5) return Arm.LDRD; + if (!L && S && H && Armv5) return Arm.STRD; + if (L && !S && H) return Arm.LDRH; + if (L && S && !H) return Arm.LDRSB; + if (L && S && H) return Arm.LDRSH; + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1100000000000000000000000000) == 0b0000000000000000000000000000) // Data Processing // ALU + { + // Bits 27, 26 are 0, so data processing / ALU + // LineDebug("Data Processing / FSR Transfer"); + // ALU Operations + uint opcode = (ins >> 21) & 0xF; + bool setFlags = (ins & BIT_20) != 0; + bool useImmediate32 = (ins & BIT_25) != 0; + + // LineDebug($"Rn: R{rn}"); + // LineDebug($"Rd: R{rd}"); + + if (setFlags) + { + if (useImmediate32) + { + switch (opcode) + { + case 0x0: return Arm.DataANDS_Imm; + case 0x1: return Arm.DataEORS_Imm; + case 0x2: return Arm.DataSUBS_Imm; + case 0x3: return Arm.DataRSBS_Imm; + case 0x4: return Arm.DataADDS_Imm; + case 0x5: return Arm.DataADCS_Imm; + case 0x6: return Arm.DataSBCS_Imm; + case 0x7: return Arm.DataRSCS_Imm; + case 0x8: return Arm.DataTSTS_Imm; + case 0x9: return Arm.DataTEQS_Imm; + case 0xA: return Arm.DataCMPS_Imm; + case 0xB: return Arm.DataCMNS_Imm; + case 0xC: return Arm.DataORRS_Imm; + case 0xD: return Arm.DataMOVS_Imm; + case 0xE: return Arm.DataBICS_Imm; + case 0xF: return Arm.DataMVNS_Imm; + } + } + else + { + switch (opcode) + { + case 0x0: return Arm.DataANDS_Reg; + case 0x1: return Arm.DataEORS_Reg; + case 0x2: return Arm.DataSUBS_Reg; + case 0x3: return Arm.DataRSBS_Reg; + case 0x4: return Arm.DataADDS_Reg; + case 0x5: return Arm.DataADCS_Reg; + case 0x6: return Arm.DataSBCS_Reg; + case 0x7: return Arm.DataRSCS_Reg; + case 0x8: return Arm.DataTSTS_Reg; + case 0x9: return Arm.DataTEQS_Reg; + case 0xA: return Arm.DataCMPS_Reg; + case 0xB: return Arm.DataCMNS_Reg; + case 0xC: return Arm.DataORRS_Reg; + case 0xD: return Arm.DataMOVS_Reg; + case 0xE: return Arm.DataBICS_Reg; + case 0xF: return Arm.DataMVNS_Reg; + } + } + } + else + { + if (useImmediate32) + { + switch (opcode) + { + case 0x0: return Arm.DataAND_Imm; + case 0x1: return Arm.DataEOR_Imm; + case 0x2: return Arm.DataSUB_Imm; + case 0x3: return Arm.DataRSB_Imm; + case 0x4: return Arm.DataADD_Imm; + case 0x5: return Arm.DataADC_Imm; + case 0x6: return Arm.DataSBC_Imm; + case 0x7: return Arm.DataRSC_Imm; + case 0x8: return Arm.DataTST_Imm; + case 0x9: return Arm.DataTEQ_Imm; + case 0xA: return Arm.DataCMP_Imm; + case 0xB: return Arm.DataCMN_Imm; + case 0xC: return Arm.DataORR_Imm; + case 0xD: return Arm.DataMOV_Imm; + case 0xE: return Arm.DataBIC_Imm; + case 0xF: return Arm.DataMVN_Imm; + } + } + else + { + switch (opcode) + { + case 0x0: return Arm.DataAND_Reg; + case 0x1: return Arm.DataEOR_Reg; + case 0x2: return Arm.DataSUB_Reg; + case 0x3: return Arm.DataRSB_Reg; + case 0x4: return Arm.DataADD_Reg; + case 0x5: return Arm.DataADC_Reg; + case 0x6: return Arm.DataSBC_Reg; + case 0x7: return Arm.DataRSC_Reg; + case 0x8: return Arm.DataTST_Reg; + case 0x9: return Arm.DataTEQ_Reg; + case 0xA: return Arm.DataCMP_Reg; + case 0xB: return Arm.DataCMN_Reg; + case 0xC: return Arm.DataORR_Reg; + case 0xD: return Arm.DataMOV_Reg; + case 0xE: return Arm.DataBIC_Reg; + case 0xF: return Arm.DataMVN_Reg; + } + } + } + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1100000000000000000000000000) == 0b0100000000000000000000000000) // LDR / STR + { + bool L = BitTest(ins, 20); + bool useRegister = BitTest(ins, 25); + if (useRegister) + { + if (L) + { + return Arm.RegularLDR_Reg; + } + else + { + return Arm.RegularSTR_Reg; + } + } + else + { + if (L) + { + return Arm.RegularLDR_Imm; + } + else + { + return Arm.RegularSTR_Imm; + } + } + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1110000000000000000000000000) == 0b1000000000000000000000000000) // LDM / STM + { + bool L = BitTest(ins, 20); // Load vs Store + + if (Armv5) + { + if (L) + { + return Arm.LDM_V5; + } + else + { + return Arm.STM_V5; + } + } + else + { + if (L) + { + return Arm.LDM; + } + else + { + return Arm.STM; + } + } + } + else if ((ins & 0b1111000000000000000000010000) == 0b1110000000000000000000010000) // Coprocessor register transfers + { + if (Armv5) + { + if (BitTest(ins, 20)) + { + return Arm.MRC; + } + else + { + return Arm.MCR; + } + } + } + // id mask 0b1111111100000000000011110000 0b1111111100000000000011110000 + else if ((ins & 0b1111000000000000000000000000) == 0b1111000000000000000000000000) // SWI - Software Interrupt + { + return Arm.SWI; + } + return Arm.Invalid; + } + + public ThumbExecutor GetInstructionThumb(ushort ins) + { + switch ((ins >> 13) & 0b111) + { + case 0b000: // Shift by immediate, Add/subtract register, Add/subtract immediate + { + switch ((ins >> 11) & 0b11) + { + case 0b00: // LSL (1) + return Thumb.ImmShiftLSL; + case 0b01: // LSR (1) + return Thumb.ImmShiftLSR; + case 0b10: // ASR (1) + return Thumb.ImmShiftASR; + case 0b11: // Add/subtract/compare/move immediate + { + switch ((ins >> 9) & 0b11) + { + case 0b00: // ADD (3) + return Thumb.ImmAluADD1; + case 0b01: // SUB (3) + return Thumb.ImmAluSUB1; + case 0b10: // ADD (1) // MOV (2) + return Thumb.ImmAluADD2; + case 0b11: // SUB (1) + return Thumb.ImmAluSUB2; + } + } + break; + } + } + break; + case 0b001: // Add/subtract/compare/move immediate + { + + switch ((ins >> 11) & 0b11) + { + case 0b00: // MOV (1) + return Thumb.MovImmediate; + case 0b01: // CMP (1) + return Thumb.CmpImmediate; + case 0b10: // ADD (2) + return Thumb.AddImmediate; + case 0b11: // SUB (2) + return Thumb.SubImmediate; + } + } + break; + case 0b010: + { + if ((ins & 0b1111110000000000) == 0b0100000000000000) // Data Processing + { + + uint opcode = (uint)((ins >> 6) & 0xFU); + switch (opcode) + { + case 0x0: // AND + return Thumb.DataAND; + case 0x1: // EOR + return Thumb.DataEOR; + case 0x2: // LSL (2) + return Thumb.DataLSL; + case 0x3: // LSR (2) + return Thumb.DataLSR; + case 0x4: // ASR (2) + return Thumb.DataASR; + case 0x5: // ADC + return Thumb.DataADC; + case 0x6: // SBC + return Thumb.DataSBC; + case 0x7: // ROR + return Thumb.DataROR; + case 0x8: // TST + return Thumb.DataTST; + case 0x9: // NEG / RSB + return Thumb.DataNEG; + case 0xA: // CMP (2) + return Thumb.DataCMP; + case 0xB: // CMN + return Thumb.DataCMN; + case 0xC: // ORR + return Thumb.DataORR; + case 0xD: // MUL + return Thumb.DataMUL; + case 0xE: // BIC + return Thumb.DataBIC; + case 0xF: // MVN + return Thumb.DataMVN; + } + } + else if ((ins & 0b1111110000000000) == 0b0100010000000000) // Special Data Processing / Branch-exchange instruction set + { + switch ((ins >> 8) & 0b11) + { + case 0b00: // ADD (4) + return Thumb.SpecialDataADD; + case 0b01: // CMP (3) + return Thumb.SpecialDataCMP; + case 0b10:// MOV (3) + return Thumb.SpecialDataMOV; + case 0b11: // BX + return Thumb.SpecialDataBX; + } + } + else if ((ins & 0b1111100000000000) == 0b0100100000000000) // LDR (3) - Load from literal pool + { + return Thumb.LDRLiteralPool; + } + else if ((ins & 0b1111000000000000) == 0b0101000000000000) // Load/store register offset + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + switch ((ins >> 9) & 0b111) + { + case 0b000: // STR (2) + return Thumb.RegOffsSTR; + case 0b001: // STRH (2) + return Thumb.RegOffsSTRH; + case 0b010: // STRB (2) + return Thumb.RegOffsSTRB; + case 0b011: // LDRSB + return Thumb.RegOffsLDRSB; + case 0b100: // LDR (2) + return Thumb.RegOffsLDR; + case 0b101: // LDRH (2) + return Thumb.RegOffsLDRH; + case 0b110: // LDRB (2) + return Thumb.RegOffsLDRB; + case 0b111: // LDRSH + return Thumb.RegOffsLDRSH; + // default: + // Error("Load/store register offset invalid opcode"); + } + } + } + break; + case 0b011: // Load/store word/byte immediate offset + { + + switch ((ins >> 11) & 0b11) + { + case 0b01: // LDR (1) + return Thumb.ImmOffsLDR; + case 0b00: // STR (1) + return Thumb.ImmOffsSTR; + case 0b10: // STRB (1) + return Thumb.ImmOffsSTRB; + case 0b11: // LDRB (1) + return Thumb.ImmOffsLDRB; + } + } + break; + case 0b100: + { + if ((ins & 0b1111000000000000) == 0b1000000000000000) // STRH (1) / LDRH (1) - Load/Store Halfword Immediate Offset + { + bool load = BitTest(ins, 11); + if (load) + { + return Thumb.ImmLDRH; + } + else + { + return Thumb.ImmSTRH; + } + } + else if ((ins & 0b1111100000000000) == 0b1001100000000000) // LDR (4) - Load from stack + { + return Thumb.StackLDR; + } + else if ((ins & 0b1111100000000000) == 0b1001000000000000) // STR (3) - Store to stack + { + return Thumb.StackSTR; + } + } + break; + case 0b101: + { + if ((ins & 0b1111000000000000) == 0b1011000000000000) // Miscellaneous (categorized like in the ARM reference manual) + { + if ((ins & 0b1111011000000000) == 0b1011010000000000) // POP & PUSH + { + if (BitTest(ins, 11)) + { + return Thumb.POP; + } + else + { + return Thumb.PUSH; + } + } + else if ((ins & 0b1111111110000000) == 0b1011000000000000) // ADD (7) + { + return Thumb.MiscImmADD; + } + else if ((ins & 0b1111111110000000) == 0b1011000010000000) // SUB (4) + { + return Thumb.MiscImmSUB; + } + else if ((ins & 0b1111111111000000) == 0b1011101011000000) // REVSH + { + return Thumb.MiscREVSH; + } + } + else if ((ins & 0b1111100000000000) == 0b1010000000000000) // ADD (5) - Add to PC + { + return Thumb.MiscPcADD; + } + else if ((ins & 0b1111100000000000) == 0b1010100000000000) // ADD (6) - Add to SP + { + return Thumb.MiscSpADD; + } + } + break; + case 0b110: + { + if ((ins & 0b1111000000000000) == 0b1100000000000000) // LDMIA, STMIA - Load/Store Multiple + { + if (BitTest(ins, 11)) + { + return Thumb.LDMIA; + } + else + { + return Thumb.STMIA; + } + } + else if ((ins & 0b1111111100000000) == 0b1101111100000000) // SWI - Software Interrupt + { + return Thumb.SWI; + } + else if ((ins & 0b1111000000000000) == 0b1101000000000000) // B (1) - Conditional + { + return Thumb.ConditionalB; + } + } + break; + case 0b111: + { + if ((ins & 0b1111100000000000) == 0b1110000000000000) // B (2) - Unconditional + { + return Thumb.UnconditionalB; + } + else if ((ins & 0b1110000000000000) == 0b1110000000000000) // BL, BLX - Branch With Link (Optional Exchange) + { + uint H = (uint)((ins >> 11) & 0b11); + switch (H) + { + case 0b10: return Thumb.BLUpperFill; + case 0b11: return Thumb.BLToThumb; + case 0b01: return Thumb.BLToArm; + } + } + } + break; + // default: + // Error("Unknown THUMB instruction"); + } + + return Thumb.Invalid; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool CheckCondition(uint code) + { + // Unconditional execution is most common, do a quick check + // instead of going through a slow switch + if (code == 0xE) + { + return true; + } + + switch (code) + { + case 0x0: // Zero, Equal, Z=1 + return Zero; + case 0x1: // Nonzero, Not Equal, Z=0 + return !Zero; + case 0x2: // Unsigned higher or same, C=1 + return Carry; + case 0x3: // Unsigned lower, C=0 + return !Carry; + case 0x4: // Signed Negative, Minus, N=1 + return Negative; + case 0x5: // Signed Positive or Zero, Plus, N=0 + return !Negative; + case 0x6: // Signed Overflow, V=1 + return Overflow; + case 0x7: // Signed No Overflow, V=0 + return !Overflow; + case 0x8: // Unsigned Higher, C=1 && Z=0 + return Carry && !Zero; + case 0x9: // Unsigned Lower or Same + return !Carry || Zero; + case 0xA: // Signed Greater or Equal + return Negative == Overflow; + case 0xB: // Signed Less Than + return Negative != Overflow; + case 0xC: // Signed Greater Than + return !Zero && Negative == Overflow; + case 0xD: // Signed less or Equal, Z=1 or N!=V + return Zero || (Negative != Overflow); + case 0xE: // Always + return true; + case 0xF: // some ARMv5 instructions have 0xF as condition code in encoding + return true; + } + + return false; + } + + public uint GetCPSR() + { + uint val = 0; + + if (Negative) val = BitSet(val, 31); + if (Zero) val = BitSet(val, 30); + if (Carry) val = BitSet(val, 29); + if (Overflow) val = BitSet(val, 28); + if (Sticky) val = BitSet(val, 27); + + if (IRQDisable) val = BitSet(val, 7); + if (FIQDisable) val = BitSet(val, 6); + if (ThumbState) val = BitSet(val, 5); + + val |= GetMode(); + return val; + } + + public void SetCPSR(uint val) + { + Negative = BitTest(val, 31); + Zero = BitTest(val, 30); + Carry = BitTest(val, 29); + Overflow = BitTest(val, 28); + Sticky = BitTest(val, 27); + + IRQDisable = BitTest(val, 7); + FIQDisable = BitTest(val, 6); + bool newThumbState = BitTest(val, 5); + if (newThumbState != ThumbState) + { + StateChange(); + } + ThumbState = newThumbState; + + SetMode((Arm7Mode)(val & 0b01111)); + } + + public uint GetSPSR() + { + switch (Mode) + { + case Arm7Mode.FIQ: + case Arm7Mode.OldFIQ: + return SPSR_fiq; + case Arm7Mode.SVC: + case Arm7Mode.OldSVC: + return SPSR_svc; + case Arm7Mode.ABT: + return SPSR_abt; + case Arm7Mode.IRQ: + case Arm7Mode.OldIRQ: + return SPSR_irq; + case Arm7Mode.UND: + return SPSR_und; + + } + + // Error("No SPSR in this mode!"); + return GetCPSR(); + } + public void SetSPSR(uint set) + { + switch (Mode) + { + case Arm7Mode.FIQ: + case Arm7Mode.OldFIQ: + SPSR_fiq = set; + return; + case Arm7Mode.SVC: + case Arm7Mode.OldSVC: + SPSR_svc = set; + return; + case Arm7Mode.ABT: + SPSR_abt = set; + return; + case Arm7Mode.IRQ: + case Arm7Mode.OldIRQ: + SPSR_irq = set; + return; + case Arm7Mode.UND: + SPSR_und = set; + return; + + } + + SetCPSR(set); + + // Error("No SPSR in this mode!"); + } + + public uint GetModeReg(uint reg, Arm7Mode mode) + { + if (mode == Mode) + { + return R[reg]; + } + + switch (mode) + { + case Arm7Mode.USR: + case Arm7Mode.SYS: return Rusr[reg - 8]; + case Arm7Mode.FIQ: return Rfiq[reg - 8]; + case Arm7Mode.IRQ: return Rirq[reg - 13]; + case Arm7Mode.SVC: return Rsvc[reg - 13]; + case Arm7Mode.ABT: return Rabt[reg - 13]; + case Arm7Mode.UND: return Rund[reg - 13]; + } + + return 0; + } + + public void SetModeReg(uint reg, Arm7Mode mode, uint val) + { + if (mode == Mode) + { + R[reg] = val; + } + + switch (mode) + { + case Arm7Mode.USR: + case Arm7Mode.SYS: Rusr[reg - 8] = val; break; + case Arm7Mode.FIQ: Rfiq[reg - 8] = val; break; + case Arm7Mode.IRQ: Rirq[reg - 13] = val; break; + case Arm7Mode.SVC: Rsvc[reg - 13] = val; break; + case Arm7Mode.ABT: Rabt[reg - 13] = val; break; + case Arm7Mode.UND: Rund[reg - 13] = val; break; + } + } + + public void SetMode(Arm7Mode mode) + { + // Bit 4 of mode is always set + mode |= (Arm7Mode)0b10000; + + // Store registers based on current mode + switch (Mode) + { + case Arm7Mode.USR: + case Arm7Mode.SYS: for (uint i = 0; i < 7; i++) Rusr[i] = R[8 + i]; break; + case Arm7Mode.FIQ: for (uint i = 0; i < 7; i++) Rfiq[i] = R[8 + i]; break; + case Arm7Mode.SVC: for (uint i = 0; i < 2; i++) Rsvc[i] = R[13 + i]; break; + case Arm7Mode.ABT: for (uint i = 0; i < 2; i++) Rabt[i] = R[13 + i]; break; + case Arm7Mode.IRQ: for (uint i = 0; i < 2; i++) Rirq[i] = R[13 + i]; break; + case Arm7Mode.UND: for (uint i = 0; i < 2; i++) Rund[i] = R[13 + i]; break; + } + + switch (mode) + { + case Arm7Mode.USR: + case Arm7Mode.SYS: for (uint i = 5; i < 7; i++) R[8 + i] = Rusr[i]; break; + case Arm7Mode.FIQ: for (uint i = 0; i < 7; i++) R[8 + i] = Rfiq[i]; break; + case Arm7Mode.SVC: for (uint i = 0; i < 2; i++) R[13 + i] = Rsvc[i]; break; + case Arm7Mode.ABT: for (uint i = 0; i < 2; i++) R[13 + i] = Rabt[i]; break; + case Arm7Mode.IRQ: for (uint i = 0; i < 2; i++) R[13 + i] = Rirq[i]; break; + case Arm7Mode.UND: for (uint i = 0; i < 2; i++) R[13 + i] = Rund[i]; break; + } + + if (Mode == Arm7Mode.FIQ) + for (uint i = 0; i < 5; i++) R[8 + i] = Rusr[i]; + + Mode = mode; + } + + public uint GetMode() + { + return (uint)Mode; + } + + public String Debug = ""; + + [Conditional("DONT")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ResetDebug() + { + Debug = ""; + } + + [Conditional("DONT")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void LineDebug(String s) + { + Debug += $"{s}\n"; + } + + // [Conditional("DEBUG")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Error(String s) + { + Debug += $"ERROR:\n"; + Debug += $"{s}\n"; + + Errored = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckOverflowSub(uint val1, uint val2, uint result) + { + return ((val1 ^ val2) & ((val1 ^ result)) & 0x80000000) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckOverflowAdd(uint val1, uint val2, uint result) + { + return (~(val1 ^ val2) & ((val1 ^ result)) & 0x80000000) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte Read8(uint addr) + { + InstructionCycles += Timing8And16[(addr >> 24) & 0xF]; + return Mem.Read8(addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Read16(uint addr) + { + InstructionCycles += Timing8And16[(addr >> 24) & 0xF]; + return Mem.Read16(addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Read32(uint addr) + { + InstructionCycles += Timing32[(addr >> 24) & 0xF]; + return Mem.Read32(addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Read16InstrFetch(uint addr) + { + InstructionCycles += Timing8And16InstrFetch[(addr >> 24) & 0xF]; + return Mem.Read16(addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint Read32InstrFetch(uint addr) + { + InstructionCycles += Timing32InstrFetch[(addr >> 24) & 0xF]; + return Mem.Read32(addr); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write8(uint addr, byte val) + { + // if (addr == 0x300402C) Console.WriteLine("DMA1 Write8: " + Util.HexN(GetCurrentInstrAddr(), 8)); + // if (addr == 0x300465C) Console.WriteLine("DMA2 Write8: " + Util.HexN(GetCurrentInstrAddr(), 8)); + + InstructionCycles += Timing8And16[(addr >> 24) & 0xF]; + Mem.Write8(addr, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write16(uint addr, ushort val) + { + // if (addr == 0x300402C) Console.WriteLine("DMA1 Write16: " + Util.HexN(GetCurrentInstrAddr(), 8)); + // if (addr == 0x300465C) Console.WriteLine("DMA2 Write16: " + Util.HexN(GetCurrentInstrAddr(), 8)); + + InstructionCycles += Timing8And16[(addr >> 24) & 0xF]; + Mem.Write16(addr, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write32(uint addr, uint val) + { + // if (addr == 0x300402C) Console.WriteLine("DMA1 Write32: " + Util.HexN(GetCurrentInstrAddr(), 8)); + // if (addr == 0x300465C) Console.WriteLine("DMA2 Write32: " + Util.HexN(GetCurrentInstrAddr(), 8)); + + InstructionCycles += Timing32[(addr >> 24) & 0xF]; + Mem.Write32(addr, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ICycle() + { + InstructionCycles += 1; + } + + public byte* Timing8And16 = AllocateUnmanagedArray(16); + public byte* Timing32 = AllocateUnmanagedArray(16); + public byte* Timing8And16InstrFetch = AllocateUnmanagedArray(16); + public byte* Timing32InstrFetch = AllocateUnmanagedArray(16); + + public uint GetCurrentInstrAddr() + { + return (uint)(R[15] - (ThumbState ? 4 : 8)); + } + } +} diff --git a/Assets/emulator/cpu/Arm7.cs.meta b/Assets/emulator/cpu/Arm7.cs.meta new file mode 100644 index 0000000..d9c2bed --- /dev/null +++ b/Assets/emulator/cpu/Arm7.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdbb33da106a94d408b21f7b02ea9466 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/cpu/Thumb.cs b/Assets/emulator/cpu/Thumb.cs new file mode 100644 index 0000000..ea48967 --- /dev/null +++ b/Assets/emulator/cpu/Thumb.cs @@ -0,0 +1,1346 @@ +using static OptimeGBA.Bits; +using static Util; +using System.Numerics; + +namespace OptimeGBA +{ + public delegate void ThumbExecutor(Arm7 arm7, ushort ins); + + public unsafe sealed class Thumb + { + public static void MovImmediate(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 8) & 0b111); + uint immed8 = ins & 0xFFu; + + arm7.LineDebug("MOV | Move large immediate to register"); + + arm7.R[rd] = immed8; + + arm7.Negative = false; + arm7.Zero = immed8 == 0; + } + + public static void CmpImmediate(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 8) & 0b111); + uint immed8 = ins & 0xFFu; + + arm7.LineDebug("CMP (1)"); + + uint rnVal = arm7.R[rd]; + uint alu_out = rnVal - immed8; + + arm7.Negative = BitTest(alu_out, 31); + arm7.Zero = alu_out == 0; + arm7.Carry = !(immed8 > rnVal); + arm7.Overflow = Arm7.CheckOverflowSub(rnVal, immed8, alu_out); + } + + public static void AddImmediate(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 8) & 0b111); + uint immed8 = ins & 0xFFu; + + arm7.LineDebug("ADD (2)"); + + uint rdVal = arm7.R[rd]; + uint final = rdVal + immed8; + + arm7.R[rd] = final; + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = (long)rdVal + (long)immed8 > 0xFFFFFFFF; + arm7.Overflow = Arm7.CheckOverflowAdd(rdVal, immed8, final); + } + + public static void SubImmediate(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 8) & 0b111); + uint immed8 = ins & 0xFFu; + + arm7.LineDebug("SUB (2)"); + + uint rdVal = arm7.R[rd]; + + uint final = rdVal - immed8; + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = !(immed8 > rdVal); + arm7.Overflow = Arm7.CheckOverflowSub(rdVal, immed8, final); + } + + public static void DataAND(Arm7 arm7, ushort ins) // AND + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rm = (uint)((ins >> 3) & 0b111); + + arm7.LineDebug("AND"); + + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + uint final = rdVal & rmVal; + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + } + + public static void DataEOR(Arm7 arm7, ushort ins) // EOR + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rm = (uint)((ins >> 3) & 0b111); + + arm7.LineDebug("EOR"); + + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + rdVal = rdVal ^ rmVal; + arm7.R[rd] = rdVal; + + arm7.Negative = BitTest(rdVal, 31); + arm7.Zero = rdVal == 0; + } + + public static void DataLSL(Arm7 arm7, ushort ins) // LSL (2) + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rm = (uint)((ins >> 3) & 0b111); + + arm7.LineDebug("LSL (2) | Logical Shift Left"); + + uint rdValue = arm7.R[rd]; + uint rsValue = arm7.R[rm]; + + if ((rsValue & 0xFF) == 0) + { + // Do nothing + } + else if ((rsValue & 0xFF) < 32) + { + arm7.Carry = BitTest(rdValue, (byte)(32 - (rsValue & 0xFF))); + rdValue = LogicalShiftLeft32(rdValue, (byte)(rsValue & 0xFF)); + } + else if ((rsValue & 0xFF) == 32) + { + arm7.Carry = BitTest(rdValue, 0); + rdValue = 0; + } + else + { + arm7.Carry = false; + rdValue = 0; + } + + arm7.R[rd] = rdValue; + + arm7.Negative = BitTest(rdValue, 31); + arm7.Zero = rdValue == 0; + } + + public static void DataLSR(Arm7 arm7, ushort ins) // LSR (2) + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("LSR (2)"); + + uint rdVal = arm7.R[rd]; + uint rsVal = arm7.R[rs]; + + if ((rsVal & 0xFF) == 0) + { + // everything unaffected + } + else if ((rsVal & 0xFF) < 32) + { + arm7.Carry = BitTest(rdVal, (byte)((rsVal & 0xFF) - 1)); + arm7.R[rd] = LogicalShiftRight32(rdVal, (byte)(rsVal & 0xFF)); + } + else if ((rsVal & 0xFF) == 32) + { + arm7.Carry = BitTest(rdVal, 31); + arm7.R[rd] = 0; + } + else + { + arm7.Carry = false; + arm7.R[rd] = 0; + } + + rdVal = arm7.R[rd]; + + arm7.Negative = BitTest(rdVal, 31); + arm7.Zero = rdVal == 0; + } + + public static void DataASR(Arm7 arm7, ushort ins) // ASR (2) + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("ASR (2)"); + + uint rdVal = arm7.R[rd]; + uint rsVal = arm7.R[rs]; + + if ((rsVal & 0xFF) == 0) + { + // Do nothing + } + else if ((rsVal & 0xFF) < 32) + { + arm7.Carry = BitTest(rdVal, (byte)((rsVal & 0xFF) - 1)); + rdVal = ArithmeticShiftRight32(rdVal, (byte)(rsVal & 0xFF)); + } + else + { + arm7.Carry = BitTest(rdVal, 31); + if (!arm7.Carry) + { + rdVal = 0; + } + else + { + rdVal = 0xFFFFFFFF; + } + } + + arm7.R[rd] = rdVal; + + arm7.Negative = BitTest(rdVal, 31); + arm7.Zero = rdVal == 0; + } + + public static void DataADC(Arm7 arm7, ushort ins) // ADC + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + uint final = rdVal + rmVal + (arm7.Carry ? 1U : 0); + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = rdVal == 0; + arm7.Carry = (long)rdVal + (long)rmVal + (arm7.Carry ? 1U : 0) > 0xFFFFFFFF; + arm7.Overflow = Arm7.CheckOverflowAdd(rdVal, rmVal, final); + } + + public static void DataSBC(Arm7 arm7, ushort ins) // SBC + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("SBC"); + + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + uint final = rdVal - rmVal - (!arm7.Carry ? 1U : 0); + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = !((long)rmVal + (!arm7.Carry ? 1U : 0) > rdVal); + arm7.Overflow = Arm7.CheckOverflowSub(rdVal, rmVal, final); + } + + public static void DataROR(Arm7 arm7, ushort ins) // ROR + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("ROR"); + + uint rdVal = arm7.R[rd]; + uint rsVal = arm7.R[rs]; + + if ((rsVal & 0xFF) == 0) + { + // Do nothing + } + else if ((rsVal & 0b11111) == 0) + { + arm7.Carry = BitTest(rdVal, 31); + } + else + { + arm7.Carry = BitTest(rdVal, (byte)((rsVal & 0b11111) - 1)); + rdVal = RotateRight32(rdVal, (byte)(rsVal & 0b11111)); + arm7.R[rd] = rdVal; + } + + arm7.Negative = BitTest(rdVal, 31); + arm7.Zero = rdVal == 0; + } + + public static void DataTST(Arm7 arm7, ushort ins) // TST + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("TST"); + + uint rnValue = arm7.R[rn]; + uint rmValue = arm7.R[rm]; + + uint final = rnValue & rmValue; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + } + + public static void DataNEG(Arm7 arm7, ushort ins) // NEG / RSB + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("NEG / RSB"); + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + uint final = 0 - rmVal; + + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = !(rmVal > 0); + arm7.Overflow = Arm7.CheckOverflowSub(0, rmVal, final); + } + + public static void DataCMP(Arm7 arm7, ushort ins) // CMP (2) + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("CMP (2)"); + + uint rnVal = arm7.R[(uint)((ins >> 0) & 0b111)]; + uint rmVal = arm7.R[(uint)((ins >> 3) & 0b111)]; + + uint alu_out = rnVal - rmVal; + + arm7.Negative = BitTest(alu_out, 31); + arm7.Zero = alu_out == 0; + arm7.Carry = !(rmVal > rnVal); + arm7.Overflow = Arm7.CheckOverflowSub(rnVal, rmVal, alu_out); + } + + public static void DataCMN(Arm7 arm7, ushort ins) // CMN + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("CMN"); + + uint rnVal = arm7.R[(uint)((ins >> 0) & 0b111)]; + uint rmVal = arm7.R[(uint)((ins >> 3) & 0b111)]; + + uint alu_out = rnVal + rmVal; + + arm7.Negative = BitTest(alu_out, 31); + arm7.Zero = alu_out == 0; + arm7.Carry = (long)rmVal + (long)rnVal > 0xFFFFFFFF; + arm7.Overflow = Arm7.CheckOverflowAdd(rnVal, rmVal, alu_out); + } + + public static void DataORR(Arm7 arm7, ushort ins) // ORR + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("ORR"); + + arm7.R[rd] = arm7.R[rd] | arm7.R[rm]; + arm7.Negative = BitTest(arm7.R[rd], 31); + arm7.Zero = arm7.R[rd] == 0; + } + + public static void DataMUL(Arm7 arm7, ushort ins) // MUL + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("MUL"); + + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + rdVal = (rmVal * rdVal); + arm7.R[rd] = rdVal; + + arm7.Negative = BitTest(rdVal, 31); + arm7.Zero = rdVal == 0; + } + + public static void DataBIC(Arm7 arm7, ushort ins) // BIC + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("BIC"); + + uint rdValue = arm7.R[rd]; + uint rmValue = arm7.R[rm]; + + uint final = rdValue & (~rmValue); + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + } + + public static void DataMVN(Arm7 arm7, ushort ins) // MVN + { + // Rm/Rs and Rd/Rn are the same, just different names for opcodes in this encoding + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = rd; + uint rm = (uint)((ins >> 3) & 0b111); + uint rs = rm; + + arm7.LineDebug("MVN"); + + arm7.R[rd] = ~arm7.R[rm]; + arm7.Negative = BitTest(arm7.R[rd], 31); + arm7.Zero = arm7.R[rd] == 0; + } + + + public static void SpecialDataADD(Arm7 arm7, ushort ins) // ADD (4) + { + arm7.LineDebug("ADD (4)"); + + uint rd = (uint)((ins >> 0) & 0b111) | ((ins & BIT_7) >> 4); + uint rm = (uint)((ins >> 3) & 0b111) | ((ins & BIT_6) >> 3); + uint rdVal = arm7.R[rd]; + uint rmVal = arm7.R[rm]; + + uint final = rdVal + rmVal; + arm7.R[rd] = final; + + if (rd == 15) + { + arm7.FlushPipeline(); + } + } + + public static void SpecialDataCMP(Arm7 arm7, ushort ins) // CMP (3) + { + arm7.LineDebug("CMP (3)"); + + uint rn = (uint)((ins >> 0) & 0b111) | ((ins & BIT_7) >> 4); + uint rm = (uint)((ins >> 3) & 0b111) | ((ins & BIT_6) >> 3); + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint alu_out = rnVal - rmVal; + + arm7.Negative = BitTest(alu_out, 31); + arm7.Zero = alu_out == 0; + arm7.Carry = !(rmVal > rnVal); + arm7.Overflow = Arm7.CheckOverflowSub(rnVal, rmVal, alu_out); + } + + public static void SpecialDataMOV(Arm7 arm7, ushort ins)// MOV (3) + { + arm7.LineDebug("MOV (3)"); + + uint rd = (uint)((ins >> 0) & 0b111) | ((ins & BIT_7) >> 4); + uint rm = (uint)((ins >> 3) & 0b111) | ((ins & BIT_6) >> 3); + + arm7.R[rd] = arm7.R[rm]; + + if (rd == 15) + { + arm7.R[15] &= 0xFFFFFFFE; + arm7.FlushPipeline(); + } + } + + public static void SpecialDataBX(Arm7 arm7, ushort ins) // BX + { + arm7.LineDebug("BX | Optionally switch back to ARM state"); + + uint rm = (uint)((ins >> 3) & 0xF); // High bit is technically an H bit, but can be ignored here + uint val = arm7.R[rm]; + arm7.LineDebug($"R{rm}"); + + // BLX (2) + if (BitTest(ins, 7)) + { + arm7.R[14] = (arm7.R[15] - 2) | 1; + } + + arm7.ThumbState = BitTest(val, 0); + arm7.R[15] = val & 0xFFFFFFFE; + arm7.FlushPipeline(); + + if (!arm7.ThumbState) + { + arm7.StateChange(); + } + } + + public static void LDRLiteralPool(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LDR (3) | PC Relative, 8-bit Immediate"); + + uint rd = (uint)((ins >> 8) & 0b111); + uint immed8 = (uint)((ins >> 0) & 0xFF); + + uint addr = (arm7.R[15] & 0xFFFFFFFC) + (immed8 * 4); + + uint readAddr = addr & ~0b11U; + uint readVal = arm7.Read32(readAddr); + arm7.R[rd] = RotateRight32(readVal, (byte)((addr & 0b11) * 8)); + } + + public static void ImmShiftLSL(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LSL (1) | Logical Shift Left"); + + uint immed5 = (uint)((ins >> 6) & 0b11111); + uint rd = (uint)((ins >> 0) & 0b111); + uint rmValue = arm7.R[(uint)((ins >> 3) & 0b111)]; + + if (immed5 == 0) + { + arm7.R[rd] = rmValue; + } + else + { + arm7.Carry = BitTest(rmValue, (byte)(32 - immed5)); + arm7.R[rd] = LogicalShiftLeft32(rmValue, (byte)immed5); + } + + arm7.Negative = BitTest(arm7.R[rd], 31); + arm7.Zero = arm7.R[rd] == 0; + } + + public static void ImmShiftLSR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LSR (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rm = (uint)((ins >> 3) & 0b111); + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint rmVal = arm7.R[rm]; + + uint final; + if (immed5 == 0) + { + arm7.Carry = BitTest(rmVal, 31); + final = 0; + } + else + { + arm7.Carry = BitTest(rmVal, (byte)(immed5 - 1)); + final = LogicalShiftRight32(rmVal, (byte)immed5); + } + + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + } + + public static void ImmShiftASR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ASR (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rmValue = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint immed5 = (uint)((ins >> 6) & 0b11111); + + if (immed5 == 0) + { + arm7.Carry = BitTest(rmValue, 31); + if (BitTest(rmValue, 31)) + { + arm7.R[rd] = 0xFFFFFFFF; + } + else + { + arm7.R[rd] = 0; + } + } + else + { + arm7.Carry = BitTest(rmValue, (byte)(immed5 - 1)); + arm7.R[rd] = ArithmeticShiftRight32(rmValue, (byte)immed5); + } + + arm7.Negative = BitTest(arm7.R[rd], 31); + arm7.Zero = arm7.R[rd] == 0; + } + + public static void ImmAluADD1(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ADD (3)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rnVal = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint rmVal = arm7.R[(uint)((ins >> 6) & 0b111)]; + uint final = rnVal + rmVal; + + arm7.R[rd] = final; + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = (long)rnVal + (long)rmVal > 0xFFFFFFFF; + arm7.Overflow = Arm7.CheckOverflowAdd(rnVal, rmVal, final); + } + + public static void ImmAluSUB1(Arm7 arm7, ushort ins) + { + arm7.LineDebug("SUB (3)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rnValue = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint rmValue = arm7.R[(uint)((ins >> 6) & 0b111)]; + + uint final = rnValue - rmValue; + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = !(rmValue > rnValue); + arm7.Overflow = Arm7.CheckOverflowSub(rnValue, rmValue, final); + } + + public static void ImmAluADD2(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ADD (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rnVal = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint immed3 = (uint)((ins >> 6) & 0b111); + + uint final = rnVal + immed3; + + arm7.R[rd] = final; + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = (long)rnVal + (long)immed3 > 0xFFFFFFFF; + arm7.Overflow = Arm7.CheckOverflowAdd(rnVal, immed3, final); + + if (rd == 15) arm7.FlushPipeline(); + } + + public static void ImmAluSUB2(Arm7 arm7, ushort ins) + { + arm7.LineDebug("SUB (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint immed3 = (uint)((ins >> 6) & 0b111); + + uint rdVal = arm7.R[rd]; + uint rnVal = arm7.R[rn]; + + uint final = rnVal - immed3; + arm7.R[rd] = final; + + arm7.Negative = BitTest(final, 31); + arm7.Zero = final == 0; + arm7.Carry = !(immed3 > rnVal); + arm7.Overflow = Arm7.CheckOverflowSub(rnVal, immed3, final); + } + + public static void ImmOffsLDR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LDR (1) | Base + Immediate"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rnValue = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnValue + (immed5 * 4); + + // Misaligned + uint readAddr = addr & ~0b11U; + uint readVal = arm7.Read32(readAddr); + arm7.R[rd] = RotateRight32(readVal, (byte)((addr & 0b11) * 8)); + + arm7.LineDebug($"Addr: {Util.HexN(addr, 8)}"); + + arm7.ICycle(); + } + + public static void ImmOffsSTR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("STR (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rnValue = arm7.R[(uint)((ins >> 3) & 0b111)]; + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnValue + (immed5 * 4); + arm7.LineDebug($"Addr: {Util.HexN(addr, 8)}"); + + arm7.Write32(addr & ~3U, arm7.R[rd]); + } + + public static void ImmOffsSTRB(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rdVal = arm7.R[rd]; + uint rn = (uint)((ins >> 3) & 0b111); + uint rnVal = arm7.R[rn]; + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnVal + immed5; + + arm7.LineDebug("STRB (1)"); + arm7.Write8(addr, (byte)rdVal); + } + + public static void ImmOffsLDRB(Arm7 arm7, ushort ins) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rdVal = arm7.R[rd]; + uint rn = (uint)((ins >> 3) & 0b111); + uint rnVal = arm7.R[rn]; + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnVal + immed5; + + arm7.LineDebug("LDRB (1)"); + arm7.R[rd] = arm7.Read8(addr); + + arm7.ICycle(); + } + + public static void RegOffsSTR(Arm7 arm7, ushort ins) // STR (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + arm7.LineDebug("STR (2)"); + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + arm7.Write32(addr & ~0b11U, arm7.R[rd]); + } + + public static void RegOffsSTRH(Arm7 arm7, ushort ins) // STRH (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + arm7.LineDebug("STRH (2)"); + + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + arm7.LineDebug("Store"); + uint rdVal = arm7.R[rd]; + // Forcibly align address to halfwords + arm7.Write16(addr & ~1u, (ushort)rdVal); + } + + public static void RegOffsSTRB(Arm7 arm7, ushort ins) // STRB (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + uint rdVal = arm7.R[rd]; + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + bool load = BitTest(ins, 11); + + arm7.LineDebug("STRB (2)"); + arm7.Write8(addr, (byte)rdVal); + } + + public static void RegOffsLDRSB(Arm7 arm7, ushort ins) // LDRSB + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + arm7.LineDebug("LDRSB"); + + // Sign extend + int readVal = (sbyte)arm7.Read8(addr); + + arm7.R[rd] = (uint)readVal; + + arm7.ICycle(); + } + + public static void RegOffsLDR(Arm7 arm7, ushort ins) // LDR (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + arm7.LineDebug("LDR (2)"); + + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + // Misaligned + uint readAddr = addr & ~0b11U; + uint readVal = arm7.Read32(readAddr); + arm7.R[rd] = RotateRight32(readVal, (byte)((addr & 0b11) * 8)); + + arm7.ICycle(); + } + + public static void RegOffsLDRH(Arm7 arm7, ushort ins) // LDRH (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + arm7.LineDebug("LDRH (2)"); + + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + arm7.LineDebug("Load"); + // Take care of alignment + arm7.R[rd] = RotateRight32(arm7.Read16(addr & ~1u), (byte)(8 * (addr & 1))); + + arm7.ICycle(); + } + + public static void RegOffsLDRB(Arm7 arm7, ushort ins) // LDRB (2) + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + uint rdVal = arm7.R[rd]; + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + bool load = BitTest(ins, 11); + + if (load) + { + arm7.LineDebug("LDRB (2)"); + arm7.R[rd] = arm7.Read8(addr); + } + else + { + arm7.LineDebug("STRB (2)"); + arm7.Write8(addr, (byte)rdVal); + } + + arm7.ICycle(); + } + + public static void RegOffsLDRSH(Arm7 arm7, ushort ins) // LDRSH + { + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rm = (uint)((ins >> 6) & 0b111); + + uint rnVal = arm7.R[rn]; + uint rmVal = arm7.R[rm]; + + uint addr = rnVal + rmVal; + + arm7.LineDebug("LDRSH"); + + int readVal; + if ((addr & 1) != 0) + { + // Misaligned, read byte instead. + readVal = (sbyte)arm7.Read8(addr); + } + else + { + readVal = (short)arm7.Read16(addr); + } + + arm7.R[rd] = (uint)readVal; + + arm7.ICycle(); + } + + public static void StackLDR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LDR (4)"); + + uint immed8 = (uint)((ins >> 0) & 0xFF); + uint rd = (uint)((ins >> 8) & 0b111); + + uint addr = arm7.R[13] + (immed8 * 4); + + // Misaligned + uint readAddr = addr & ~0b11U; + uint readVal = arm7.Read32(readAddr); + arm7.R[rd] = RotateRight32(readVal, (byte)((addr & 0b11) * 8)); + + arm7.ICycle(); + } + + public static void StackSTR(Arm7 arm7, ushort ins) + { + arm7.LineDebug("STR (3)"); + + uint immed8 = (uint)((ins >> 0) & 0xFF); + uint rd = (uint)((ins >> 8) & 0b111); + + uint addr = arm7.R[13] + (immed8 * 4); + arm7.Write32(addr & ~3U, arm7.R[rd]); + + arm7.ICycle(); + } + + public static void ImmLDRH(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LDRH (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rnVal = arm7.R[rn]; + + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnVal + (immed5 * 2); + + arm7.LineDebug("Load"); + arm7.R[rd] = RotateRight32(arm7.Read16(addr & ~1u), (byte)(8 * (addr & 1))); + + arm7.ICycle(); + } + + public static void ImmSTRH(Arm7 arm7, ushort ins) + { + arm7.LineDebug("STRH (1)"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rn = (uint)((ins >> 3) & 0b111); + uint rnVal = arm7.R[rn]; + + uint immed5 = (uint)((ins >> 6) & 0b11111); + + uint addr = rnVal + (immed5 * 2); + + arm7.LineDebug("Store"); + arm7.Write16(addr & ~1u, (ushort)arm7.R[rd]); + } + + public static void POP(Arm7 arm7, ushort ins) + { + arm7.LineDebug("POP"); + + // String regs = ""; + uint addr = arm7.R[13]; + + uint registerCount = (uint)BitOperations.PopCount(ins & 0x1FFU); + arm7.R[13] = addr + registerCount * 4; + + if (BitTest(ins, 0)) { /* regs += "R0 "; */ arm7.R[0] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 1)) { /* regs += "R1 "; */ arm7.R[1] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 2)) { /* regs += "R2 "; */ arm7.R[2] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 3)) { /* regs += "R3 "; */ arm7.R[3] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 4)) { /* regs += "R4 "; */ arm7.R[4] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 5)) { /* regs += "R5 "; */ arm7.R[5] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 6)) { /* regs += "R6 "; */ arm7.R[6] = arm7.Read32(addr & ~3u); addr += 4; } + if (BitTest(ins, 7)) { /* regs += "R7 "; */ arm7.R[7] = arm7.Read32(addr & ~3u); addr += 4; } + + if (BitTest(ins, 8)) + { + /* regs += "PC "; */ + arm7.R[15] = arm7.Read32(addr); + if (arm7.Armv5) + { + arm7.ThumbState = BitTest(arm7.R[15], 0); + } + arm7.FlushPipeline(); + arm7.LineDebug(Util.Hex(arm7.R[15], 8)); + addr += 4; + } + + + // Handle empty rlist + if ((ins & 0x1FF) == 0) + { + if (!arm7.Armv5) + { + arm7.R[15] = arm7.Read32(addr & ~3u); + arm7.FlushPipeline(); + } + System.Console.WriteLine("POP empty Rlist"); + arm7.R[13] += 0x40; + } + + // LineDebug(regs); + + arm7.ICycle(); + } + + public static void PUSH(Arm7 arm7, ushort ins) + { + arm7.LineDebug("PUSH"); + + uint addr = arm7.R[13]; + + addr -= 4 * (uint)BitOperations.PopCount((uint)ins & 0x1FF); + + if (BitTest(ins, 0)) { /* regs += "R0 "; */ arm7.Write32(addr & ~3u, arm7.R[0]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 1)) { /* regs += "R1 "; */ arm7.Write32(addr & ~3u, arm7.R[1]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 2)) { /* regs += "R2 "; */ arm7.Write32(addr & ~3u, arm7.R[2]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 3)) { /* regs += "R3 "; */ arm7.Write32(addr & ~3u, arm7.R[3]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 4)) { /* regs += "R4 "; */ arm7.Write32(addr & ~3u, arm7.R[4]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 5)) { /* regs += "R5 "; */ arm7.Write32(addr & ~3u, arm7.R[5]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 6)) { /* regs += "R6 "; */ arm7.Write32(addr & ~3u, arm7.R[6]); addr += 4; arm7.R[13] -= 4; } + if (BitTest(ins, 7)) { /* regs += "R7 "; */ arm7.Write32(addr & ~3u, arm7.R[7]); addr += 4; arm7.R[13] -= 4; } + + if (BitTest(ins, 8)) + { + /* regs += "LR "; */ + arm7.Write32(addr, arm7.R[14]); + addr += 4; + arm7.R[13] -= 4; + } + + // Handle empty rlist + if ((ins & 0x1FF) == 0) + { + if (!arm7.Armv5) + { + arm7.Write32(addr & ~3u, arm7.R[15]); + } + arm7.R[13] += 0x40; + } + + // LineDebug(regs); + } + + public static void MiscImmADD(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ADD (7)"); + uint immed7 = (uint)(ins & 0b1111111); + arm7.R[13] = arm7.R[13] + (immed7 << 2); + } + + public static void MiscImmSUB(Arm7 arm7, ushort ins) + { + arm7.LineDebug("SUB (4)"); + + uint immed7 = (uint)(ins & 0b1111111); + arm7.R[13] = arm7.R[13] - (immed7 << 2); + } + + public static void MiscREVSH(Arm7 arm7, ushort ins) + { + arm7.LineDebug("REVSH"); + + uint rd = (uint)((ins >> 0) & 0b111); + uint rdVal = arm7.R[rd]; + uint rn = (uint)((ins >> 3) & 0b111); + uint rnVal = arm7.R[rn]; + + uint rnValHalfLower = ((rnVal >> 0) & 0xFFFF); + uint rnValHalfUpper = ((rnVal >> 8) & 0xFFFF); + + rdVal &= 0xFFFF0000; + rdVal |= (rnValHalfUpper << 0); + rdVal |= (rnValHalfLower << 8); + + // Sign Extend + if (BitTest(rn, 7)) + { + rdVal |= 0xFFFF0000; + } + else + { + rdVal &= 0x0000FFFF; + } + + arm7.R[rd] = rdVal; + } + + public static void MiscPcADD(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ADD (5)"); + + uint immed8 = (uint)(ins & 0xFF); + uint rd = (uint)((ins >> 8) & 0b111); + + arm7.R[rd] = (arm7.R[15] & 0xFFFFFFFC) + (immed8 * 4); + } + + public static void MiscSpADD(Arm7 arm7, ushort ins) + { + arm7.LineDebug("ADD (6)"); + + uint immed8 = (uint)(ins & 0xFF); + uint rd = (uint)((ins >> 8) & 0b111); + + arm7.R[rd] = arm7.R[13] + (immed8 << 2); + } + + public static void LDMIA(Arm7 arm7, ushort ins) + { + arm7.LineDebug("LDMIA | Load Multiple Increment After"); + + uint rn = (uint)((ins >> 8) & 0b111); + uint addr = arm7.R[rn]; + + // String regs = ""; + + uint registerList = ins & 0xFFU; + uint registerCount = (uint)BitOperations.PopCount(registerList); + uint writebackVal = arm7.R[rn] + registerCount * 4; + + arm7.R[rn] = writebackVal; + + uint register = 0; + for (; registerList != 0; registerList >>= 1) + { + if (BitTest(registerList, 0)) + { + arm7.R[register] = arm7.Read32(addr & ~3u); + addr += 4; + } + register++; + } + + // Handle empty rlist + if ((ins & 0xFF) == 0) + { + if (!arm7.Armv5) + { + arm7.R[15] = arm7.Read32(addr & ~3u); + arm7.FlushPipeline(); + } + arm7.R[rn] += 0x40; + } + + // LineDebug(regs); + + arm7.ICycle(); + } + + public static void STMIA(Arm7 arm7, ushort ins) + { + arm7.LineDebug("STMIA | Store Multiple Increment After"); + + uint rn = (uint)((ins >> 8) & 0b111); + uint addr = arm7.R[rn]; + + // String regs = ""; + + arm7.R[15] += 2; + + uint registerList = ins & 0xFFU; + uint registerCount = (uint)BitOperations.PopCount(registerList); + uint writebackVal = arm7.R[rn] + registerCount * 4; + + uint register = 0; + for (; registerList != 0; registerList >>= 1) + { + if (BitTest(registerList, 0)) + { + arm7.Write32(addr & ~3u, arm7.R[register]); + addr += 4; + if (!arm7.Armv5) + { + arm7.R[rn] = writebackVal; + } + } + register++; + } + + if (arm7.Armv5) + { + arm7.R[rn] = writebackVal; + } + + // Handle empty rlist + if ((ins & 0xFF) == 0) + { + if (!arm7.Armv5) + { + arm7.Write32(addr & ~3u, arm7.R[15]); + } + arm7.R[rn] += 0x40; + } + + arm7.R[15] -= 2; + } + // LineDebug(regs); + + public static void SWI(Arm7 arm7, ushort ins) + { + arm7.SPSR_svc = arm7.GetCPSR(); + + arm7.SetMode(Arm7Mode.SVC); // Go into SVC / Supervisor mode + + arm7.R[14] = arm7.R[15] - 2; + + arm7.ThumbState = false; // Back to ARM state + arm7.IRQDisable = true; + + arm7.R[15] = arm7.VectorSoftwareInterrupt; + arm7.FlushPipeline(); + + arm7.StateChange(); + } + + public static void ConditionalB(Arm7 arm7, ushort ins) + { + arm7.LineDebug("B | Conditional Branch"); + uint cond = (uint)((ins >> 8) & 0xF); + bool condition = arm7.CheckCondition(cond); + + if (condition) + { + // B + int offset = (int)(ins & 0xFF) << 1; + // Signed with Two's Complement + offset = (offset << 23) >> 23; + + arm7.R[15] = (uint)(arm7.R[15] + offset); + arm7.FlushPipeline(); + } + else + { + arm7.LineDebug("Not Taken"); + } + } + + public static void UnconditionalB(Arm7 arm7, ushort ins) + { + arm7.LineDebug("B | Unconditional Branch"); + int signedImmed11 = (int)(ins & 0b11111111111) << 1; + signedImmed11 = (signedImmed11 << 20) >> 20; + + arm7.R[15] = (uint)(arm7.R[15] + signedImmed11); + arm7.FlushPipeline(); + } + + public static void BLUpperFill(Arm7 arm7, ushort ins) + { + arm7.LineDebug("BL, BLX | Branch With Link (And Exchange)"); + + uint H = (uint)((ins >> 11) & 0b11); + int offset11 = ins & 0b11111111111; + + offset11 <<= 12; + + // Sign extend + offset11 = ((int)offset11 << 9) >> 9; + + arm7.LineDebug($"offset11: {offset11}"); + arm7.R[14] = (uint)(arm7.R[15] + offset11); + arm7.LineDebug("Upper fill"); + } + + public static void BLToThumb(Arm7 arm7, ushort ins) + { + arm7.LineDebug("BL, BLX | Branch With Link (And Exchange)"); + + int offset11 = ins & 0b11111111111; + + uint oldR14 = arm7.R[14]; + arm7.R[14] = (arm7.R[15] - 2) | 1; + arm7.R[15] = (uint)(oldR14 + (offset11 << 1)); + arm7.R[15] &= 0xFFFFFFFE; + arm7.FlushPipeline(); + arm7.LineDebug($"Jump to ${Util.HexN(arm7.R[15], 8)}"); + arm7.LineDebug("Stay in THUMB state"); + } + + public static void BLToArm(Arm7 arm7, ushort ins) + { + arm7.LineDebug("BL, BLX | Branch With Link (And Exchange)"); + + int offset11 = ins & 0b11111111111; + + uint oldR14 = arm7.R[14]; + arm7.R[14] = (arm7.R[15] - 2) | 1; + arm7.R[15] = (uint)((oldR14 + (offset11 << 1)) & ~3); + arm7.ThumbState = false; + arm7.FlushPipeline(); + arm7.LineDebug($"Jump to ${Util.HexN(arm7.R[15], 8)}"); + arm7.LineDebug("Exit THUMB state"); + + arm7.StateChange(); + } + + public static void BLXRegister(Arm7 arm7, ushort ins) + { + + arm7.StateChange(); + } + + public static void Invalid(Arm7 arm7, ushort ins) + { + arm7.Error($"Invalid THUMB Instruction: {Hex(ins, 4)} @ {HexN(arm7.R[15], 8)}"); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/cpu/Thumb.cs.meta b/Assets/emulator/cpu/Thumb.cs.meta new file mode 100644 index 0000000..932bbbd --- /dev/null +++ b/Assets/emulator/cpu/Thumb.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4c625315174d2b449589c3b015a86c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Iris/Iris.Common.meta b/Assets/emulator/saving.meta similarity index 77% rename from Assets/Iris/Iris.Common.meta rename to Assets/emulator/saving.meta index fec6a2f..f40f3b1 100644 --- a/Assets/Iris/Iris.Common.meta +++ b/Assets/emulator/saving.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 51d282b83f644064f8adda50edd656d7 +guid: 30b648fdc4aff1d43b6fe55d747a6dc2 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/emulator/saving/Eeprom.cs b/Assets/emulator/saving/Eeprom.cs new file mode 100644 index 0000000..8b5e153 --- /dev/null +++ b/Assets/emulator/saving/Eeprom.cs @@ -0,0 +1,197 @@ +using System; +using static OptimeGBA.Bits; +namespace OptimeGBA +{ + public enum EepromState + { + Ready, + StartRequest, + ReceiveRequestType, + ReceiveAddrForRead, + ReceiveAddrForWrite, + ReceiveDataForWrite, + ReceiveTerminatingZero + } + + public enum EepromSize + { + Eeprom4k, + Eeprom64k + } + + public sealed class Eeprom : SaveProvider + { + EepromState State = EepromState.Ready; + EepromSize Size; + + public byte[] EEPROM = new byte[0x2000]; + public uint Addr = 0; + public uint ReadAddr = 0; + + public uint BitsRemaining = 0; + public uint ReadBitsRemaining = 0; + + public Gba Gba; + + public Eeprom(Gba gba, EepromSize size) + { + Gba = gba; + Size = size; + } + + public byte ReadBitEEPROM() + { + byte bitIndex = (byte)(ReadAddr & 7); + uint index = ReadAddr >> 3; + return (byte)(BitTest(EEPROM[index], bitIndex) ? 1 : 0); + } + public void WriteBitEEPROM(bool bit) + { + byte bitIndex = (byte)(Addr & 7); + uint index = Addr >> 3; + if (bit) + { + EEPROM[index] = BitSet(EEPROM[index], bitIndex); + } + else + { + EEPROM[index] = BitClear(EEPROM[index], bitIndex); + } + } + + public override byte Read8(uint addr) + { + if (Gba.Dma.DmaLock) + { + // Console.WriteLine("[EEPROM] Read from DMA"); + } + + byte val = 0; + if (ReadBitsRemaining > 0) + { + if (ReadBitsRemaining <= 64) + { + val = ReadBitEEPROM(); + // Console.WriteLine($"[EEPROM] Read (addr: {Util.Hex(ReadAddr, 4)}) {val}, bits remaining: " + ReadBitsRemaining); + ReadAddr++; + } + else + { + val = 1; + } + + ReadBitsRemaining--; + } + else + { + ReadBitsRemaining = 68; + } + + return val; + } + + public override void Write8(uint addr, byte val) + { + if (Gba.Dma.DmaLock) + { + // Console.WriteLine("[EEPROM] Write from DMA"); + } + + bool bit = BitTest(val, 0); + switch (State) + { + case EepromState.Ready: + if (bit) + { + // Console.WriteLine("[EEPROM] Request started"); + State = EepromState.StartRequest; + } + break; + case EepromState.StartRequest: + BitsRemaining = Size == EepromSize.Eeprom64k ? 14U : 6U; + if (bit) + { + // Console.WriteLine("[EEPROM] Receiving read address"); + State = EepromState.ReceiveAddrForRead; + ReadAddr = 0; + } + else + { + // Console.WriteLine("[EEPROM] Receiving write address"); + State = EepromState.ReceiveAddrForWrite; + Addr = 0; + } + break; + case EepromState.ReceiveAddrForRead: + if (BitsRemaining > 0) + { + ReadAddr |= bit ? 1u : 0u; + ReadAddr <<= 1; + ReadAddr &= 0x3FF; + + BitsRemaining--; + // Console.WriteLine($"[EEPROM] Setting read address ({bit}), bits remaining: {BitsRemaining}"); + + if (BitsRemaining == 0) + { + // Console.WriteLine("[EEPROM] Read address written: " + Util.Hex(ReadAddr, 4)); + State = EepromState.ReceiveTerminatingZero; + BitsRemaining = 68; + ReadBitsRemaining = 68; + } + } + break; + case EepromState.ReceiveAddrForWrite: + if (BitsRemaining > 0) + { + Addr |= bit ? 1u : 0u; + Addr <<= 1; + Addr &= 0x3FF; + + BitsRemaining--; + // Console.WriteLine($"[EEPROM] Setting write address ({bit}), bits remaining: {BitsRemaining}"); + + if (BitsRemaining == 0) + { + BitsRemaining = 64; + State = EepromState.ReceiveDataForWrite; + // Console.WriteLine("[EEPROM] Write address set: " + Util.Hex(Addr, 4)); + } + } + break; + case EepromState.ReceiveDataForWrite: + if (BitsRemaining > 0) + { + WriteBitEEPROM(bit); + // Console.WriteLine($"[EEPROM] Write (addr: {Util.Hex(Addr, 4)}) {Convert.ToByte(bit)}, bits remaining: " + BitsRemaining); + Addr++; + BitsRemaining--; + + if (BitsRemaining == 0) + { + // Console.WriteLine($"[EEPROM] Write finished"); + State = EepromState.Ready; + } + } + break; + case EepromState.ReceiveTerminatingZero: + State = EepromState.Ready; + // Console.WriteLine($"[EEPROM] Received terminating zero"); + break; + } + } + + public override byte[] GetSave() + { + return EEPROM; + } + + public override void LoadSave(byte[] save) + { + for (uint i = 0; i < save.Length && i < EEPROM.Length; i++) + { + EEPROM[i] = save[i]; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/saving/Eeprom.cs.meta b/Assets/emulator/saving/Eeprom.cs.meta new file mode 100644 index 0000000..af7c651 --- /dev/null +++ b/Assets/emulator/saving/Eeprom.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 02514cda0b41acb43b39679713973344 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/saving/Flash.cs b/Assets/emulator/saving/Flash.cs new file mode 100644 index 0000000..dbcd0cb --- /dev/null +++ b/Assets/emulator/saving/Flash.cs @@ -0,0 +1,223 @@ +using System; +using static Util; +namespace OptimeGBA +{ + public enum FlashState + { + InitialState, + PreCommand0, + PreCommand1, + + FullErase, + EraseSector, + PrepareWriteByte, + SetBank, + } + + public enum FlashStateSecondary + { + Ready, + PrepareEraseCommand + } + + public enum FlashSize + { + Flash512k, + Flash1m + } + + public unsafe sealed class Flash : SaveProvider + { + Gba Gba; + + FlashState State = FlashState.InitialState; + FlashStateSecondary StateSecondary = FlashStateSecondary.Ready; + + FlashSize Size; + + bool IdentificationMode = false; + bool Bank1 = false; + bool PrepareSetBank = false; + bool PrepareWrite = false; + + byte[] Memory; + + public Flash(Gba gba, FlashSize size) + { + Gba = gba; + Size = size; + + switch (size) + { + case FlashSize.Flash1m: + Memory = new byte[131072]; + break; + case FlashSize.Flash512k: + Memory = new byte[65536]; + break; + } + } + + public override byte Read8(uint addr) + { + byte val = 0; + + addr -= 0xE000000; + if (Bank1) addr += 0x10000; + if (addr < Memory.Length) + { + val = Memory[addr]; + } + + if (IdentificationMode) + { + // Return correct IDs in identification mode + if (Size == FlashSize.Flash1m) + { + switch (addr) + { + case 0x0: return 0x62; + case 0x1: return 0x13; + } + } + else + { + switch (addr) + { + case 0x0: return 0x32; + case 0x1: return 0x1B; + } + } + } + + // Console.WriteLine("Flash.Read8 addr:" + HexN(addr, 8) + " val:" + HexN(val, 2)); + // Gba.Arm7.Error("read"); + + return val; + } + + public override void Write8(uint addr, byte val) + { + if (PrepareSetBank && addr == 0xE000000) + { + if (Size == FlashSize.Flash1m) + { + Bank1 = (val & 1) != 0 ? true : false; + } + PrepareSetBank = false; + return; + } + + if (PrepareWrite) + { + addr -= 0xE000000; + if (Bank1) addr += 0x10000; + if (addr < Memory.Length) + { + // Writes can only clear bits + Memory[addr] &= val; + Dirty = true; + } + PrepareWrite = false; + return; + } + + switch (State) + { + case FlashState.InitialState: + if (addr == 0xE005555 && val == 0xAA) + { + // Console.WriteLine("pre-command 0 sent"); + State = FlashState.PreCommand0; + } + break; + case FlashState.PreCommand0: + if (addr == 0xE002AAA && val == 0x55) + { + // Console.WriteLine("pre-command 1 sent, ready to receive commands"); + State = FlashState.PreCommand1; + } + break; + case FlashState.PreCommand1: + switch (StateSecondary) + { + case FlashStateSecondary.Ready: + if (addr == 0xE005555) + { + switch (val) + { + case 0x90: + // Console.WriteLine("enter identification mode"); + IdentificationMode = true; + break; + case 0xF0: + // Console.WriteLine("exit identification mode"); + // Gba.Arm7.Error("here"); + IdentificationMode = false; + break; + case 0x80: + // Console.WriteLine("preparing to erase"); + StateSecondary = FlashStateSecondary.PrepareEraseCommand; + break; + case 0xB0: + // Console.WriteLine("preparing to set bank"); + PrepareSetBank = true; + break; + case 0xA0: + // Console.WriteLine("preparing to write"); + PrepareWrite = true; + break; + } + } + break; + case FlashStateSecondary.PrepareEraseCommand: + switch (val) + { + // Erase everything + case 0x10: + if (addr == 0xE005555) + { + for (uint i = 0; i < Memory.Length; i++) + { + Memory[i] = 0xFF; + } + Dirty = true; + } + StateSecondary = FlashStateSecondary.Ready; + break; + // Erase 4 KB + case 0x30: + uint page = addr & 0xF000; + if (Bank1) page += 0x10000; + for (uint i = 0; i < 0x1000; i++) + { + if (i < Memory.Length) + { + Memory[page + i] = 0xFF; + } + } + Dirty = true; + StateSecondary = FlashStateSecondary.Ready; + break; + } + break; + } + State = FlashState.InitialState; + break; + } + } + + public override byte[] GetSave() + { + return Memory; + } + + public override void LoadSave(byte[] save) + { + for (uint i = 0; i < save.Length && i < Memory.Length; i++) + { + Memory[i] = save[i]; + } + } + } +} \ No newline at end of file diff --git a/Assets/emulator/saving/Flash.cs.meta b/Assets/emulator/saving/Flash.cs.meta new file mode 100644 index 0000000..cd21824 --- /dev/null +++ b/Assets/emulator/saving/Flash.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1c16b6ed7731d8241808dee15c595b1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/saving/NullSaveProvider.cs b/Assets/emulator/saving/NullSaveProvider.cs new file mode 100644 index 0000000..268a853 --- /dev/null +++ b/Assets/emulator/saving/NullSaveProvider.cs @@ -0,0 +1,27 @@ +using System; +using static OptimeGBA.Bits; +namespace OptimeGBA +{ + public sealed class NullSaveProvider : SaveProvider + { + public override byte Read8(uint addr) + { + return 0; + } + + public override void Write8(uint addr, byte val) + { + + } + + public override byte[] GetSave() + { + return new byte[0]; + } + + public override void LoadSave(byte[] save) + { + + } + } +} \ No newline at end of file diff --git a/Assets/emulator/saving/NullSaveProvider.cs.meta b/Assets/emulator/saving/NullSaveProvider.cs.meta new file mode 100644 index 0000000..98ef998 --- /dev/null +++ b/Assets/emulator/saving/NullSaveProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cdf64d6c1dcbdd04086faacf9d434ae4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/saving/SaveProvider.cs b/Assets/emulator/saving/SaveProvider.cs new file mode 100644 index 0000000..a3cac78 --- /dev/null +++ b/Assets/emulator/saving/SaveProvider.cs @@ -0,0 +1,11 @@ +namespace OptimeGBA { + public abstract class SaveProvider { + public abstract byte Read8(uint addr); + public abstract void Write8(uint addr, byte val); + + public abstract byte[] GetSave(); + public abstract void LoadSave(byte[] save); + + public bool Dirty; + } +} \ No newline at end of file diff --git a/Assets/emulator/saving/SaveProvider.cs.meta b/Assets/emulator/saving/SaveProvider.cs.meta new file mode 100644 index 0000000..4bc13ac --- /dev/null +++ b/Assets/emulator/saving/SaveProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 330b8a9bc8cbda54888256ca7fdf4532 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/emulator/saving/Sram.cs b/Assets/emulator/saving/Sram.cs new file mode 100644 index 0000000..4d611ac --- /dev/null +++ b/Assets/emulator/saving/Sram.cs @@ -0,0 +1,39 @@ +using System; +using static OptimeGBA.Bits; +namespace OptimeGBA +{ + public sealed class Sram : SaveProvider + { + byte[] Memory = new byte[65536]; + + public override byte Read8(uint addr) + { + addr -= 0xE000000; + if (addr < Memory.Length) + { + return Memory[addr]; + } + return 0; + } + + public override void Write8(uint addr, byte val) + { + addr -= 0xE000000; + if (addr < Memory.Length) + { + Memory[addr] = val; + Dirty = true; + } + } + + public override byte[] GetSave() + { + return Memory; + } + + public override void LoadSave(byte[] save) + { + save.CopyTo(Memory, 0); + } + } +} \ No newline at end of file diff --git a/Assets/emulator/saving/Sram.cs.meta b/Assets/emulator/saving/Sram.cs.meta new file mode 100644 index 0000000..373d4fc --- /dev/null +++ b/Assets/emulator/saving/Sram.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9eaade4d84ac1dc40a7f22e635f80b39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: