From 9f7c58a5cc947e5f7c93fa35aadb87cc5cbd9e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Mon, 2 Feb 2026 01:22:59 -0600 Subject: [PATCH 1/5] feat(cpu): add Rsbs instruction implementation Implement Rsbs (reverse subtract with flags) handler for ARM Thumb in ArithmeticOps.cs. Decode rn and rd from the 3-bit opcode fields, read rn, perform subtraction of rn from immediate 0 using existing SubWithFlags helper, and write result back to rd while updating CPU flags. Mark method aggressive-inline for performance. This enables correct execution of Rsbs opcodes on the RP2040 Cortex-M0+ emulation core and fixes missing behavior for instructions that rely on reverse subtract semantics. --- src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index 56aad08..810bcf5 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -192,6 +192,18 @@ public static void Muls (ushort opcode, CortexM0Plus cpu) cpu.Registers.Z = (ptrRdm == 0); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Rsbs(ushort opcode, CortexM0Plus cpu) + { + var rn = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + + ptrRd = SubWithFlags(cpu, 0, valRn); + } + [MethodImpl (MethodImplOptions.AggressiveInlining)] public static void SubsImmediate3 (ushort opcode, CortexM0Plus cpu) { From 0668325024d24fd1d80a68cf110614d3406285e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Mon, 2 Feb 2026 01:39:18 -0600 Subject: [PATCH 2/5] feat: add dotnet tool config and tidy decoder class Add a dotnet-tools.json to pin csharpier 1.2.5 for consistent formatting across the repo. Refactor InstructionDecoder declaration to "public sealed unsafe and remove unused singleton and complex initialization scaffolding. This change simplifies the class header ordering and ensures proper unsafe modifier placement without altering decoder logic. Also reformat surrounding code to match the project's style expectations. --- dotnet-tools.json | 13 + src/RP2040.Core/Cpu/CortexM0Plus.cs | 498 ++--- src/RP2040.Core/Cpu/InstructionDecoder.cs | 469 +++-- src/RP2040.Core/Cpu/Instructions/BitOps.cs | 531 +++--- src/RP2040.Core/Cpu/Instructions/FlowOps.cs | 343 ++-- src/RP2040.Core/Cpu/Instructions/MemoryOps.cs | 385 ++-- src/RP2040.Core/Cpu/Instructions/SystemOps.cs | 286 +-- src/RP2040.Core/Cpu/Registers.cs | 131 +- src/RP2040.Core/Helpers/InstructionEmiter.cs | 647 ++++--- src/RP2040.Core/Memory/BusInterconnect.cs | 339 ++-- src/RP2040.Core/Memory/IMemoryBus.cs | 18 +- src/RP2040.Core/Memory/IMemoryMappedDevice.cs | 14 +- src/RP2040.Core/Memory/Ram.cs | 100 +- src/RP2040.Core/RP2040.Core.csproj | 24 +- .../RP2040.Peripherals.csproj | 12 +- .../Cpu/InstructionDecoderTests.cs | 277 +-- .../Cpu/Instructions/ArithmeticOpsTests.cs | 1657 +++++++++-------- .../Cpu/Instructions/BitOpsTests.cs | 1099 +++++------ .../Cpu/Instructions/FlowOpsTests.cs | 305 +-- .../Cpu/Instructions/MemoryOpsTests.cs | 528 +++--- .../Cpu/Instructions/SystemOpTests.cs | 713 +++---- .../RP2040.Core.Tests/Fixtures/CpuTestBase.cs | 72 +- .../RP2040.Core.Tests.csproj | 46 +- 23 files changed, 4373 insertions(+), 4134 deletions(-) create mode 100644 dotnet-tools.json diff --git a/dotnet-tools.json b/dotnet-tools.json new file mode 100644 index 0000000..cdb3c8d --- /dev/null +++ b/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "csharpier": { + "version": "1.2.5", + "commands": [ + "csharpier" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/src/RP2040.Core/Cpu/CortexM0Plus.cs b/src/RP2040.Core/Cpu/CortexM0Plus.cs index 722e1bd..4bfbcc4 100644 --- a/src/RP2040.Core/Cpu/CortexM0Plus.cs +++ b/src/RP2040.Core/Cpu/CortexM0Plus.cs @@ -2,249 +2,265 @@ using RP2040.Core.Memory; [module: SkipLocalsInit] + namespace RP2040.Core.Cpu; public unsafe class CortexM0Plus { - - public readonly BusInterconnect Bus; - public Registers Registers; - public long Cycles; - - private readonly InstructionDecoder _decoder; - - // CACHÉ DE FETCH LOCAL - // Apunta directamente al buffer interno de RandomAccessMemory - private byte* _fetchPtr; - private uint _fetchMask; - private uint _currentRegionId; - - private const uint EXC_RETURN_HANDLER = 0xFFFFFFF1; // Return to Handler mode, using MSP - private const uint EXC_RETURN_THREAD_MSP = 0xFFFFFFF9; // Return to Thread mode, using MSP - private const uint EXC_RETURN_THREAD_PSP = 0xFFFFFFFD; // Return to Thread mode, using PSP - - public CortexM0Plus (BusInterconnect bus) - { - Bus = bus; - _decoder = InstructionDecoder.Instance; - Reset (); - } - - public void Reset () - { - Registers.SP = Bus.ReadWord (0x00000000); - Registers.PC = Bus.ReadWord (0x00000004); - - // Inicializar caché - UpdateFetchCache (Registers.PC); - - Registers.N = false; - Registers.Z = false; - Registers.C = false; - Registers.V = false; - - Cycles = 0; - } - - /// - /// Actualiza los punteros de caché cuando el PC salta a una región de memoria diferente. - /// - [MethodImpl (MethodImplOptions.AggressiveInlining)] - private void UpdateFetchCache (uint pc) - { - _currentRegionId = pc >> 28; - - switch (_currentRegionId) { - case BusInterconnect.REGION_FLASH: - _fetchPtr = Bus.PtrFlash; - _fetchMask = BusInterconnect.MASK_FLASH & ~1u; - break; - case BusInterconnect.REGION_SRAM: - _fetchPtr = Bus.PtrSram; - _fetchMask = BusInterconnect.MASK_SRAM & ~1u; - break; - case BusInterconnect.REGION_BOOTROM: - _fetchPtr = Bus.PtrBootRom; - _fetchMask = BusInterconnect.MASK_BOOTROM & ~1u; - break; - default: - _fetchPtr = null; // Detiene el fast-fetch - break; - } - } - - [MethodImpl (MethodImplOptions.AggressiveOptimization)] - public void Run (int instructions) - { - var decoder = _decoder; - - // 1. Cargar caché a registros del CPU Host (Stack) - // Esto elimina la indirección 'this._fetchPtr' dentro del while - var fetchPtr = _fetchPtr; - var fetchMask = _fetchMask; - var regionId = _currentRegionId; - - while (instructions-- > 0) { - var pc = Registers.PC; - - // 2. FAST GUARD: ¿Seguimos en la misma región de memoria? - // (pc >> 28) es una operación de un solo ciclo. - if ((pc >> 28) != regionId) { - // FALLBACK: Cambio de región detectado. - UpdateFetchCache (pc); - - // Recargar caché local - fetchPtr = _fetchPtr; - fetchMask = _fetchMask; - regionId = _currentRegionId; - - // Si saltamos a memoria no ejecutable, abortamos el batch - if (fetchPtr == null) break; - } - - // 3. FETCH ULTRA-RÁPIDO - // (pc & ~1u): Alineación obligatoria en ARM Thumb (elimina bit 0). - // & fetchMask: Mantiene el acceso dentro del buffer Pinned (seguridad). - var opcode = Unsafe.ReadUnaligned (fetchPtr + (pc & fetchMask)); - - // 4. PRE-UPDATE PC (Speculative) - Registers.PC = pc + 2; - - Cycles++; - - // 5. DISPATCH - decoder.Dispatch (opcode, this); - } - - _currentRegionId = regionId; - _fetchPtr = fetchPtr; - _fetchMask = fetchMask; - } - - // Step simple para Debugging - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void Step () - { - var pc = Registers.PC; - var opcode = Bus.ReadHalfWord (pc); - Registers.PC = pc + 2; - Cycles++; - _decoder.Dispatch (opcode, this); - } - - [MethodImpl (MethodImplOptions.NoInlining)] // NoInlining (it is not used commonly) - public void UpdateStackPointerSource () - { - if (Registers.IPSR != 0) return; - - var switchToPsp = (Registers.CONTROL & 2) != 0; - - if (switchToPsp) { - Registers.MSP_Storage = Registers.SP; - Registers.SP = Registers.PSP_Storage; - } - else { - Registers.PSP_Storage = Registers.SP; - Registers.SP = Registers.MSP_Storage; - } - } - - [MethodImpl (MethodImplOptions.NoInlining)] - public void ExceptionEntry (uint exceptionNumber) - { - var framePtr = Registers.SP; - - var needsAlign = (framePtr & 4) != 0; - var framePtrAlign = needsAlign ? 1u : 0u; - - var stackAdjust = 0x20u + (needsAlign ? 4u : 0u); - var finalSp = framePtr - stackAdjust; - - var frameBase = finalSp; - - Bus.WriteWord (frameBase + 0x00, Registers.R0); - Bus.WriteWord (frameBase + 0x04, Registers.R1); - Bus.WriteWord (frameBase + 0x08, Registers.R2); - Bus.WriteWord (frameBase + 0x0C, Registers.R3); - Bus.WriteWord (frameBase + 0x10, Registers.R12); - Bus.WriteWord (frameBase + 0x14, Registers.LR); - Bus.WriteWord (frameBase + 0x18, Registers.PC & 0xFFFFFFFE); // Return Address - - var xpsr = Registers.GetxPsr () | (framePtrAlign << 9); - Bus.WriteWord (frameBase + 0x1C, xpsr); - - if (Registers.IPSR > 0) { - Registers.LR = EXC_RETURN_HANDLER; - } - else { - Registers.LR = (Registers.CONTROL & 2) != 0 ? EXC_RETURN_THREAD_PSP : EXC_RETURN_THREAD_MSP; - } - - if ((Registers.CONTROL & 2) != 0) { - Registers.PSP_Storage = finalSp; - Registers.SP = Registers.MSP_Storage; - } - else { - Registers.SP = finalSp; - } - - Registers.IPSR = exceptionNumber; - Registers.CONTROL &= ~2u; - - uint vtor = 0; // TODO: Read from Registers.VTOR or PPB - var vectorAddress = vtor + (exceptionNumber * 4); - - var targetPc = Bus.ReadWord (vectorAddress); - Registers.PC = targetPc & 0xFFFFFFFE; - - Cycles += 12; // Exception Entry cost (aprox 12-15 ciclos) - } - - [MethodImpl (MethodImplOptions.NoInlining)] - public void ExceptionReturn (uint excReturn) - { - var returnToThread = (excReturn & 8) != 0; - var usePsp = (excReturn & 4) != 0; - - if (!returnToThread && usePsp) { - usePsp = false; - } - - if (returnToThread) { - Registers.IPSR = 0; - - if (usePsp) { - Registers.MSP_Storage = Registers.SP; - Registers.SP = Registers.PSP_Storage; - Registers.CONTROL |= 2; - } - else { - Registers.CONTROL &= ~2u; - } - } - - var framePtr = Registers.SP; - - Registers.R0 = Bus.ReadWord (framePtr + 0x00); - Registers.R1 = Bus.ReadWord (framePtr + 0x04); - Registers.R2 = Bus.ReadWord (framePtr + 0x08); - Registers.R3 = Bus.ReadWord (framePtr + 0x0C); - Registers.R12 = Bus.ReadWord (framePtr + 0x10); - Registers.LR = Bus.ReadWord (framePtr + 0x14); - var retPC = Bus.ReadWord (framePtr + 0x18); - var xpsr = Bus.ReadWord (framePtr + 0x1C); - - Registers.N = (xpsr & 0x80000000) != 0; - Registers.Z = (xpsr & 0x40000000) != 0; - Registers.C = (xpsr & 0x20000000) != 0; - Registers.V = (xpsr & 0x10000000) != 0; - - var alignAdjust = (xpsr & (1 << 9)) != 0; - var stackFree = 0x20u + (alignAdjust ? 4u : 0u); - - Registers.SP += stackFree; - Registers.PC = retPC & 0xFFFFFFFE; - - Cycles += 10; - } + public readonly BusInterconnect Bus; + public Registers Registers; + public long Cycles; + + private readonly InstructionDecoder _decoder; + + // CACHÉ DE FETCH LOCAL + // Apunta directamente al buffer interno de RandomAccessMemory + private byte* _fetchPtr; + private uint _fetchMask; + private uint _currentRegionId; + + private const uint EXC_RETURN_HANDLER = 0xFFFFFFF1; // Return to Handler mode, using MSP + private const uint EXC_RETURN_THREAD_MSP = 0xFFFFFFF9; // Return to Thread mode, using MSP + private const uint EXC_RETURN_THREAD_PSP = 0xFFFFFFFD; // Return to Thread mode, using PSP + + public CortexM0Plus(BusInterconnect bus) + { + Bus = bus; + _decoder = InstructionDecoder.Instance; + Reset(); + } + + public void Reset() + { + Registers.SP = Bus.ReadWord(0x00000000); + Registers.PC = Bus.ReadWord(0x00000004); + + // Inicializar caché + UpdateFetchCache(Registers.PC); + + Registers.N = false; + Registers.Z = false; + Registers.C = false; + Registers.V = false; + + Cycles = 0; + } + + /// + /// Actualiza los punteros de caché cuando el PC salta a una región de memoria diferente. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateFetchCache(uint pc) + { + _currentRegionId = pc >> 28; + + switch (_currentRegionId) + { + case BusInterconnect.REGION_FLASH: + _fetchPtr = Bus.PtrFlash; + _fetchMask = BusInterconnect.MASK_FLASH & ~1u; + break; + case BusInterconnect.REGION_SRAM: + _fetchPtr = Bus.PtrSram; + _fetchMask = BusInterconnect.MASK_SRAM & ~1u; + break; + case BusInterconnect.REGION_BOOTROM: + _fetchPtr = Bus.PtrBootRom; + _fetchMask = BusInterconnect.MASK_BOOTROM & ~1u; + break; + default: + _fetchPtr = null; // Detiene el fast-fetch + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public void Run(int instructions) + { + var decoder = _decoder; + + // 1. Cargar caché a registros del CPU Host (Stack) + // Esto elimina la indirección 'this._fetchPtr' dentro del while + var fetchPtr = _fetchPtr; + var fetchMask = _fetchMask; + var regionId = _currentRegionId; + + while (instructions-- > 0) + { + var pc = Registers.PC; + + // 2. FAST GUARD: ¿Seguimos en la misma región de memoria? + // (pc >> 28) es una operación de un solo ciclo. + if ((pc >> 28) != regionId) + { + // FALLBACK: Cambio de región detectado. + UpdateFetchCache(pc); + + // Recargar caché local + fetchPtr = _fetchPtr; + fetchMask = _fetchMask; + regionId = _currentRegionId; + + // Si saltamos a memoria no ejecutable, abortamos el batch + if (fetchPtr == null) + break; + } + + // 3. FETCH ULTRA-RÁPIDO + // (pc & ~1u): Alineación obligatoria en ARM Thumb (elimina bit 0). + // & fetchMask: Mantiene el acceso dentro del buffer Pinned (seguridad). + var opcode = Unsafe.ReadUnaligned(fetchPtr + (pc & fetchMask)); + + // 4. PRE-UPDATE PC (Speculative) + Registers.PC = pc + 2; + + Cycles++; + + // 5. DISPATCH + decoder.Dispatch(opcode, this); + } + + _currentRegionId = regionId; + _fetchPtr = fetchPtr; + _fetchMask = fetchMask; + } + + // Step simple para Debugging + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Step() + { + var pc = Registers.PC; + var opcode = Bus.ReadHalfWord(pc); + Registers.PC = pc + 2; + Cycles++; + _decoder.Dispatch(opcode, this); + } + + [MethodImpl(MethodImplOptions.NoInlining)] // NoInlining (it is not used commonly) + public void UpdateStackPointerSource() + { + if (Registers.IPSR != 0) + return; + + var switchToPsp = (Registers.CONTROL & 2) != 0; + + if (switchToPsp) + { + Registers.MSP_Storage = Registers.SP; + Registers.SP = Registers.PSP_Storage; + } + else + { + Registers.PSP_Storage = Registers.SP; + Registers.SP = Registers.MSP_Storage; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void ExceptionEntry(uint exceptionNumber) + { + var framePtr = Registers.SP; + + var needsAlign = (framePtr & 4) != 0; + var framePtrAlign = needsAlign ? 1u : 0u; + + var stackAdjust = 0x20u + (needsAlign ? 4u : 0u); + var finalSp = framePtr - stackAdjust; + + var frameBase = finalSp; + + Bus.WriteWord(frameBase + 0x00, Registers.R0); + Bus.WriteWord(frameBase + 0x04, Registers.R1); + Bus.WriteWord(frameBase + 0x08, Registers.R2); + Bus.WriteWord(frameBase + 0x0C, Registers.R3); + Bus.WriteWord(frameBase + 0x10, Registers.R12); + Bus.WriteWord(frameBase + 0x14, Registers.LR); + Bus.WriteWord(frameBase + 0x18, Registers.PC & 0xFFFFFFFE); // Return Address + + var xpsr = Registers.GetxPsr() | (framePtrAlign << 9); + Bus.WriteWord(frameBase + 0x1C, xpsr); + + if (Registers.IPSR > 0) + { + Registers.LR = EXC_RETURN_HANDLER; + } + else + { + Registers.LR = + (Registers.CONTROL & 2) != 0 ? EXC_RETURN_THREAD_PSP : EXC_RETURN_THREAD_MSP; + } + + if ((Registers.CONTROL & 2) != 0) + { + Registers.PSP_Storage = finalSp; + Registers.SP = Registers.MSP_Storage; + } + else + { + Registers.SP = finalSp; + } + + Registers.IPSR = exceptionNumber; + Registers.CONTROL &= ~2u; + + uint vtor = 0; // TODO: Read from Registers.VTOR or PPB + var vectorAddress = vtor + (exceptionNumber * 4); + + var targetPc = Bus.ReadWord(vectorAddress); + Registers.PC = targetPc & 0xFFFFFFFE; + + Cycles += 12; // Exception Entry cost (aprox 12-15 ciclos) + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void ExceptionReturn(uint excReturn) + { + var returnToThread = (excReturn & 8) != 0; + var usePsp = (excReturn & 4) != 0; + + if (!returnToThread && usePsp) + { + usePsp = false; + } + + if (returnToThread) + { + Registers.IPSR = 0; + + if (usePsp) + { + Registers.MSP_Storage = Registers.SP; + Registers.SP = Registers.PSP_Storage; + Registers.CONTROL |= 2; + } + else + { + Registers.CONTROL &= ~2u; + } + } + + var framePtr = Registers.SP; + + Registers.R0 = Bus.ReadWord(framePtr + 0x00); + Registers.R1 = Bus.ReadWord(framePtr + 0x04); + Registers.R2 = Bus.ReadWord(framePtr + 0x08); + Registers.R3 = Bus.ReadWord(framePtr + 0x0C); + Registers.R12 = Bus.ReadWord(framePtr + 0x10); + Registers.LR = Bus.ReadWord(framePtr + 0x14); + var retPC = Bus.ReadWord(framePtr + 0x18); + var xpsr = Bus.ReadWord(framePtr + 0x1C); + + Registers.N = (xpsr & 0x80000000) != 0; + Registers.Z = (xpsr & 0x40000000) != 0; + Registers.C = (xpsr & 0x20000000) != 0; + Registers.V = (xpsr & 0x10000000) != 0; + + var alignAdjust = (xpsr & (1 << 9)) != 0; + var stackFree = 0x20u + (alignAdjust ? 4u : 0u); + + Registers.SP += stackFree; + Registers.PC = retPC & 0xFFFFFFFE; + + Cycles += 10; + } } diff --git a/src/RP2040.Core/Cpu/InstructionDecoder.cs b/src/RP2040.Core/Cpu/InstructionDecoder.cs index ae2c74b..59ec893 100644 --- a/src/RP2040.Core/Cpu/InstructionDecoder.cs +++ b/src/RP2040.Core/Cpu/InstructionDecoder.cs @@ -6,242 +6,237 @@ namespace RP2040.Core.Cpu; -public unsafe sealed class InstructionDecoder : IDisposable +public sealed unsafe class InstructionDecoder : IDisposable { - public static InstructionDecoder Instance { get; } = new InstructionDecoder (); - - private readonly InstructionHandler[] _lookupTable = new InstructionHandler[65536]; - private GCHandle _pinnedHandle; - private readonly InstructionHandler* _fastTablePtr; - - bool _disposed; - - private readonly struct OpcodeRule (ushort mask, ushort pattern, InstructionHandler handler) - { - public readonly ushort Mask = mask; - public readonly ushort Pattern = pattern; - public readonly InstructionHandler Handler = handler; - } - - public InstructionDecoder () - { - _pinnedHandle = GCHandle.Alloc (_lookupTable, GCHandleType.Pinned); - _fastTablePtr = (InstructionHandler*)_pinnedHandle.AddrOfPinnedObject (); - - InstructionHandler undefinedPtr = &HandleUndefined; - fixed (InstructionHandler* ptrToArr = _lookupTable) { - new Span (ptrToArr, _lookupTable.Length).Fill ((nuint)undefinedPtr); - } - - ReadOnlySpan rules = [ - // ================================================================ - // GROUP 1: Mask 0xFFFF (Exact Match - Max Priority) - // ================================================================ - // MRS Rd, spec_reg (F3EF) - new OpcodeRule (0xFFFF, 0xF3EF, &SystemOps.Mrs), - // DMB, DSB, ISB (F3BF) - // Mask: 1111 1111 1111 1111 - new OpcodeRule (0xFFFF, 0xF3BF, &SystemOps.Barrier), - - // ================================================================ - // GROUP 2: Mask 0xFFF0 - // ================================================================ - // MSR spec_reg, Rn (F38x) - new OpcodeRule (0xFFF0, 0xF380, &SystemOps.Msr), - - // ================================================================ - // GROUP 3: Mask 0xFFC0 (10 bits significant) - // IMPORTANT: Must come before 0xFF00 to prevent generic instructions - // (like ORRS or ADD generic) from shadowing these specific opcodes. - // ================================================================ - // ADCS (Rd, Rm) - new OpcodeRule (0xFFC0, 0x4140, &ArithmeticOps.Adcs), - // ANDS (Rn, Rm) - new OpcodeRule (0xFFC0, 0x4000, &BitOps.Ands), - // ASRS (Register) - Encoding T2 - new OpcodeRule (0xFFC0, 0x4100, &BitOps.AsrsRegister), - // BICS (Rdn, Rm) - new OpcodeRule (0xFFC0, 0x4380, &BitOps.Bics), - // CMN (Rn, Rm) - new OpcodeRule (0xFFC0, 0x42C0, &ArithmeticOps.Cmn), - // CMP Rn, Rm (Low Registers - Encoding T1) - new OpcodeRule (0xFFC0, 0x4280, &ArithmeticOps.CmpRegister), - // EORS Rdn, Rm - new OpcodeRule (0xFFC0, 0x4040, &BitOps.Eors), - // MULS Rn, Rdm - (Must be before ORRS 0x4300) - new OpcodeRule (0xFFC0, 0x4340, &ArithmeticOps.Muls), - // MVNS Rd, Rm - new OpcodeRule (0xFFC0, 0x43C0, &BitOps.Mvns), - // LSLS Rd, Rm, #0 - new OpcodeRule (0xFFC0, 0x0000, &BitOps.LslsZero), - // LSLS (Register) - Encoding T2 - new OpcodeRule (0xFFC0, 0x4080, &BitOps.LslsRegister), - // Rev16 Rd, Rn - new OpcodeRule (0xFFC0, 0xBA40, &BitOps.Rev16), - // REVSH Rd, Rm - new OpcodeRule (0xFFC0, 0xBAC0, &BitOps.Revsh), - // REV (Rd, Rn) - new OpcodeRule (0xFFC0, 0xBA00, &BitOps.Rev), - - // ================================================================ - // GROUP 4: Mask 0xFF87 (High Register Special Cases) - // CRITICAL: These are specific cases of the 0xFF00 generic group. - // They verify Bit 7 (DN) and Bits 0-2 (Rd/Rm). - // ================================================================ - // 1. High Priority: ADD PC, Rm (R15) - new OpcodeRule (0xFF87, 0x4487, &ArithmeticOps.AddHighToPc), - // 2. High Priority: ADD SP, Rm (R13) - new OpcodeRule (0xFF87, 0x4485, &ArithmeticOps.AddHighToSp), - // BLX Rm - new OpcodeRule (0xFF87, 0x4780, &FlowOps.Blx), - // BX Rm - new OpcodeRule (0xFF87, 0x4700, &FlowOps.Bx), - // 1. MOV PC, Rm (High Priority) - new OpcodeRule (0xFF87, 0x4687, &BitOps.MovToPc), - // 2. MOV SP, Rm (High Priority) - new OpcodeRule (0xFF87, 0x4685, &BitOps.MovToSp), - - // ================================================================ - // GROUP 5: Mask 0xFF80 - // ================================================================ - // ADD (SP + imm7) - new OpcodeRule (0xFF80, 0xB000, &ArithmeticOps.AddSpImmediate7), - // Sub (SP - imm) - new OpcodeRule (0xFF80, 0xB080, &ArithmeticOps.SubSp), - - // ================================================================ - // GROUP 6: Mask 0xFF00 (8 bits significant - Broad Categories) - // ================================================================ - // 3. Low Priority: ADD Generic (R0-R12, R14) - new OpcodeRule (0xFF00, 0x4400, &ArithmeticOps.AddHighToReg), - // CMP Rn, Rm (High Registers - Encoding T2) - new OpcodeRule (0xFF00, 0x4500, &ArithmeticOps.CmpHighRegister), - // 3. MOV Rd, Rm (Generic Fallback) - new OpcodeRule (0xFF00, 0x4600, &BitOps.MovRegister), - // MULS (0x4340) and MVNS (0x43C0) are subsets and were handled above - // ORRS (Rd, Rm) - NOTE: This covers 0x4300-0x43FF. - new OpcodeRule (0xFF00, 0x4300, &BitOps.Orrs), - - // Stack Operations - new OpcodeRule (0xFF00, 0xBC00, &MemoryOps.Pop), - new OpcodeRule (0xFF00, 0xBD00, &MemoryOps.PopPc), - new OpcodeRule (0xFF00, 0xB400, &MemoryOps.Push), - new OpcodeRule (0xFF00, 0xB500, &MemoryOps.PushLr), - - // Conditional Branches (T1) - // SVC (0xDF00) is technically caught here if not handled separately. - // Ensure the handler filters 0xF (SVC) or add a specific SVC rule with higher priority. - new OpcodeRule (0xFF00, 0xD000, &FlowOps.Beq), - new OpcodeRule (0xFF00, 0xD100, &FlowOps.Bne), - new OpcodeRule (0xFF00, 0xD200, &FlowOps.Bcs), - new OpcodeRule (0xFF00, 0xD300, &FlowOps.Bcc), - new OpcodeRule (0xFF00, 0xD400, &FlowOps.Bmi), - new OpcodeRule (0xFF00, 0xD500, &FlowOps.Bpl), - new OpcodeRule (0xFF00, 0xD600, &FlowOps.Bvs), - new OpcodeRule (0xFF00, 0xD700, &FlowOps.Bvc), - new OpcodeRule (0xFF00, 0xD800, &FlowOps.Bhi), - new OpcodeRule (0xFF00, 0xD900, &FlowOps.Bls), - new OpcodeRule (0xFF00, 0xDA00, &FlowOps.Bge), - new OpcodeRule (0xFF00, 0xDB00, &FlowOps.Blt), - new OpcodeRule (0xFF00, 0xDC00, &FlowOps.Bgt), - new OpcodeRule (0xFF00, 0xDD00, &FlowOps.Ble), - - // ================================================================ - // GROUP 7: Mask 0xFE00 - // ================================================================ - // ADDS (Rd, Rn, Rm) - Encoding T1 Register - new OpcodeRule (0xFE00, 0x1800, &ArithmeticOps.AddsRegister), - // ADDS (Rd, Rn, imm3) - new OpcodeRule (0xFE00, 0x1C00, &ArithmeticOps.AddsImmediate3), - // SUBS (imm3) - new OpcodeRule (0xFE00, 0x1E00, &ArithmeticOps.SubsImmediate3), - // SUBS (Register) - new OpcodeRule (0xFE00, 0x1A00, &ArithmeticOps.SubsRegister), - - // ================================================================ - // GROUP 8: Mask 0xF800 (5 bits significant - Most Generic) - // ================================================================ - // ADD (Rd = SP + imm8) - new OpcodeRule (0xF800, 0xA800, &ArithmeticOps.AddSpImmediate8), - // ADDS (Rd, imm8) - new OpcodeRule (0xF800, 0x3000, &ArithmeticOps.AddsImmediate8), - // SUBS (imm8) - new OpcodeRule (0xF800, 0x3800, &ArithmeticOps.SubsImmediate8), - // ADR (Rd, imm8) - new OpcodeRule (0xF800, 0xA000, &ArithmeticOps.Adr), - // ASRS (Rd, Rm, imm5) - new OpcodeRule (0xF800, 0x1000, &BitOps.AsrsImm5), - // BL (Branch with Link) - new OpcodeRule (0xF800, 0xF000, &FlowOps.Bl), - // B (Unconditional) - T2 - new OpcodeRule (0xF800, 0xE000, &FlowOps.Branch), - // CMP Rn, #imm8 - new OpcodeRule (0xF800, 0x2800, &ArithmeticOps.CmpImmediate), - // MOVS (Rd, #imm8) - new OpcodeRule (0xF800, 0x2000, &BitOps.Movs), - // LDMIA (Load Multiple Increment After) - new OpcodeRule (0xF800, 0xC800, &MemoryOps.Ldmia), - // LSLS (Rd, Rm, imm5) - new OpcodeRule (0xF800, 0x0000, &BitOps.LslsImm5), - - // ================================================================ - // GROUP 9: Mask 0xBF00 - // ================================================================ - // NOP (Hint) - new OpcodeRule (0xBF00, 0xBF00, &SystemOps.Nop), - ]; - - for (var i = 0; i < 65536; i++) { - var opcode = (ushort)i; - foreach (ref readonly var rule in rules) { - if ((opcode & rule.Mask) != rule.Pattern) - continue; - _fastTablePtr[i] = rule.Handler; - break; - } - } - } - - [ExcludeFromCodeCoverage] - ~InstructionDecoder() - { - Dispose(false); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void Dispatch (ushort opcode, CortexM0Plus cpu) - { - _fastTablePtr[opcode] (opcode, cpu); - } - - public nuint GetHandler (ushort opcode) - { - return (nuint)_fastTablePtr[opcode]; - } - - private static void HandleUndefined (ushort opcode, CortexM0Plus cpu) - { - throw new Exception ($"Undefined Opcode: 0x{opcode:X4} PC={cpu.Registers.PC:X8}"); - } - - [ExcludeFromCodeCoverage] - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - [ExcludeFromCodeCoverage] - private void Dispose(bool disposing) - { - _ = disposing; - if (_disposed) return; - - if (_pinnedHandle.IsAllocated) - { - _pinnedHandle.Free(); - } - - _disposed = true; - } + public static InstructionDecoder Instance { get; } = new InstructionDecoder(); + + private readonly InstructionHandler[] _lookupTable = new InstructionHandler[65536]; + private GCHandle _pinnedHandle; + private readonly InstructionHandler* _fastTablePtr; + + bool _disposed; + + private readonly struct OpcodeRule(ushort mask, ushort pattern, InstructionHandler handler) + { + public readonly ushort Mask = mask; + public readonly ushort Pattern = pattern; + public readonly InstructionHandler Handler = handler; + } + + public InstructionDecoder() + { + _pinnedHandle = GCHandle.Alloc(_lookupTable, GCHandleType.Pinned); + _fastTablePtr = (InstructionHandler*)_pinnedHandle.AddrOfPinnedObject(); + + InstructionHandler undefinedPtr = &HandleUndefined; + fixed (InstructionHandler* ptrToArr = _lookupTable) + { + new Span(ptrToArr, _lookupTable.Length).Fill((nuint)undefinedPtr); + } + + ReadOnlySpan rules = + [ + // ================================================================ + // GROUP 1: Mask 0xFFFF (Exact Match - Max Priority) + // ================================================================ + // MRS Rd, spec_reg (F3EF) + new OpcodeRule(0xFFFF, 0xF3EF, &SystemOps.Mrs), + // DMB, DSB, ISB (F3BF) + // Mask: 1111 1111 1111 1111 + new OpcodeRule(0xFFFF, 0xF3BF, &SystemOps.Barrier), + // ================================================================ + // GROUP 2: Mask 0xFFF0 + // ================================================================ + // MSR spec_reg, Rn (F38x) + new OpcodeRule(0xFFF0, 0xF380, &SystemOps.Msr), + // ================================================================ + // GROUP 3: Mask 0xFFC0 (10 bits significant) + // IMPORTANT: Must come before 0xFF00 to prevent generic instructions + // (like ORRS or ADD generic) from shadowing these specific opcodes. + // ================================================================ + // ADCS (Rd, Rm) + new OpcodeRule(0xFFC0, 0x4140, &ArithmeticOps.Adcs), + // ANDS (Rn, Rm) + new OpcodeRule(0xFFC0, 0x4000, &BitOps.Ands), + // ASRS (Register) - Encoding T2 + new OpcodeRule(0xFFC0, 0x4100, &BitOps.AsrsRegister), + // BICS (Rdn, Rm) + new OpcodeRule(0xFFC0, 0x4380, &BitOps.Bics), + // CMN (Rn, Rm) + new OpcodeRule(0xFFC0, 0x42C0, &ArithmeticOps.Cmn), + // CMP Rn, Rm (Low Registers - Encoding T1) + new OpcodeRule(0xFFC0, 0x4280, &ArithmeticOps.CmpRegister), + // EORS Rdn, Rm + new OpcodeRule(0xFFC0, 0x4040, &BitOps.Eors), + // MULS Rn, Rdm - (Must be before ORRS 0x4300) + new OpcodeRule(0xFFC0, 0x4340, &ArithmeticOps.Muls), + // MVNS Rd, Rm + new OpcodeRule(0xFFC0, 0x43C0, &BitOps.Mvns), + // LSLS Rd, Rm, #0 + new OpcodeRule(0xFFC0, 0x0000, &BitOps.LslsZero), + // LSLS (Register) - Encoding T2 + new OpcodeRule(0xFFC0, 0x4080, &BitOps.LslsRegister), + // Rev16 Rd, Rn + new OpcodeRule(0xFFC0, 0xBA40, &BitOps.Rev16), + // REVSH Rd, Rm + new OpcodeRule(0xFFC0, 0xBAC0, &BitOps.Revsh), + // REV (Rd, Rn) + new OpcodeRule(0xFFC0, 0xBA00, &BitOps.Rev), + // ================================================================ + // GROUP 4: Mask 0xFF87 (High Register Special Cases) + // CRITICAL: These are specific cases of the 0xFF00 generic group. + // They verify Bit 7 (DN) and Bits 0-2 (Rd/Rm). + // ================================================================ + // 1. High Priority: ADD PC, Rm (R15) + new OpcodeRule(0xFF87, 0x4487, &ArithmeticOps.AddHighToPc), + // 2. High Priority: ADD SP, Rm (R13) + new OpcodeRule(0xFF87, 0x4485, &ArithmeticOps.AddHighToSp), + // BLX Rm + new OpcodeRule(0xFF87, 0x4780, &FlowOps.Blx), + // BX Rm + new OpcodeRule(0xFF87, 0x4700, &FlowOps.Bx), + // 1. MOV PC, Rm (High Priority) + new OpcodeRule(0xFF87, 0x4687, &BitOps.MovToPc), + // 2. MOV SP, Rm (High Priority) + new OpcodeRule(0xFF87, 0x4685, &BitOps.MovToSp), + // ================================================================ + // GROUP 5: Mask 0xFF80 + // ================================================================ + // ADD (SP + imm7) + new OpcodeRule(0xFF80, 0xB000, &ArithmeticOps.AddSpImmediate7), + // Sub (SP - imm) + new OpcodeRule(0xFF80, 0xB080, &ArithmeticOps.SubSp), + // ================================================================ + // GROUP 6: Mask 0xFF00 (8 bits significant - Broad Categories) + // ================================================================ + // 3. Low Priority: ADD Generic (R0-R12, R14) + new OpcodeRule(0xFF00, 0x4400, &ArithmeticOps.AddHighToReg), + // CMP Rn, Rm (High Registers - Encoding T2) + new OpcodeRule(0xFF00, 0x4500, &ArithmeticOps.CmpHighRegister), + // 3. MOV Rd, Rm (Generic Fallback) + new OpcodeRule(0xFF00, 0x4600, &BitOps.MovRegister), + // MULS (0x4340) and MVNS (0x43C0) are subsets and were handled above + // ORRS (Rd, Rm) - NOTE: This covers 0x4300-0x43FF. + new OpcodeRule(0xFF00, 0x4300, &BitOps.Orrs), + // Stack Operations + new OpcodeRule(0xFF00, 0xBC00, &MemoryOps.Pop), + new OpcodeRule(0xFF00, 0xBD00, &MemoryOps.PopPc), + new OpcodeRule(0xFF00, 0xB400, &MemoryOps.Push), + new OpcodeRule(0xFF00, 0xB500, &MemoryOps.PushLr), + // Conditional Branches (T1) + // SVC (0xDF00) is technically caught here if not handled separately. + // Ensure the handler filters 0xF (SVC) or add a specific SVC rule with higher priority. + new OpcodeRule(0xFF00, 0xD000, &FlowOps.Beq), + new OpcodeRule(0xFF00, 0xD100, &FlowOps.Bne), + new OpcodeRule(0xFF00, 0xD200, &FlowOps.Bcs), + new OpcodeRule(0xFF00, 0xD300, &FlowOps.Bcc), + new OpcodeRule(0xFF00, 0xD400, &FlowOps.Bmi), + new OpcodeRule(0xFF00, 0xD500, &FlowOps.Bpl), + new OpcodeRule(0xFF00, 0xD600, &FlowOps.Bvs), + new OpcodeRule(0xFF00, 0xD700, &FlowOps.Bvc), + new OpcodeRule(0xFF00, 0xD800, &FlowOps.Bhi), + new OpcodeRule(0xFF00, 0xD900, &FlowOps.Bls), + new OpcodeRule(0xFF00, 0xDA00, &FlowOps.Bge), + new OpcodeRule(0xFF00, 0xDB00, &FlowOps.Blt), + new OpcodeRule(0xFF00, 0xDC00, &FlowOps.Bgt), + new OpcodeRule(0xFF00, 0xDD00, &FlowOps.Ble), + // ================================================================ + // GROUP 7: Mask 0xFE00 + // ================================================================ + // ADDS (Rd, Rn, Rm) - Encoding T1 Register + new OpcodeRule(0xFE00, 0x1800, &ArithmeticOps.AddsRegister), + // ADDS (Rd, Rn, imm3) + new OpcodeRule(0xFE00, 0x1C00, &ArithmeticOps.AddsImmediate3), + // SUBS (imm3) + new OpcodeRule(0xFE00, 0x1E00, &ArithmeticOps.SubsImmediate3), + // SUBS (Register) + new OpcodeRule(0xFE00, 0x1A00, &ArithmeticOps.SubsRegister), + // ================================================================ + // GROUP 8: Mask 0xF800 (5 bits significant - Most Generic) + // ================================================================ + // ADD (Rd = SP + imm8) + new OpcodeRule(0xF800, 0xA800, &ArithmeticOps.AddSpImmediate8), + // ADDS (Rd, imm8) + new OpcodeRule(0xF800, 0x3000, &ArithmeticOps.AddsImmediate8), + // SUBS (imm8) + new OpcodeRule(0xF800, 0x3800, &ArithmeticOps.SubsImmediate8), + // ADR (Rd, imm8) + new OpcodeRule(0xF800, 0xA000, &ArithmeticOps.Adr), + // ASRS (Rd, Rm, imm5) + new OpcodeRule(0xF800, 0x1000, &BitOps.AsrsImm5), + // BL (Branch with Link) + new OpcodeRule(0xF800, 0xF000, &FlowOps.Bl), + // B (Unconditional) - T2 + new OpcodeRule(0xF800, 0xE000, &FlowOps.Branch), + // CMP Rn, #imm8 + new OpcodeRule(0xF800, 0x2800, &ArithmeticOps.CmpImmediate), + // MOVS (Rd, #imm8) + new OpcodeRule(0xF800, 0x2000, &BitOps.Movs), + // LDMIA (Load Multiple Increment After) + new OpcodeRule(0xF800, 0xC800, &MemoryOps.Ldmia), + // LSLS (Rd, Rm, imm5) + new OpcodeRule(0xF800, 0x0000, &BitOps.LslsImm5), + // ================================================================ + // GROUP 9: Mask 0xBF00 + // ================================================================ + // NOP (Hint) + new OpcodeRule(0xBF00, 0xBF00, &SystemOps.Nop), + ]; + + for (var i = 0; i < 65536; i++) + { + var opcode = (ushort)i; + foreach (ref readonly var rule in rules) + { + if ((opcode & rule.Mask) != rule.Pattern) + continue; + _fastTablePtr[i] = rule.Handler; + break; + } + } + } + + [ExcludeFromCodeCoverage] + ~InstructionDecoder() + { + Dispose(false); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispatch(ushort opcode, CortexM0Plus cpu) + { + _fastTablePtr[opcode](opcode, cpu); + } + + public nuint GetHandler(ushort opcode) + { + return (nuint)_fastTablePtr[opcode]; + } + + private static void HandleUndefined(ushort opcode, CortexM0Plus cpu) + { + throw new Exception($"Undefined Opcode: 0x{opcode:X4} PC={cpu.Registers.PC:X8}"); + } + + [ExcludeFromCodeCoverage] + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + [ExcludeFromCodeCoverage] + private void Dispose(bool disposing) + { + _ = disposing; + if (_disposed) + return; + + if (_pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + + _disposed = true; + } } diff --git a/src/RP2040.Core/Cpu/Instructions/BitOps.cs b/src/RP2040.Core/Cpu/Instructions/BitOps.cs index 7b9bfe7..faf3f17 100644 --- a/src/RP2040.Core/Cpu/Instructions/BitOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/BitOps.cs @@ -6,279 +6,280 @@ namespace RP2040.Core.Cpu.Instructions; public static class BitOps { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Ands (ushort opcode, CortexM0Plus cpu) - { - var rdn = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; - - ref var ptrRdn = ref cpu.Registers[rdn]; - var valRm = cpu.Registers[rm]; - - var result = ptrRdn & valRm; - ptrRdn = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AsrsImm5 (ushort opcode, CortexM0Plus cpu) - { - var rd = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; - var imm5 = (opcode >> 6) & 0x1F; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; - - var shiftVal = imm5 == 0 ? 31 : imm5; - var shiftCarry = (imm5 - 1) & 0x1F; - - var result = (uint)((int)valRm >> shiftVal); - var carry = ((valRm >> shiftCarry) & 1) != 0; - - ptrRd = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - cpu.Registers.C = carry; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AsrsRegister (ushort opcode, CortexM0Plus cpu) - { - var rdn = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; - - ref var ptrRdn = ref cpu.Registers[rdn]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Ands(ushort opcode, CortexM0Plus cpu) + { + var rdn = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; - var valRdn = ptrRdn; - var valRm = cpu.Registers[rm]; - - var shift = (int)(valRm & 0xFF); - - if (shift == 0) { - cpu.Registers.N = (int)valRdn < 0; - cpu.Registers.Z = (valRdn == 0); - return; - } + ref var ptrRdn = ref cpu.Registers[rdn]; + var valRm = cpu.Registers[rm]; + + var result = ptrRdn & valRm; + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AsrsImm5(ushort opcode, CortexM0Plus cpu) + { + var rd = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; + var imm5 = (opcode >> 6) & 0x1F; - var effShift = shift < 32 ? shift : 31; - var effCarryShift = shift < 32 ? (shift - 1) : 31; - - var result = (uint)((int)valRdn >> effShift); - var carry = ((valRdn >> effCarryShift) & 1) != 0; - - ptrRdn = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - cpu.Registers.C = carry; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bics (ushort opcode, CortexM0Plus cpu) - { - var rdn = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; - - ref var ptrRdn = ref cpu.Registers[rdn]; - var valRm = cpu.Registers[rm]; - - var result = ptrRdn & ~valRm; - ptrRdn = result; + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - } + var shiftVal = imm5 == 0 ? 31 : imm5; + var shiftCarry = (imm5 - 1) & 0x1F; + + var result = (uint)((int)valRm >> shiftVal); + var carry = ((valRm >> shiftCarry) & 1) != 0; - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Eors (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rdn = opcode & 0x7; - ref var ptrRdn = ref cpu.Registers[rdn]; - var valRm = cpu.Registers[rm]; + ptrRd = result; - var result = ptrRdn ^ valRm; - ptrRdn = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void LslsImm5 (ushort opcode, CortexM0Plus cpu) - { - var imm5 = (opcode >> 6) & 0x1F; - var rm = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + cpu.Registers.C = carry; + } - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AsrsRegister(ushort opcode, CortexM0Plus cpu) + { + var rdn = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; - var extended = (ulong)valRm << imm5; - var result = (uint)extended; - var carry = (extended & 0x1_0000_0000) != 0; - - ptrRd = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - cpu.Registers.C = carry; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void LslsRegister (ushort opcode, CortexM0Plus cpu) - { - var rdn = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; + ref var ptrRdn = ref cpu.Registers[rdn]; - ref var ptrRdn = ref cpu.Registers[rdn]; - - var valRdn = ptrRdn; - var shift = (int)(cpu.Registers[rm] & 0xFF); - - var extended = (ulong)valRdn << shift; - var result = shift >= 32 ? 0 : (uint)extended; - - var calcCarry = (extended & 0x1_0000_0000) != 0; - var finalCarry = (shift == 0) ? (cpu.Registers.GetC () != 0) : calcCarry; - - ptrRdn = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - cpu.Registers.C = finalCarry; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void LslsZero (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; - - ptrRd = valRm; - - cpu.Registers.N = (int)valRm < 0; - cpu.Registers.Z = (valRm == 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void MovRegister (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - var rd = ((opcode >> 4) & 0x8) | (opcode & 0x7); - - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; - - valRm += (uint)((rm + 1) >> 4) << 1; - ptrRd = valRm; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Movs (ushort opcode, CortexM0Plus cpu) - { - var value = (uint)(opcode & 0xFF); - - cpu.Registers[(opcode >> 8) & 7] = value; - - cpu.Registers.N = false; - cpu.Registers.Z = value == 0; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void MovToPc (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - - ref var pc = ref cpu.Registers.PC; - var valRm = cpu.Registers[rm]; - - valRm += (uint)((rm + 1) >> 4) << 1; - pc = valRm & 0xFFFFFFFE; - cpu.Cycles++; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void MovToSp (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - ref var sp = ref cpu.Registers.SP; - - var valRm = cpu.Registers[rm]; - valRm += (uint)((rm + 1) >> 4) << 1; - - sp = valRm & 0xFFFFFFFC; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Mvns (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 7; - var rd = opcode & 7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; - - var result = ~valRm; - ptrRd = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - } + var valRdn = ptrRdn; + var valRm = cpu.Registers[rm]; - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Orrs (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 7; - var rdn = opcode & 7; + var shift = (int)(valRm & 0xFF); - ref var ptrRdn = ref cpu.Registers[rdn]; - var valRm = cpu.Registers[rm]; - - var result = ptrRdn | valRm; - ptrRdn = result; - - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Rev (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - var val = cpu.Registers[rm]; - - cpu.Registers[rd] = BinaryPrimitives.ReverseEndianness (val); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Rev16 (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - var val = cpu.Registers[rm]; - - cpu.Registers[rd] = ((val & 0xFF00FF00u) >> 8) | ((val & 0x00FF00FFu) << 8); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Revsh (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - var val = (ushort)cpu.Registers[rm]; - var reversed = BinaryPrimitives.ReverseEndianness (val); - cpu.Registers[rd] = (uint)(short)reversed; - } + if (shift == 0) + { + cpu.Registers.N = (int)valRdn < 0; + cpu.Registers.Z = (valRdn == 0); + return; + } + + var effShift = shift < 32 ? shift : 31; + var effCarryShift = shift < 32 ? (shift - 1) : 31; + + var result = (uint)((int)valRdn >> effShift); + var carry = ((valRdn >> effCarryShift) & 1) != 0; + + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + cpu.Registers.C = carry; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bics(ushort opcode, CortexM0Plus cpu) + { + var rdn = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; + + ref var ptrRdn = ref cpu.Registers[rdn]; + var valRm = cpu.Registers[rm]; + + var result = ptrRdn & ~valRm; + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Eors(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rdn = opcode & 0x7; + ref var ptrRdn = ref cpu.Registers[rdn]; + var valRm = cpu.Registers[rm]; + + var result = ptrRdn ^ valRm; + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LslsImm5(ushort opcode, CortexM0Plus cpu) + { + var imm5 = (opcode >> 6) & 0x1F; + var rm = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; + + var extended = (ulong)valRm << imm5; + var result = (uint)extended; + var carry = (extended & 0x1_0000_0000) != 0; + + ptrRd = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + cpu.Registers.C = carry; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LslsRegister(ushort opcode, CortexM0Plus cpu) + { + var rdn = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; + + ref var ptrRdn = ref cpu.Registers[rdn]; + + var valRdn = ptrRdn; + var shift = (int)(cpu.Registers[rm] & 0xFF); + + var extended = (ulong)valRdn << shift; + var result = shift >= 32 ? 0 : (uint)extended; + + var calcCarry = (extended & 0x1_0000_0000) != 0; + var finalCarry = (shift == 0) ? (cpu.Registers.GetC() != 0) : calcCarry; + + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + cpu.Registers.C = finalCarry; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void LslsZero(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; + + ptrRd = valRm; + + cpu.Registers.N = (int)valRm < 0; + cpu.Registers.Z = (valRm == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MovRegister(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + var rd = ((opcode >> 4) & 0x8) | (opcode & 0x7); + + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; + + valRm += (uint)((rm + 1) >> 4) << 1; + ptrRd = valRm; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Movs(ushort opcode, CortexM0Plus cpu) + { + var value = (uint)(opcode & 0xFF); + + cpu.Registers[(opcode >> 8) & 7] = value; + + cpu.Registers.N = false; + cpu.Registers.Z = value == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MovToPc(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + + ref var pc = ref cpu.Registers.PC; + var valRm = cpu.Registers[rm]; + + valRm += (uint)((rm + 1) >> 4) << 1; + pc = valRm & 0xFFFFFFFE; + cpu.Cycles++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MovToSp(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + ref var sp = ref cpu.Registers.SP; + + var valRm = cpu.Registers[rm]; + valRm += (uint)((rm + 1) >> 4) << 1; + + sp = valRm & 0xFFFFFFFC; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Mvns(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 7; + var rd = opcode & 7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; + + var result = ~valRm; + ptrRd = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Orrs(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 7; + var rdn = opcode & 7; + + ref var ptrRdn = ref cpu.Registers[rdn]; + var valRm = cpu.Registers[rm]; + + var result = ptrRdn | valRm; + ptrRdn = result; + + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Rev(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + var val = cpu.Registers[rm]; + + cpu.Registers[rd] = BinaryPrimitives.ReverseEndianness(val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Rev16(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + var val = cpu.Registers[rm]; + + cpu.Registers[rd] = ((val & 0xFF00FF00u) >> 8) | ((val & 0x00FF00FFu) << 8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Revsh(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + var val = (ushort)cpu.Registers[rm]; + var reversed = BinaryPrimitives.ReverseEndianness(val); + cpu.Registers[rd] = (uint)(short)reversed; + } } diff --git a/src/RP2040.Core/Cpu/Instructions/FlowOps.cs b/src/RP2040.Core/Cpu/Instructions/FlowOps.cs index beab2de..2027395 100644 --- a/src/RP2040.Core/Cpu/Instructions/FlowOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/FlowOps.cs @@ -4,168 +4,183 @@ namespace RP2040.Core.Cpu.Instructions; public static class FlowOps { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bl (ushort opcodeH1, CortexM0Plus cpu) - { - ref var pc = ref cpu.Registers.PC; - var opcodeH2 = cpu.Bus.ReadHalfWord (pc); - - pc += 2; - var nextPc = pc; - - var s = (opcodeH1 >> 10) & 1; - var j1 = (opcodeH2 >> 13) & 1; - var j2 = (opcodeH2 >> 11) & 1; - - var imm10 = opcodeH1 & 0x3FF; - var imm11 = opcodeH2 & 0x7FF; - - var i1 = ~(j1 ^ s) & 1; - var i2 = ~(j2 ^ s) & 1; - - var offset = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); - - offset = (offset << 7) >> 7; - - cpu.Registers.LR = nextPc | 1; - pc = (uint)(nextPc + offset); - - cpu.Cycles += 2; // Takes total 3 cycles - // BLTaken Action is Missing - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Blx (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - ref var pc = ref cpu.Registers.PC; - - cpu.Registers.LR = pc | 0x1; - - var targetAddress = cpu.Registers[rm]; - pc = targetAddress & 0xFFFFFFFE; - - cpu.Cycles++; - // BLTaken Action is Missing - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Branch (ushort opcode, CortexM0Plus cpu) - { - var offset = (opcode << 21) >> 20; - - ref var pc = ref cpu.Registers.PC; - - pc += (uint)(offset + 2); - - cpu.Cycles++; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Beq (ushort opcode, CortexM0Plus cpu) // Z == 1 - { - if (cpu.Registers.Z) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bne (ushort opcode, CortexM0Plus cpu) // Z == 0 - { - if (!cpu.Registers.Z) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bcs (ushort opcode, CortexM0Plus cpu) // C == 1 - { - if (cpu.Registers.C) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bcc (ushort opcode, CortexM0Plus cpu) // C == 0 - { - if (!cpu.Registers.C) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bmi (ushort opcode, CortexM0Plus cpu) // N == 1 - { - if (cpu.Registers.N) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bpl (ushort opcode, CortexM0Plus cpu) // N == 0 - { - if (!cpu.Registers.N) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bvs (ushort opcode, CortexM0Plus cpu) // V == 1 - { - if (cpu.Registers.V) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bvc (ushort opcode, CortexM0Plus cpu) // V == 0 - { - if (!cpu.Registers.V) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bhi (ushort opcode, CortexM0Plus cpu) // C==1 && Z==0 - { - if (cpu.Registers.C && !cpu.Registers.Z) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bls (ushort opcode, CortexM0Plus cpu) // C==0 || Z==1 - { - if (!cpu.Registers.C || cpu.Registers.Z) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bge (ushort opcode, CortexM0Plus cpu) // N == V - { - if (cpu.Registers.N == cpu.Registers.V) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Blt (ushort opcode, CortexM0Plus cpu) // N != V - { - if (cpu.Registers.N != cpu.Registers.V) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bgt (ushort opcode, CortexM0Plus cpu) // Z==0 && N==V - { - if (!cpu.Registers.Z && (cpu.Registers.N == cpu.Registers.V)) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Ble (ushort opcode, CortexM0Plus cpu) // Z==1 || N!=V - { - if (cpu.Registers.Z || (cpu.Registers.N != cpu.Registers.V)) TakeBranch (opcode, cpu); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Bx (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xf; - var target = cpu.Registers[rm]; - if (target >= 0xFFFFFFF0 && cpu.Registers.IPSR != 0) { - cpu.ExceptionReturn (target); - return; - } - cpu.Registers.PC = target & 0xFFFFFFFE; - cpu.Cycles++; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - private static void TakeBranch (ushort opcode, CortexM0Plus cpu) - { - var offset = (sbyte)(opcode & 0xFF) << 1; - - ref var pc = ref cpu.Registers.PC; - pc += (uint)(offset + 2); - - cpu.Cycles++; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bl(ushort opcodeH1, CortexM0Plus cpu) + { + ref var pc = ref cpu.Registers.PC; + var opcodeH2 = cpu.Bus.ReadHalfWord(pc); + + pc += 2; + var nextPc = pc; + + var s = (opcodeH1 >> 10) & 1; + var j1 = (opcodeH2 >> 13) & 1; + var j2 = (opcodeH2 >> 11) & 1; + + var imm10 = opcodeH1 & 0x3FF; + var imm11 = opcodeH2 & 0x7FF; + + var i1 = ~(j1 ^ s) & 1; + var i2 = ~(j2 ^ s) & 1; + + var offset = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1); + + offset = (offset << 7) >> 7; + + cpu.Registers.LR = nextPc | 1; + pc = (uint)(nextPc + offset); + + cpu.Cycles += 2; // Takes total 3 cycles + // BLTaken Action is Missing + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Blx(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + ref var pc = ref cpu.Registers.PC; + + cpu.Registers.LR = pc | 0x1; + + var targetAddress = cpu.Registers[rm]; + pc = targetAddress & 0xFFFFFFFE; + + cpu.Cycles++; + // BLTaken Action is Missing + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Branch(ushort opcode, CortexM0Plus cpu) + { + var offset = (opcode << 21) >> 20; + + ref var pc = ref cpu.Registers.PC; + + pc += (uint)(offset + 2); + + cpu.Cycles++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Beq(ushort opcode, CortexM0Plus cpu) // Z == 1 + { + if (cpu.Registers.Z) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bne(ushort opcode, CortexM0Plus cpu) // Z == 0 + { + if (!cpu.Registers.Z) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bcs(ushort opcode, CortexM0Plus cpu) // C == 1 + { + if (cpu.Registers.C) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bcc(ushort opcode, CortexM0Plus cpu) // C == 0 + { + if (!cpu.Registers.C) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bmi(ushort opcode, CortexM0Plus cpu) // N == 1 + { + if (cpu.Registers.N) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bpl(ushort opcode, CortexM0Plus cpu) // N == 0 + { + if (!cpu.Registers.N) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bvs(ushort opcode, CortexM0Plus cpu) // V == 1 + { + if (cpu.Registers.V) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bvc(ushort opcode, CortexM0Plus cpu) // V == 0 + { + if (!cpu.Registers.V) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bhi(ushort opcode, CortexM0Plus cpu) // C==1 && Z==0 + { + if (cpu.Registers.C && !cpu.Registers.Z) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bls(ushort opcode, CortexM0Plus cpu) // C==0 || Z==1 + { + if (!cpu.Registers.C || cpu.Registers.Z) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bge(ushort opcode, CortexM0Plus cpu) // N == V + { + if (cpu.Registers.N == cpu.Registers.V) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Blt(ushort opcode, CortexM0Plus cpu) // N != V + { + if (cpu.Registers.N != cpu.Registers.V) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bgt(ushort opcode, CortexM0Plus cpu) // Z==0 && N==V + { + if (!cpu.Registers.Z && (cpu.Registers.N == cpu.Registers.V)) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Ble(ushort opcode, CortexM0Plus cpu) // Z==1 || N!=V + { + if (cpu.Registers.Z || (cpu.Registers.N != cpu.Registers.V)) + TakeBranch(opcode, cpu); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Bx(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xf; + var target = cpu.Registers[rm]; + if (target >= 0xFFFFFFF0 && cpu.Registers.IPSR != 0) + { + cpu.ExceptionReturn(target); + return; + } + cpu.Registers.PC = target & 0xFFFFFFFE; + cpu.Cycles++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void TakeBranch(ushort opcode, CortexM0Plus cpu) + { + var offset = (sbyte)(opcode & 0xFF) << 1; + + ref var pc = ref cpu.Registers.PC; + pc += (uint)(offset + 2); + + cpu.Cycles++; + } } diff --git a/src/RP2040.Core/Cpu/Instructions/MemoryOps.cs b/src/RP2040.Core/Cpu/Instructions/MemoryOps.cs index 5a41791..de37545 100644 --- a/src/RP2040.Core/Cpu/Instructions/MemoryOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/MemoryOps.cs @@ -4,188 +4,207 @@ namespace RP2040.Core.Cpu.Instructions; -public unsafe static class MemoryOps +public static unsafe class MemoryOps { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Pop (ushort opcode, CortexM0Plus cpu) - { - var mask = (uint)(opcode & 0xFF); - var regCount = (uint)BitOperations.PopCount (mask); - - var sp = cpu.Registers.SP; - var finalSp = sp + (regCount * 4); - - if ((sp >> 28) == BusInterconnect.REGION_SRAM) { - var rawPtr = cpu.Bus.PtrSram + (sp & BusInterconnect.MASK_SRAM); - - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = Unsafe.ReadUnaligned (rawPtr); - - rawPtr += 4; - mask &= (mask - 1); - } - } - else { - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = cpu.Bus.ReadWord (sp); - sp += 4; - mask &= (mask - 1); - } - } - - cpu.Registers.SP = finalSp; - cpu.Cycles += 1 + regCount; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void PopPc (ushort opcode, CortexM0Plus cpu) - { - var mask = (uint)(opcode & 0xFF); - var regCount = (uint)BitOperations.PopCount (mask); - - var sp = cpu.Registers.SP; - var finalSp = sp + ((regCount + 1) * 4); - - if ((sp >> 28) == BusInterconnect.REGION_SRAM) { - var rawPtr = cpu.Bus.PtrSram + (sp & BusInterconnect.MASK_SRAM); - - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = Unsafe.ReadUnaligned (rawPtr); - rawPtr += 4; - mask &= (mask - 1); - } - var newPc = Unsafe.ReadUnaligned (rawPtr); - - cpu.Registers.PC = newPc & 0xFFFFFFFE; - } - else { - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = cpu.Bus.ReadWord (sp); - sp += 4; - mask &= (mask - 1); - } - var newPc = cpu.Bus.ReadWord (sp); - cpu.Registers.PC = newPc & 0xFFFFFFFE; - } - - cpu.Registers.SP = finalSp; - cpu.Cycles += 4 + regCount; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Push (ushort opcode, CortexM0Plus cpu) - { - var mask = (uint)(opcode & 0xFF); - var regCount = (uint)BitOperations.PopCount (mask); - var totalBytes = regCount * 4; - - var oldSp = cpu.Registers.SP; - var newSp = oldSp - totalBytes; - - if ((newSp >> 28) == BusInterconnect.REGION_SRAM) { - var rawPtr = cpu.Bus.PtrSram + (newSp & BusInterconnect.MASK_SRAM); - - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - - Unsafe.WriteUnaligned (rawPtr, cpu.Registers[regIdx]); - - rawPtr += 4; - mask &= (mask - 1); - } - } - else { - var writePtr = newSp; - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - var val = cpu.Registers[regIdx]; - cpu.Bus.WriteWord (writePtr, val); - - writePtr += 4; - mask &= (mask - 1); - } - } - - cpu.Registers.SP = newSp; - cpu.Cycles += regCount; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void PushLr (ushort opcode, CortexM0Plus cpu) - { - var mask = (uint)(opcode & 0xFF); - var regCount = (uint)BitOperations.PopCount (mask); - var totalBytes = (regCount + 1) * 4; // +1 because of LR - - var oldSp = cpu.Registers.SP; - var newSp = oldSp - totalBytes; - - if ((newSp >> 28) == BusInterconnect.REGION_SRAM) { - var rawPtr = cpu.Bus.PtrSram + (newSp & BusInterconnect.MASK_SRAM); - - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - Unsafe.WriteUnaligned (rawPtr, cpu.Registers[regIdx]); - rawPtr += 4; - mask &= (mask - 1); - } - Unsafe.WriteUnaligned (rawPtr, cpu.Registers.LR); - } - else { - var writePtr = newSp; - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Bus.WriteWord (writePtr, cpu.Registers[regIdx]); - writePtr += 4; - mask &= (mask - 1); - } - cpu.Bus.WriteWord (writePtr, cpu.Registers.LR); - } - - cpu.Registers.SP = newSp; - cpu.Cycles += regCount + 1; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Ldmia (ushort opcode, CortexM0Plus cpu) - { - var rn = (opcode >> 8) & 0x7; - var mask = (uint)(opcode & 0xFF); - - var regCount = (uint)BitOperations.PopCount (mask); - var baseAddr = cpu.Registers[rn]; - - var isRnInList = (mask >> rn) & 1; - var writeBackOffset = (regCount * 4) * (isRnInList ^ 1); - - if ((baseAddr >> 28) == BusInterconnect.REGION_SRAM) { - var ptr = cpu.Bus.PtrSram + (baseAddr & BusInterconnect.MASK_SRAM); - - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = Unsafe.ReadUnaligned (ptr); - - ptr += 4; - mask &= (mask - 1); - } - } - else // SLOW PATH - { - var readPtr = baseAddr; - while (mask != 0) { - var regIdx = BitOperations.TrailingZeroCount (mask); - cpu.Registers[regIdx] = cpu.Bus.ReadWord (readPtr); - - readPtr += 4; - mask &= (mask - 1); - } - } - - cpu.Registers[rn] += writeBackOffset; - cpu.Cycles += (int)regCount; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Pop(ushort opcode, CortexM0Plus cpu) + { + var mask = (uint)(opcode & 0xFF); + var regCount = (uint)BitOperations.PopCount(mask); + + var sp = cpu.Registers.SP; + var finalSp = sp + (regCount * 4); + + if ((sp >> 28) == BusInterconnect.REGION_SRAM) + { + var rawPtr = cpu.Bus.PtrSram + (sp & BusInterconnect.MASK_SRAM); + + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = Unsafe.ReadUnaligned(rawPtr); + + rawPtr += 4; + mask &= (mask - 1); + } + } + else + { + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = cpu.Bus.ReadWord(sp); + sp += 4; + mask &= (mask - 1); + } + } + + cpu.Registers.SP = finalSp; + cpu.Cycles += 1 + regCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PopPc(ushort opcode, CortexM0Plus cpu) + { + var mask = (uint)(opcode & 0xFF); + var regCount = (uint)BitOperations.PopCount(mask); + + var sp = cpu.Registers.SP; + var finalSp = sp + ((regCount + 1) * 4); + + if ((sp >> 28) == BusInterconnect.REGION_SRAM) + { + var rawPtr = cpu.Bus.PtrSram + (sp & BusInterconnect.MASK_SRAM); + + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = Unsafe.ReadUnaligned(rawPtr); + rawPtr += 4; + mask &= (mask - 1); + } + var newPc = Unsafe.ReadUnaligned(rawPtr); + + cpu.Registers.PC = newPc & 0xFFFFFFFE; + } + else + { + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = cpu.Bus.ReadWord(sp); + sp += 4; + mask &= (mask - 1); + } + var newPc = cpu.Bus.ReadWord(sp); + cpu.Registers.PC = newPc & 0xFFFFFFFE; + } + + cpu.Registers.SP = finalSp; + cpu.Cycles += 4 + regCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Push(ushort opcode, CortexM0Plus cpu) + { + var mask = (uint)(opcode & 0xFF); + var regCount = (uint)BitOperations.PopCount(mask); + var totalBytes = regCount * 4; + + var oldSp = cpu.Registers.SP; + var newSp = oldSp - totalBytes; + + if ((newSp >> 28) == BusInterconnect.REGION_SRAM) + { + var rawPtr = cpu.Bus.PtrSram + (newSp & BusInterconnect.MASK_SRAM); + + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + + Unsafe.WriteUnaligned(rawPtr, cpu.Registers[regIdx]); + + rawPtr += 4; + mask &= (mask - 1); + } + } + else + { + var writePtr = newSp; + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + var val = cpu.Registers[regIdx]; + cpu.Bus.WriteWord(writePtr, val); + + writePtr += 4; + mask &= (mask - 1); + } + } + + cpu.Registers.SP = newSp; + cpu.Cycles += regCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void PushLr(ushort opcode, CortexM0Plus cpu) + { + var mask = (uint)(opcode & 0xFF); + var regCount = (uint)BitOperations.PopCount(mask); + var totalBytes = (regCount + 1) * 4; // +1 because of LR + + var oldSp = cpu.Registers.SP; + var newSp = oldSp - totalBytes; + + if ((newSp >> 28) == BusInterconnect.REGION_SRAM) + { + var rawPtr = cpu.Bus.PtrSram + (newSp & BusInterconnect.MASK_SRAM); + + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + Unsafe.WriteUnaligned(rawPtr, cpu.Registers[regIdx]); + rawPtr += 4; + mask &= (mask - 1); + } + Unsafe.WriteUnaligned(rawPtr, cpu.Registers.LR); + } + else + { + var writePtr = newSp; + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Bus.WriteWord(writePtr, cpu.Registers[regIdx]); + writePtr += 4; + mask &= (mask - 1); + } + cpu.Bus.WriteWord(writePtr, cpu.Registers.LR); + } + + cpu.Registers.SP = newSp; + cpu.Cycles += regCount + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Ldmia(ushort opcode, CortexM0Plus cpu) + { + var rn = (opcode >> 8) & 0x7; + var mask = (uint)(opcode & 0xFF); + + var regCount = (uint)BitOperations.PopCount(mask); + var baseAddr = cpu.Registers[rn]; + + var isRnInList = (mask >> rn) & 1; + var writeBackOffset = (regCount * 4) * (isRnInList ^ 1); + + if ((baseAddr >> 28) == BusInterconnect.REGION_SRAM) + { + var ptr = cpu.Bus.PtrSram + (baseAddr & BusInterconnect.MASK_SRAM); + + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = Unsafe.ReadUnaligned(ptr); + + ptr += 4; + mask &= (mask - 1); + } + } + else // SLOW PATH + { + var readPtr = baseAddr; + while (mask != 0) + { + var regIdx = BitOperations.TrailingZeroCount(mask); + cpu.Registers[regIdx] = cpu.Bus.ReadWord(readPtr); + + readPtr += 4; + mask &= (mask - 1); + } + } + + cpu.Registers[rn] += writeBackOffset; + cpu.Cycles += (int)regCount; + } } diff --git a/src/RP2040.Core/Cpu/Instructions/SystemOps.cs b/src/RP2040.Core/Cpu/Instructions/SystemOps.cs index f8215d6..c9d625e 100644 --- a/src/RP2040.Core/Cpu/Instructions/SystemOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/SystemOps.cs @@ -1,139 +1,159 @@ using System.Runtime.CompilerServices; + namespace RP2040.Core.Cpu.Instructions; public static class SystemOps { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Barrier (ushort opcodeH1, CortexM0Plus cpu) - { - cpu.Registers.PC += 2; - cpu.Cycles += 2; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Nop (ushort opcodeH1, CortexM0Plus cpu) - { - // Do nothing - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Mrs (ushort opcodeH1, CortexM0Plus cpu) - { - ref var pc = ref cpu.Registers.PC; - var opcodeH2 = cpu.Bus.ReadHalfWord (pc); - pc += 2; - - var sysm = opcodeH2 & 0xFF; - var rd = (opcodeH2 >> 8) & 0xF; - - uint result = 0; - - if ((sysm & 0xF8) == 0) { - var fullPsr = cpu.Registers.GetxPsr (); - - uint mask = 0; - if ((sysm & 1) != 0) mask |= 0x1FF; - if ((sysm & 4) == 0) mask |= 0xF0000000; - - result = fullPsr & mask; - } - else { - var isPrivileged = (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 1) == 0); - switch (sysm) { - case 8: // MSP - var usePsp = (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); - result = usePsp ? cpu.Registers.MSP_Storage : cpu.Registers.SP; - break; - - case 9: // PSP - var usePsp2 = (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); - result = usePsp2 ? cpu.Registers.SP : cpu.Registers.PSP_Storage; - break; - - case 16: // PRIMASK - result = cpu.Registers.PRIMASK & 1; - break; - - case 20: // CONTROL - result = cpu.Registers.CONTROL & 3; - isPrivileged = true; - break; - - default: - result = 0; - break; - } - if (!isPrivileged) result = 0; - } - - cpu.Registers[rd] = result; - cpu.Cycles += 2; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Msr (ushort opcodeH1, CortexM0Plus cpu) - { - ref var pc = ref cpu.Registers.PC; - var opcodeH2 = cpu.Bus.ReadHalfWord (pc); - pc += 2; - - var rn = opcodeH1 & 0xF; - var sysm = opcodeH2 & 0xFF; - var value = cpu.Registers[rn]; - - if ((sysm & 0xF8) == 0) { - if ((sysm & 4) == 0) { - cpu.Registers.N = (int)value < 0; // Bit 31 - cpu.Registers.Z = (value & 0x40000000) != 0; - cpu.Registers.C = (value & 0x20000000) != 0; - cpu.Registers.V = (value & 0x10000000) != 0; - } - } - else { - var isPrivileged = (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 1) == 0); - - if (isPrivileged) { - switch (sysm) { - case 8: // MSP - var alignedMsp = value & 0xFFFFFFFC; - - var isMspActive = (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 2) == 0); - - if (isMspActive) cpu.Registers.SP = alignedMsp; - else cpu.Registers.MSP_Storage = alignedMsp; - break; - - case 9: // PSP - var alignedPsp = value & 0xFFFFFFFC; - - var isPspActive = (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); - - if (isPspActive) cpu.Registers.SP = alignedPsp; - else cpu.Registers.PSP_Storage = alignedPsp; - break; - - case 16: // PRIMASK - cpu.Registers.PRIMASK = value & 1; - break; - - case 20: // CONTROL - var oldControl = cpu.Registers.CONTROL; - var newNpriv = value & 1; - var newSpsel = (cpu.Registers.IPSR == 0) ? (value & 2) : (oldControl & 2); - - var newControl = newNpriv | newSpsel; - - if (((oldControl ^ newControl) & 2) != 0) { - cpu.Registers.CONTROL = newControl; - cpu.UpdateStackPointerSource (); - } - else { - cpu.Registers.CONTROL = newControl; - } - break; - } - } - } - cpu.Cycles += 2; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Barrier(ushort opcodeH1, CortexM0Plus cpu) + { + cpu.Registers.PC += 2; + cpu.Cycles += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Nop(ushort opcodeH1, CortexM0Plus cpu) + { + // Do nothing + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Mrs(ushort opcodeH1, CortexM0Plus cpu) + { + ref var pc = ref cpu.Registers.PC; + var opcodeH2 = cpu.Bus.ReadHalfWord(pc); + pc += 2; + + var sysm = opcodeH2 & 0xFF; + var rd = (opcodeH2 >> 8) & 0xF; + + uint result = 0; + + if ((sysm & 0xF8) == 0) + { + var fullPsr = cpu.Registers.GetxPsr(); + + uint mask = 0; + if ((sysm & 1) != 0) + mask |= 0x1FF; + if ((sysm & 4) == 0) + mask |= 0xF0000000; + + result = fullPsr & mask; + } + else + { + var isPrivileged = (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 1) == 0); + switch (sysm) + { + case 8: // MSP + var usePsp = (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); + result = usePsp ? cpu.Registers.MSP_Storage : cpu.Registers.SP; + break; + + case 9: // PSP + var usePsp2 = (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); + result = usePsp2 ? cpu.Registers.SP : cpu.Registers.PSP_Storage; + break; + + case 16: // PRIMASK + result = cpu.Registers.PRIMASK & 1; + break; + + case 20: // CONTROL + result = cpu.Registers.CONTROL & 3; + isPrivileged = true; + break; + + default: + result = 0; + break; + } + if (!isPrivileged) + result = 0; + } + + cpu.Registers[rd] = result; + cpu.Cycles += 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Msr(ushort opcodeH1, CortexM0Plus cpu) + { + ref var pc = ref cpu.Registers.PC; + var opcodeH2 = cpu.Bus.ReadHalfWord(pc); + pc += 2; + + var rn = opcodeH1 & 0xF; + var sysm = opcodeH2 & 0xFF; + var value = cpu.Registers[rn]; + + if ((sysm & 0xF8) == 0) + { + if ((sysm & 4) == 0) + { + cpu.Registers.N = (int)value < 0; // Bit 31 + cpu.Registers.Z = (value & 0x40000000) != 0; + cpu.Registers.C = (value & 0x20000000) != 0; + cpu.Registers.V = (value & 0x10000000) != 0; + } + } + else + { + var isPrivileged = (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 1) == 0); + + if (isPrivileged) + { + switch (sysm) + { + case 8: // MSP + var alignedMsp = value & 0xFFFFFFFC; + + var isMspActive = + (cpu.Registers.IPSR != 0) || ((cpu.Registers.CONTROL & 2) == 0); + + if (isMspActive) + cpu.Registers.SP = alignedMsp; + else + cpu.Registers.MSP_Storage = alignedMsp; + break; + + case 9: // PSP + var alignedPsp = value & 0xFFFFFFFC; + + var isPspActive = + (cpu.Registers.IPSR == 0) && ((cpu.Registers.CONTROL & 2) != 0); + + if (isPspActive) + cpu.Registers.SP = alignedPsp; + else + cpu.Registers.PSP_Storage = alignedPsp; + break; + + case 16: // PRIMASK + cpu.Registers.PRIMASK = value & 1; + break; + + case 20: // CONTROL + var oldControl = cpu.Registers.CONTROL; + var newNpriv = value & 1; + var newSpsel = (cpu.Registers.IPSR == 0) ? (value & 2) : (oldControl & 2); + + var newControl = newNpriv | newSpsel; + + if (((oldControl ^ newControl) & 2) != 0) + { + cpu.Registers.CONTROL = newControl; + cpu.UpdateStackPointerSource(); + } + else + { + cpu.Registers.CONTROL = newControl; + } + break; + } + } + } + cpu.Cycles += 2; + } } diff --git a/src/RP2040.Core/Cpu/Registers.cs b/src/RP2040.Core/Cpu/Registers.cs index 9d2c2bf..03bcb3c 100644 --- a/src/RP2040.Core/Cpu/Registers.cs +++ b/src/RP2040.Core/Cpu/Registers.cs @@ -1,85 +1,88 @@ using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace RP2040.Core.Cpu; -[StructLayout (LayoutKind.Sequential)] +[StructLayout(LayoutKind.Sequential)] public struct Registers { - // --- Low Registers (R0-R7) --- - public uint R0; - public uint R1; - public uint R2; - public uint R3; - public uint R4; - public uint R5; - public uint R6; - public uint R7; + // --- Low Registers (R0-R7) --- + public uint R0; + public uint R1; + public uint R2; + public uint R3; + public uint R4; + public uint R5; + public uint R6; + public uint R7; - // --- High Registers (R8-R12) --- - public uint R8; - public uint R9; - public uint R10; - public uint R11; - public uint R12; + // --- High Registers (R8-R12) --- + public uint R8; + public uint R9; + public uint R10; + public uint R11; + public uint R12; - // R13: Stack Pointer (SP) - public uint SP; + // R13: Stack Pointer (SP) + public uint SP; - // R14: Link Register (LR) - public uint LR; + // R14: Link Register (LR) + public uint LR; - // R15: Program Counter (PC) - public uint PC; + // R15: Program Counter (PC) + public uint PC; - // --- Backing Stores for Stack Pointers --- - public uint MSP_Storage; - public uint PSP_Storage; + // --- Backing Stores for Stack Pointers --- + public uint MSP_Storage; + public uint PSP_Storage; - // --- System Registers --- - public uint PRIMASK; // Bit 0: PM - public uint CONTROL; // Bit 1: SPSEL, Bit 0: nPRIV - public uint IPSR; // Exception Number (0 = Thread Mode) + // --- System Registers --- + public uint PRIMASK; // Bit 0: PM + public uint CONTROL; // Bit 1: SPSEL, Bit 0: nPRIV + public uint IPSR; // Exception Number (0 = Thread Mode) - // --- Program Status Register (xPSR) --- - public bool N; // Negative - public bool Z; // Zero - public bool C; // Carry - public bool V; // Overflow + // --- Program Status Register (xPSR) --- + public bool N; // Negative + public bool Z; // Zero + public bool C; // Carry + public bool V; // Overflow - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public byte GetC () => Unsafe.As (ref C); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetC() => Unsafe.As(ref C); - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public uint GetxPsr () - { - uint apsr = 0; - if (N) apsr |= 0x80000000; - if (Z) apsr |= 0x40000000; - if (C) apsr |= 0x20000000; - if (V) apsr |= 0x10000000; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetxPsr() + { + uint apsr = 0; + if (N) + apsr |= 0x80000000; + if (Z) + apsr |= 0x40000000; + if (C) + apsr |= 0x20000000; + if (V) + apsr |= 0x10000000; - // xPSR combina APSR, EPSR (Thumb bit siempre 1) e IPSR - return apsr | 0x01000000 | (IPSR & 0x3F); - } + // xPSR combina APSR, EPSR (Thumb bit siempre 1) e IPSR + return apsr | 0x01000000 | (IPSR & 0x3F); + } - // Interrupt Status Register (IPSR) y Execution (EPSR) se pueden manejar aparte o implícitamente. + // Interrupt Status Register (IPSR) y Execution (EPSR) se pueden manejar aparte o implícitamente. - /// - /// Helper para obtener el valor indexado (sugar syntax para el Span) - /// - public ref uint this [int index] { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - [UnscopedRef] - get { - return ref Unsafe.Add (ref R0, index); - } - } + /// + /// Helper para obtener el valor indexado (sugar syntax para el Span) + /// + public ref uint this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [UnscopedRef] + get { return ref Unsafe.Add(ref R0, index); } + } - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public unsafe uint* GetBasePointer () - { - return (uint*)Unsafe.AsPointer (ref R0); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe uint* GetBasePointer() + { + return (uint*)Unsafe.AsPointer(ref R0); + } } diff --git a/src/RP2040.Core/Helpers/InstructionEmiter.cs b/src/RP2040.Core/Helpers/InstructionEmiter.cs index 253a0a7..5c6d50c 100644 --- a/src/RP2040.Core/Helpers/InstructionEmiter.cs +++ b/src/RP2040.Core/Helpers/InstructionEmiter.cs @@ -1,295 +1,366 @@ using System.Diagnostics.CodeAnalysis; + namespace RP2040.Core.Helpers; [ExcludeFromCodeCoverage] public static class InstructionEmiter { - const string LowRegisterIndexOutOfRange = "Register index out of range (0-7)"; - const string HighRegisterIndexOutOfRange = "Register index out of range (0-15)"; - - public static ushort Adcs (int rd, int rm) - { - if (rd > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4140 | (rm << 3) | rd); - } - - public static ushort AddSpImm7 (uint imm7) - { - imm7 >>= 2; - if (imm7 > 0x7F) throw new ArgumentException ("Immediate too large for ADD SP, immediate"); - return (ushort)(0xB000 | imm7); - } - - public static ushort AddSpImm8 (int rd, uint imm8) - { - imm8 >>= 2; - if (imm8 > 0xFF) throw new ArgumentException ("Immediate too large for ADD SP, immediate"); - if (rd > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)((uint)(0xA800 | rd << 8) | imm8); - } - - public static ushort AddHighRegisters (uint rdn, uint rm) - { - if (rdn > 0xf || rm > 0xf) throw new ArgumentException ("Register index out of range (0-15)"); - return (ushort)(0x4400 | (rdn & 0x8) << 4 | (rm & 0xf) << 3 | rdn & 0x7); - } - - public static ushort AddsImm3 (uint rd, uint rn, uint imm3) - { - if (rd > 7 || rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm3 > 7) throw new ArgumentException ("Immediate too large for ADDS"); - return (ushort)(0x1C00 | (imm3 & 0x7) << 6 | ((rn & 0x07) << 3) | (rd & 0x07)); - } - - public static ushort AddsImm8 (uint rdn, uint imm8) - { - if (rdn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm8 > 255) throw new ArgumentException ("Immediate too large for ADDS"); - return (ushort)(0x3000 | (rdn & 0x07) << 8 | (imm8 & 0xFF)); - } - - public static ushort AddsRegister (uint rd, uint rn, uint rm) - { - if (rd > 7 || rn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x1800 | ((rm & 0x07) << 6) | (rn & 0x07) << 3 | (rd & 0x07)); - } - - public static ushort SubSp (uint imm) - { - if (imm > 508) throw new ArgumentOutOfRangeException(nameof(imm), "Immediate value must be between 0 and 508."); - return (ushort)(0xB080 | ((imm >> 2) & 0x7f)); - } - - public static ushort SubsImm3 (uint rd, uint rn, uint imm3) - { - if (rd > 7 || rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm3 > 7) throw new ArgumentException("Immediate too large for SUBS"); - - return (ushort)(0x1E00 | ((imm3 & 0x7) << 6) | ((rn & 7) << 3) | (rd & 7)); - } - - public static ushort SubsImm8 (uint rdn, uint imm8) - { - if (rdn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm8 > 255) throw new ArgumentException ("Immediate too large for SUBS"); - return (ushort)(0x3800 | ((rdn & 7) << 8) | (imm8 & 0xff)); - } - - public static ushort SubsReg(uint rd, uint rn, uint rm) - { - if (rd > 7 || rn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x1A00 | ((rm & 0x7) << 6) | ((rn & 7) << 3) | (rd & 7)); - } - - public static ushort Adr (uint rd, uint imm8) - { - if (rd > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm8 >> 2 > 0xFF) throw new ArgumentException ("Immediate too large"); - return (ushort)(0xA000 | (rd & 7) << 8 | imm8 >> 2 & 0xFF); - } - - public static ushort Ands (uint rn, uint rm) - { - if (rn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4000 | (rm & 7) << 3 | rn & 7); - } - - public static ushort AsrsImm5 (uint rd, uint rm, uint imm5) - { - if (rd > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm5 > 31) throw new ArgumentException ("Immediate too large for ASRS"); - return (ushort)(0x1000 | (imm5 & 0x1F) << 6 | ((rm & 7) << 3) | (rd & 7)); - } - - public static ushort AsrsRegister (uint rdn, uint rm) - { - if (rdn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4100 | ((rm & 7) << 3) | rdn); - } - - public static ushort Bics (uint rdn, uint rm) - { - if (rdn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4380 | ((rm & 7) << 3) | rdn); - } - - public static uint Bl (int offset) - { - if (offset < -16777216 || offset > 16777214) - throw new ArgumentException ("Offset out of range for BL (+/- 16MB)"); - var s = (uint)((offset >> 24) & 1); - var i1 = (uint)((offset >> 23) & 1); - var i2 = (uint)((offset >> 22) & 1); - var imm10 = (uint)((offset >> 12) & 0x3FF); - var imm11 = (uint)((offset >> 1) & 0x7FF); - var j1 = (~i1 ^ s) & 1; - var j2 = (~i2 ^ s) & 1; - var h1 = 0xF000 | (s << 10) | imm10; - var h2 = 0xD000 | (j1 << 13) | (j2 << 11) | imm11; - return (h2 << 16) | h1; - } - - public static ushort Blx (int rm) - { - if (rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4780 | rm << 3); - } - - public static ushort BranchConditional (uint cond, uint offset) - { - if (cond > 15) throw new ArgumentException ("Condition code out of range (0-15)"); - if (offset > 0x3FF) throw new ArgumentException ("Offset out of range for Conditional Branch (-256 to +254)"); - if (offset % 2 != 0) throw new ArgumentException ("Offset must be aligned to 2 bytes"); - return (ushort)(0xD000 | (cond & 0xF) << 8 | offset >> 1 & 0x1FF); - } - - public static ushort Branch (uint offset) - { - if (offset > 0xFFF) throw new ArgumentException ("Offset out of range for Unconditional Branch (-2048 to +2046)"); - if (offset % 2 != 0) throw new ArgumentException ("Offset must be aligned to 2 bytes"); - return (ushort)(0xE000 | ((offset >> 1) & 0x7FF)); - } - - public static ushort Bx (uint rm) - { - if (rm > 15) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4700 | rm << 3); - } - - public static ushort Cmn (uint rn, uint rm) - { - if (rn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x42c0 | ((rn & 7) << 3) | rm); - } - - public static ushort CmpImm (uint rn, uint imm8) - { - if (rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm8 > 255) throw new ArgumentException ("Immediate too large for CMP"); - return (ushort)(0x2800 | (rn & 7) << 3 | imm8 & 0xFF); - } - - public static ushort CmpRegister (uint rn, uint rm) - { - if (rn > 7 || rm > 7) throw new ArgumentException ("CMP T1 only supports Low Registers (0-7)"); - return (ushort)(0x4280 | ((rm & 0x7) << 3) | (rn & 0x7)); - } - - public static ushort CmpHighRegister (uint rn, uint rm) - { - if (rn > 15 || rm > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - var n = (rn >> 3) & 1; - return (ushort)(0x4500 | (n << 7) | ((rm & 0xF) << 3) | (rn & 0x7)); - } - - public const uint Dmb = 0x8f4ff3bfu; - - public const uint Dsb = 0x8f4ff3bf; - - public static ushort Eors (uint rdn, uint rm) - { - if (rdn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4040 | ((rm & 7) << 3) | rdn & 0x7); - } - - public const uint Isb = 0x8f6ff3bf; - - public static ushort Mov (uint rd, uint rm) - { - if (rd > 15 || rm > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - return (ushort)(0x4600 | ((rd & 8) << 4) | ((rm & 0xF) << 3) | (rd & 7)); - } - - public static ushort Movs (uint rd, uint imm8) - { - if (rd > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - if (imm8 > 255) throw new ArgumentException ("Immediate too large for MOVS"); - return (ushort)(0x2000 | (rd & 7) << 8 | imm8 & 0xFF); - } - - public static ushort Muls (uint rn, uint rdm) - { - if (rn > 7 || rdm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4340 | ((rn & 7) << 3) | (rdm & 7)); - } - - public static ushort Mvns (uint rd, uint rm) - { - if (rd > 15 || rm > 15) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x43c0 | ((rm & 7) << 3) | (rd & 7)); - } - - public const ushort Nop = 0xBF00; - - public static ushort Orrs (uint rn, uint rm) - { - if (rn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4300 | ((rm & 7) << 3) | rn & 7); - } - - public static ushort Pop (bool p, uint registerList) - { - if (registerList > 255) throw new ArgumentException ("Register list too large (0-15)"); - return (ushort)(0xBC00 | (p ? 0x100u : 0u) | registerList); - } - - public static ushort Push (bool m, uint registerList) - { - if (registerList > 255) throw new ArgumentException ("Register list too large (0-15)"); - return (ushort)(0xB400 | (m ? 0x100u : 0u) | registerList); - } - - public static ushort Rev(uint rd, uint rn) - { - if (rd > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - if (rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0xBA00 | ((rn & 7) << 3) | (rd & 7)); - } - - public static uint Mrs (uint rd, uint specialRegister) - { - if (rd > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - if (specialRegister > 255) throw new ArgumentException ("Special register index out of range (0-15)"); - return 0x80000000 | (rd & 0xf) << 24 | (specialRegister & 0xFF) << 16 | 0xf3ef; - } - - public static uint Msr (uint specialRegister, uint rd) - { - if (rd > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - if (specialRegister > 255) throw new ArgumentException ("Special register index out of range (0-15)"); - return 0x88000000 | (specialRegister & 0xFF) << 16 | 0xf380 | rd & 0xf; - } - - public static ushort Ldmia (uint rn, uint registerList) - { - if (registerList > 255) throw new ArgumentException ("Register list too large (0-15)"); - if (rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0xC800 | (rn & 0x7) << 8 | registerList & 0xFF); - } - - public static ushort LslsImm5 (uint rd, uint rm, uint imm5) - { - if (rd > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - if (imm5 > 31) throw new ArgumentException ("Immediate too large for LSLS"); - return (ushort)((imm5 & 0x1F) << 6 | ((rm & 7) << 3) | (rd & 7)); - } - - public static ushort LslsRegister (uint rdn, uint rm) - { - if (rdn > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0x4080 | ((rm & 7) << 3) | rdn); - } - - public static ushort Revsh (uint rd, uint rm) - { - if (rd > 7 || rm > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0xBAC0 | ((rm & 7) << 3) | (rd & 7)); - } - - public static ushort Rev16(uint rd, uint rn) - { - if (rd > 15) throw new ArgumentException (HighRegisterIndexOutOfRange); - if (rn > 7) throw new ArgumentException (LowRegisterIndexOutOfRange); - return (ushort)(0xBA40 | ((rn & 0x7) << 3) | (rd & 0x7)); - } + const string LowRegisterIndexOutOfRange = "Register index out of range (0-7)"; + const string HighRegisterIndexOutOfRange = "Register index out of range (0-15)"; + + public static ushort Adcs(int rd, int rm) + { + if (rd > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4140 | (rm << 3) | rd); + } + + public static ushort AddSpImm7(uint imm7) + { + imm7 >>= 2; + if (imm7 > 0x7F) + throw new ArgumentException("Immediate too large for ADD SP, immediate"); + return (ushort)(0xB000 | imm7); + } + + public static ushort AddSpImm8(int rd, uint imm8) + { + imm8 >>= 2; + if (imm8 > 0xFF) + throw new ArgumentException("Immediate too large for ADD SP, immediate"); + if (rd > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)((uint)(0xA800 | rd << 8) | imm8); + } + + public static ushort AddHighRegisters(uint rdn, uint rm) + { + if (rdn > 0xf || rm > 0xf) + throw new ArgumentException("Register index out of range (0-15)"); + return (ushort)(0x4400 | (rdn & 0x8) << 4 | (rm & 0xf) << 3 | rdn & 0x7); + } + + public static ushort AddsImm3(uint rd, uint rn, uint imm3) + { + if (rd > 7 || rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm3 > 7) + throw new ArgumentException("Immediate too large for ADDS"); + return (ushort)(0x1C00 | (imm3 & 0x7) << 6 | ((rn & 0x07) << 3) | (rd & 0x07)); + } + + public static ushort AddsImm8(uint rdn, uint imm8) + { + if (rdn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm8 > 255) + throw new ArgumentException("Immediate too large for ADDS"); + return (ushort)(0x3000 | (rdn & 0x07) << 8 | (imm8 & 0xFF)); + } + + public static ushort AddsRegister(uint rd, uint rn, uint rm) + { + if (rd > 7 || rn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x1800 | ((rm & 0x07) << 6) | (rn & 0x07) << 3 | (rd & 0x07)); + } + + public static ushort Adr(uint rd, uint imm8) + { + if (rd > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm8 >> 2 > 0xFF) + throw new ArgumentException("Immediate too large"); + return (ushort)(0xA000 | (rd & 7) << 8 | imm8 >> 2 & 0xFF); + } + + public static ushort Ands(uint rn, uint rm) + { + if (rn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4000 | (rm & 7) << 3 | rn & 7); + } + + public static ushort AsrsImm5(uint rd, uint rm, uint imm5) + { + if (rd > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm5 > 31) + throw new ArgumentException("Immediate too large for ASRS"); + return (ushort)(0x1000 | (imm5 & 0x1F) << 6 | ((rm & 7) << 3) | (rd & 7)); + } + + public static ushort AsrsRegister(uint rdn, uint rm) + { + if (rdn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4100 | ((rm & 7) << 3) | rdn); + } + + public static ushort Bics(uint rdn, uint rm) + { + if (rdn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4380 | ((rm & 7) << 3) | rdn); + } + + public static uint Bl(int offset) + { + if (offset < -16777216 || offset > 16777214) + throw new ArgumentException("Offset out of range for BL (+/- 16MB)"); + var s = (uint)((offset >> 24) & 1); + var i1 = (uint)((offset >> 23) & 1); + var i2 = (uint)((offset >> 22) & 1); + var imm10 = (uint)((offset >> 12) & 0x3FF); + var imm11 = (uint)((offset >> 1) & 0x7FF); + var j1 = (~i1 ^ s) & 1; + var j2 = (~i2 ^ s) & 1; + var h1 = 0xF000 | (s << 10) | imm10; + var h2 = 0xD000 | (j1 << 13) | (j2 << 11) | imm11; + return (h2 << 16) | h1; + } + + public static ushort Blx(int rm) + { + if (rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4780 | rm << 3); + } + + public static ushort BranchConditional(uint cond, uint offset) + { + if (cond > 15) + throw new ArgumentException("Condition code out of range (0-15)"); + if (offset > 0x3FF) + throw new ArgumentException( + "Offset out of range for Conditional Branch (-256 to +254)" + ); + if (offset % 2 != 0) + throw new ArgumentException("Offset must be aligned to 2 bytes"); + return (ushort)(0xD000 | (cond & 0xF) << 8 | offset >> 1 & 0x1FF); + } + + public static ushort Branch(uint offset) + { + if (offset > 0xFFF) + throw new ArgumentException( + "Offset out of range for Unconditional Branch (-2048 to +2046)" + ); + if (offset % 2 != 0) + throw new ArgumentException("Offset must be aligned to 2 bytes"); + return (ushort)(0xE000 | ((offset >> 1) & 0x7FF)); + } + + public static ushort Bx(uint rm) + { + if (rm > 15) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4700 | rm << 3); + } + + public static ushort Cmn(uint rn, uint rm) + { + if (rn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x42c0 | ((rn & 7) << 3) | rm); + } + + public static ushort CmpImm(uint rn, uint imm8) + { + if (rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm8 > 255) + throw new ArgumentException("Immediate too large for CMP"); + return (ushort)(0x2800 | (rn & 7) << 3 | imm8 & 0xFF); + } + + public static ushort CmpRegister(uint rn, uint rm) + { + if (rn > 7 || rm > 7) + throw new ArgumentException("CMP T1 only supports Low Registers (0-7)"); + return (ushort)(0x4280 | ((rm & 0x7) << 3) | (rn & 0x7)); + } + + public static ushort CmpHighRegister(uint rn, uint rm) + { + if (rn > 15 || rm > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + var n = (rn >> 3) & 1; + return (ushort)(0x4500 | (n << 7) | ((rm & 0xF) << 3) | (rn & 0x7)); + } + + public const uint Dmb = 0x8f4ff3bfu; + + public const uint Dsb = 0x8f4ff3bf; + + public static ushort Eors(uint rdn, uint rm) + { + if (rdn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4040 | ((rm & 7) << 3) | rdn & 0x7); + } + + public const uint Isb = 0x8f6ff3bf; + + public static ushort Mov(uint rd, uint rm) + { + if (rd > 15 || rm > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + return (ushort)(0x4600 | ((rd & 8) << 4) | ((rm & 0xF) << 3) | (rd & 7)); + } + + public static ushort Movs(uint rd, uint imm8) + { + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (imm8 > 255) + throw new ArgumentException("Immediate too large for MOVS"); + return (ushort)(0x2000 | (rd & 7) << 8 | imm8 & 0xFF); + } + + public static ushort Muls(uint rn, uint rdm) + { + if (rn > 7 || rdm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4340 | ((rn & 7) << 3) | (rdm & 7)); + } + + public static ushort Mvns(uint rd, uint rm) + { + if (rd > 15 || rm > 15) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x43c0 | ((rm & 7) << 3) | (rd & 7)); + } + + public const ushort Nop = 0xBF00; + + public static ushort Orrs(uint rn, uint rm) + { + if (rn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4300 | ((rm & 7) << 3) | rn & 7); + } + + public static ushort Pop(bool p, uint registerList) + { + if (registerList > 255) + throw new ArgumentException("Register list too large (0-15)"); + return (ushort)(0xBC00 | (p ? 0x100u : 0u) | registerList); + } + + public static ushort Push(bool m, uint registerList) + { + if (registerList > 255) + throw new ArgumentException("Register list too large (0-15)"); + return (ushort)(0xB400 | (m ? 0x100u : 0u) | registerList); + } + + public static ushort Rev(uint rd, uint rn) + { + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0xBA00 | ((rn & 7) << 3) | (rd & 7)); + } + + public static uint Mrs(uint rd, uint specialRegister) + { + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (specialRegister > 255) + throw new ArgumentException("Special register index out of range (0-15)"); + return 0x80000000 | (rd & 0xf) << 24 | (specialRegister & 0xFF) << 16 | 0xf3ef; + } + + public static uint Msr(uint specialRegister, uint rd) + { + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (specialRegister > 255) + throw new ArgumentException("Special register index out of range (0-15)"); + return 0x88000000 | (specialRegister & 0xFF) << 16 | 0xf380 | rd & 0xf; + } + + public static ushort Ldmia(uint rn, uint registerList) + { + if (registerList > 255) + throw new ArgumentException("Register list too large (0-15)"); + if (rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0xC800 | (rn & 0x7) << 8 | registerList & 0xFF); + } + + public static ushort LslsImm5(uint rd, uint rm, uint imm5) + { + if (rd > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm5 > 31) + throw new ArgumentException("Immediate too large for LSLS"); + return (ushort)((imm5 & 0x1F) << 6 | ((rm & 7) << 3) | (rd & 7)); + } + + public static ushort LslsRegister(uint rdn, uint rm) + { + if (rdn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x4080 | ((rm & 7) << 3) | rdn); + } + + public static ushort Revsh(uint rd, uint rm) + { + if (rd > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0xBAC0 | ((rm & 7) << 3) | (rd & 7)); + } + + public static ushort Rev16(uint rd, uint rn) + { + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0xBA40 | ((rn & 0x7) << 3) | (rd & 0x7)); + } + + public static ushort Rsbs(uint rd, uint rn) + { + return 0; + } + + public static ushort SubSp(uint imm) + { + if (imm > 508) + throw new ArgumentOutOfRangeException( + nameof(imm), + "Immediate value must be between 0 and 508." + ); + return (ushort)(0xB080 | ((imm >> 2) & 0x7f)); + } + + public static ushort SubsImm3(uint rd, uint rn, uint imm3) + { + if (rd > 7 || rn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm3 > 7) + throw new ArgumentException("Immediate too large for SUBS"); + + return (ushort)(0x1E00 | ((imm3 & 0x7) << 6) | ((rn & 7) << 3) | (rd & 7)); + } + + public static ushort SubsImm8(uint rdn, uint imm8) + { + if (rdn > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + if (imm8 > 255) + throw new ArgumentException("Immediate too large for SUBS"); + return (ushort)(0x3800 | ((rdn & 7) << 8) | (imm8 & 0xff)); + } + + public static ushort SubsReg(uint rd, uint rn, uint rm) + { + if (rd > 7 || rn > 7 || rm > 7) + throw new ArgumentException(LowRegisterIndexOutOfRange); + return (ushort)(0x1A00 | ((rm & 0x7) << 6) | ((rn & 7) << 3) | (rd & 7)); + } } diff --git a/src/RP2040.Core/Memory/BusInterconnect.cs b/src/RP2040.Core/Memory/BusInterconnect.cs index 78ab4f6..279f3aa 100644 --- a/src/RP2040.Core/Memory/BusInterconnect.cs +++ b/src/RP2040.Core/Memory/BusInterconnect.cs @@ -5,166 +5,181 @@ namespace RP2040.Core.Memory; public unsafe class BusInterconnect : IMemoryBus, IDisposable { - public const uint REGION_BOOTROM = 0x0; - public const uint REGION_FLASH = 0x1; - public const uint REGION_SRAM = 0x2; - - public const uint MASK_SRAM = 0x7FFFF; // 512KB (covers 264KB + mirrors) - public const uint MASK_FLASH = 0x1FFFFF; // 2MB - public const uint MASK_BOOTROM = 0x3FFF; // 16KB - - public const uint SRAM_START_ADDRESS = 0x20000000; - public const uint FLASH_START_ADDRESS = 0x10000000; - - public readonly byte* PtrSram; - public readonly byte* PtrFlash; - public readonly byte* PtrBootRom; - - private readonly byte** _pageTable; - private readonly uint* _maskTable; - - private readonly IMemoryMappedDevice[] _memoryMap = new IMemoryMappedDevice[16]; - - private readonly RandomAccessMemory _sram; - private readonly RandomAccessMemory _bootRom; - private readonly RandomAccessMemory _flash; - - private bool _disposed; - - public BusInterconnect () - { - _pageTable = (byte**)NativeMemory.AllocZeroed(16, (nuint)sizeof(byte*)); - _maskTable = (uint*)NativeMemory.AllocZeroed(16, sizeof(uint)); - - _sram = new RandomAccessMemory (512 * 1024); - _flash = new RandomAccessMemory (2 * 1024 * 1024); - _bootRom = new RandomAccessMemory (16 * 1024); - - PtrSram = _sram.BasePtr; - PtrFlash = _flash.BasePtr; - PtrBootRom = _bootRom.BasePtr; - - _pageTable[REGION_BOOTROM] = PtrBootRom; - _maskTable[REGION_BOOTROM] = MASK_BOOTROM; - - _pageTable[REGION_FLASH] = PtrFlash; - _maskTable[REGION_FLASH] = MASK_FLASH; - - _pageTable[REGION_SRAM] = PtrSram; - _maskTable[REGION_SRAM] = MASK_SRAM; - - MapDevice ((int)REGION_BOOTROM, _bootRom); - MapDevice ((int)REGION_FLASH, _flash); - MapDevice ((int)REGION_SRAM, _sram); - } - - public void MapDevice (int regionIndex, IMemoryMappedDevice device) - { - if (regionIndex is < 0 or > 15) throw new ArgumentOutOfRangeException (nameof (regionIndex)); - _memoryMap[regionIndex] = device; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public byte ReadByte (uint address) - { - var region = address >> 28; - var basePtr = _pageTable[region]; - - return basePtr != null ? basePtr[address & _maskTable[region]] : ReadByteDispatch (address); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public ushort ReadHalfWord (uint address) - { - var region = address >> 28; - var basePtr = _pageTable[region]; - - return basePtr != null ? Unsafe.ReadUnaligned (basePtr + (address & _maskTable[region])) : ReadHalfWordDispatch (address); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public uint ReadWord (uint address) - { - var region = address >> 28; - var basePtr = _pageTable[region]; - - return basePtr != null ? Unsafe.ReadUnaligned (basePtr + (address & _maskTable[region])) : ReadWordDispatch (address); - - } - - // --- SLOW PATH DISPATCHERS --- - - [MethodImpl (MethodImplOptions.NoInlining)] - private byte ReadByteDispatch (uint address) - => _memoryMap[address >> 28]?.ReadByte (address & 0x0FFFFFFF) ?? 0; - - [MethodImpl (MethodImplOptions.NoInlining)] - private ushort ReadHalfWordDispatch (uint address) - => _memoryMap[address >> 28]?.ReadHalfWord (address & 0x0FFFFFFF) ?? 0; - - [MethodImpl (MethodImplOptions.NoInlining)] - private uint ReadWordDispatch (uint address) - => _memoryMap[address >> 28]?.ReadWord (address & 0x0FFFFFFF) ?? 0; - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteWord (uint address, uint value) - { - if ((address >> 28) == REGION_SRAM) { - Unsafe.WriteUnaligned (PtrSram + (address & MASK_SRAM), value); - return; - } - var region = address >> 28; - if (region == REGION_FLASH || region == REGION_BOOTROM) return; - - WriteWordDispatch (region, address, value); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteByte (uint address, byte value) - { - if ((address >> 28) == REGION_SRAM) { - PtrSram[address & MASK_SRAM] = value; - return; - } - if ((address >> 28) <= REGION_FLASH) return; // ROM(0) o FLASH(1) - _memoryMap[address >> 28]?.WriteByte (address & 0x0FFFFFFF, value); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteHalfWord (uint address, ushort value) - { - if ((address >> 28) == REGION_SRAM) { - Unsafe.WriteUnaligned (PtrSram + (address & MASK_SRAM), value); - return; - } - if ((address >> 28) <= REGION_FLASH) return; - _memoryMap[address >> 28]?.WriteHalfWord (address & 0x0FFFFFFF, value); - } - - [MethodImpl (MethodImplOptions.NoInlining)] - private void WriteWordDispatch (uint region, uint address, uint value) => _memoryMap[region]?.WriteWord (address & 0x0FFFFFFF, value); - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - if (_disposed) { - return; - } - - if (disposing) { - _sram?.Dispose (); - _flash?.Dispose (); - _bootRom?.Dispose (); - } - - if (_pageTable != null) NativeMemory.Free(_pageTable); - if (_maskTable != null) NativeMemory.Free(_maskTable); - - _disposed = true; - } + public const uint REGION_BOOTROM = 0x0; + public const uint REGION_FLASH = 0x1; + public const uint REGION_SRAM = 0x2; + + public const uint MASK_SRAM = 0x7FFFF; // 512KB (covers 264KB + mirrors) + public const uint MASK_FLASH = 0x1FFFFF; // 2MB + public const uint MASK_BOOTROM = 0x3FFF; // 16KB + + public const uint SRAM_START_ADDRESS = 0x20000000; + public const uint FLASH_START_ADDRESS = 0x10000000; + + public readonly byte* PtrSram; + public readonly byte* PtrFlash; + public readonly byte* PtrBootRom; + + private readonly byte** _pageTable; + private readonly uint* _maskTable; + + private readonly IMemoryMappedDevice[] _memoryMap = new IMemoryMappedDevice[16]; + + private readonly RandomAccessMemory _sram; + private readonly RandomAccessMemory _bootRom; + private readonly RandomAccessMemory _flash; + + private bool _disposed; + + public BusInterconnect() + { + _pageTable = (byte**)NativeMemory.AllocZeroed(16, (nuint)sizeof(byte*)); + _maskTable = (uint*)NativeMemory.AllocZeroed(16, sizeof(uint)); + + _sram = new RandomAccessMemory(512 * 1024); + _flash = new RandomAccessMemory(2 * 1024 * 1024); + _bootRom = new RandomAccessMemory(16 * 1024); + + PtrSram = _sram.BasePtr; + PtrFlash = _flash.BasePtr; + PtrBootRom = _bootRom.BasePtr; + + _pageTable[REGION_BOOTROM] = PtrBootRom; + _maskTable[REGION_BOOTROM] = MASK_BOOTROM; + + _pageTable[REGION_FLASH] = PtrFlash; + _maskTable[REGION_FLASH] = MASK_FLASH; + + _pageTable[REGION_SRAM] = PtrSram; + _maskTable[REGION_SRAM] = MASK_SRAM; + + MapDevice((int)REGION_BOOTROM, _bootRom); + MapDevice((int)REGION_FLASH, _flash); + MapDevice((int)REGION_SRAM, _sram); + } + + public void MapDevice(int regionIndex, IMemoryMappedDevice device) + { + if (regionIndex is < 0 or > 15) + throw new ArgumentOutOfRangeException(nameof(regionIndex)); + _memoryMap[regionIndex] = device; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte(uint address) + { + var region = address >> 28; + var basePtr = _pageTable[region]; + + return basePtr != null ? basePtr[address & _maskTable[region]] : ReadByteDispatch(address); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadHalfWord(uint address) + { + var region = address >> 28; + var basePtr = _pageTable[region]; + + return basePtr != null + ? Unsafe.ReadUnaligned(basePtr + (address & _maskTable[region])) + : ReadHalfWordDispatch(address); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadWord(uint address) + { + var region = address >> 28; + var basePtr = _pageTable[region]; + + return basePtr != null + ? Unsafe.ReadUnaligned(basePtr + (address & _maskTable[region])) + : ReadWordDispatch(address); + } + + // --- SLOW PATH DISPATCHERS --- + + [MethodImpl(MethodImplOptions.NoInlining)] + private byte ReadByteDispatch(uint address) => + _memoryMap[address >> 28]?.ReadByte(address & 0x0FFFFFFF) ?? 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private ushort ReadHalfWordDispatch(uint address) => + _memoryMap[address >> 28]?.ReadHalfWord(address & 0x0FFFFFFF) ?? 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private uint ReadWordDispatch(uint address) => + _memoryMap[address >> 28]?.ReadWord(address & 0x0FFFFFFF) ?? 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteWord(uint address, uint value) + { + if ((address >> 28) == REGION_SRAM) + { + Unsafe.WriteUnaligned(PtrSram + (address & MASK_SRAM), value); + return; + } + var region = address >> 28; + if (region == REGION_FLASH || region == REGION_BOOTROM) + return; + + WriteWordDispatch(region, address, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteByte(uint address, byte value) + { + if ((address >> 28) == REGION_SRAM) + { + PtrSram[address & MASK_SRAM] = value; + return; + } + if ((address >> 28) <= REGION_FLASH) + return; // ROM(0) o FLASH(1) + _memoryMap[address >> 28]?.WriteByte(address & 0x0FFFFFFF, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteHalfWord(uint address, ushort value) + { + if ((address >> 28) == REGION_SRAM) + { + Unsafe.WriteUnaligned(PtrSram + (address & MASK_SRAM), value); + return; + } + if ((address >> 28) <= REGION_FLASH) + return; + _memoryMap[address >> 28]?.WriteHalfWord(address & 0x0FFFFFFF, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void WriteWordDispatch(uint region, uint address, uint value) => + _memoryMap[region]?.WriteWord(address & 0x0FFFFFFF, value); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _sram?.Dispose(); + _flash?.Dispose(); + _bootRom?.Dispose(); + } + + if (_pageTable != null) + NativeMemory.Free(_pageTable); + if (_maskTable != null) + NativeMemory.Free(_maskTable); + + _disposed = true; + } } diff --git a/src/RP2040.Core/Memory/IMemoryBus.cs b/src/RP2040.Core/Memory/IMemoryBus.cs index 002df06..032c496 100644 --- a/src/RP2040.Core/Memory/IMemoryBus.cs +++ b/src/RP2040.Core/Memory/IMemoryBus.cs @@ -2,15 +2,15 @@ namespace RP2040.Core.Memory; public interface IMemoryBus { - // Usamos uint para direcciones (32-bit address space) + // Usamos uint para direcciones (32-bit address space) - // Read Operations - byte ReadByte (uint address); - ushort ReadHalfWord (uint address); // 16-bit - uint ReadWord (uint address); // 32-bit + // Read Operations + byte ReadByte(uint address); + ushort ReadHalfWord(uint address); // 16-bit + uint ReadWord(uint address); // 32-bit - // Write Operations - void WriteByte (uint address, byte value); - void WriteHalfWord (uint address, ushort value); - void WriteWord (uint address, uint value); + // Write Operations + void WriteByte(uint address, byte value); + void WriteHalfWord(uint address, ushort value); + void WriteWord(uint address, uint value); } diff --git a/src/RP2040.Core/Memory/IMemoryMappedDevice.cs b/src/RP2040.Core/Memory/IMemoryMappedDevice.cs index c11de39..547ac59 100644 --- a/src/RP2040.Core/Memory/IMemoryMappedDevice.cs +++ b/src/RP2040.Core/Memory/IMemoryMappedDevice.cs @@ -2,13 +2,13 @@ namespace RP2040.Core.Memory; public interface IMemoryMappedDevice { - uint Size { get; } + uint Size { get; } - byte ReadByte (uint address); - ushort ReadHalfWord (uint address); - uint ReadWord (uint address); + byte ReadByte(uint address); + ushort ReadHalfWord(uint address); + uint ReadWord(uint address); - void WriteByte (uint address, byte value); - void WriteHalfWord (uint address, ushort value); - void WriteWord (uint address, uint value); + void WriteByte(uint address, byte value); + void WriteHalfWord(uint address, ushort value); + void WriteWord(uint address, uint value); } diff --git a/src/RP2040.Core/Memory/Ram.cs b/src/RP2040.Core/Memory/Ram.cs index 9c5e445..4305043 100644 --- a/src/RP2040.Core/Memory/Ram.cs +++ b/src/RP2040.Core/Memory/Ram.cs @@ -1,57 +1,59 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + namespace RP2040.Core.Memory; public unsafe class RandomAccessMemory : IMemoryMappedDevice, IDisposable { - readonly byte[] _memory; - GCHandle _pinnedHandle; - - public readonly byte* BasePtr; - public uint Size { - get; - } - - public RandomAccessMemory (int size) - { - _memory = new byte[size]; - _pinnedHandle = GCHandle.Alloc (_memory, GCHandleType.Pinned); - BasePtr = (byte*)_pinnedHandle.AddrOfPinnedObject (); - Size = (uint)size; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public byte ReadByte (uint address) - { - return BasePtr[address]; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public ushort ReadHalfWord (uint address) - { - return Unsafe.ReadUnaligned (BasePtr + address); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public uint ReadWord (uint address) - { - return Unsafe.ReadUnaligned (BasePtr + address); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteByte (uint address, byte value) => BasePtr[address] = value; - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteHalfWord (uint address, ushort value) => Unsafe.WriteUnaligned (BasePtr + address, value); - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public void WriteWord (uint address, uint value) => Unsafe.WriteUnaligned (BasePtr + address, value); - - public void Dispose () - { - if (_pinnedHandle.IsAllocated) { - _pinnedHandle.Free (); - } - } + readonly byte[] _memory; + GCHandle _pinnedHandle; + + public readonly byte* BasePtr; + public uint Size { get; } + + public RandomAccessMemory(int size) + { + _memory = new byte[size]; + _pinnedHandle = GCHandle.Alloc(_memory, GCHandleType.Pinned); + BasePtr = (byte*)_pinnedHandle.AddrOfPinnedObject(); + Size = (uint)size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte ReadByte(uint address) + { + return BasePtr[address]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ReadHalfWord(uint address) + { + return Unsafe.ReadUnaligned(BasePtr + address); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint ReadWord(uint address) + { + return Unsafe.ReadUnaligned(BasePtr + address); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteByte(uint address, byte value) => BasePtr[address] = value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteHalfWord(uint address, ushort value) => + Unsafe.WriteUnaligned(BasePtr + address, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteWord(uint address, uint value) => + Unsafe.WriteUnaligned(BasePtr + address, value); + + public void Dispose() + { + if (_pinnedHandle.IsAllocated) + { + _pinnedHandle.Free(); + } + } } diff --git a/src/RP2040.Core/RP2040.Core.csproj b/src/RP2040.Core/RP2040.Core.csproj index a6834ea..487ab0f 100644 --- a/src/RP2040.Core/RP2040.Core.csproj +++ b/src/RP2040.Core/RP2040.Core.csproj @@ -1,15 +1,13 @@  - - - net10.0 - enable - enable - true - true - true - true - true - embedded - - + + net10.0 + enable + enable + true + true + true + true + true + embedded + diff --git a/src/RP2040.Peripherals/RP2040.Peripherals.csproj b/src/RP2040.Peripherals/RP2040.Peripherals.csproj index 237d661..b7eb9cb 100644 --- a/src/RP2040.Peripherals/RP2040.Peripherals.csproj +++ b/src/RP2040.Peripherals/RP2040.Peripherals.csproj @@ -1,9 +1,7 @@  - - - net10.0 - enable - enable - - + + net10.0 + enable + enable + diff --git a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs index 04783a0..51ec962 100644 --- a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs @@ -8,123 +8,162 @@ namespace RP2040.tests.Cpu; public unsafe class InstructionDecoderTests { - const int R0 = 0; - const int R1 = 1; - const int R2 = 2; - const int R3 = 3; - const int R4 = 4; - const int R5 = 5; - const int R6 = 6; - const int R7 = 7; - const int R8 = 8; - const int R9 = 9; - const int R10 = 10; - const int R11 = 11; - - const int IP = 12; - const int SP = 13; - const int LR = 14; - const int PC = 15; - - static ulong AddressOf (InstructionHandler handler) => (ulong)handler; - static readonly InstructionDecoder Decoder = InstructionDecoder.Instance; - - [Theory] - [MemberData (nameof (GetInstructionTestCases))] - public void ShouldMapCorrectly (string name, ushort opcode, ulong expectedHandlerAddress) - { - // Act - var actualHandler = (ulong)Decoder.GetHandler (opcode); - - // Assert - actualHandler.Should ().Be (expectedHandlerAddress, $"The instruction '{name}' should decode correctly"); - } - - public static TheoryData GetInstructionTestCases () - { - var cases = new TheoryData (); - - // --- Arithmetic Operations --- - Add ("Adcs", InstructionEmiter.Adcs (R4, R4), &ArithmeticOps.Adcs); - Add ("AddSpImm7", InstructionEmiter.AddSpImm7 (0x10), &ArithmeticOps.AddSpImmediate7); - Add ("AddSpImm8", InstructionEmiter.AddSpImm8 (R1, 0x10), &ArithmeticOps.AddSpImmediate8); - Add ("AddsImm3", InstructionEmiter.AddsImm3 (R1, R2, 3), &ArithmeticOps.AddsImmediate3); - Add ("AddsImm8", InstructionEmiter.AddsImm8 (R1, 1), &ArithmeticOps.AddsImmediate8); - Add ("AddsRegister", InstructionEmiter.AddsRegister (R1, R2, R7), &ArithmeticOps.AddsRegister); - Add ("Adr", InstructionEmiter.Adr (R4, 0x50), &ArithmeticOps.Adr); - Add("SubsRegister", InstructionEmiter.SubsReg(R1, R2, R4), &ArithmeticOps.SubsRegister); - Add("SubsImm3", InstructionEmiter.SubsImm3(R1, R2, 3), &ArithmeticOps.SubsImmediate3); - Add("SubsImm8", InstructionEmiter.SubsImm8(R1, 0x10), &ArithmeticOps.SubsImmediate8); - Add("SubSp", InstructionEmiter.SubSp(0x10), &ArithmeticOps.SubSp); - - // Special Cases for AddHighRegister - Add ("AddHighReg (Reg)", InstructionEmiter.AddHighRegisters (R1, R2), &ArithmeticOps.AddHighToReg); - Add ("AddHighReg (Sp)", InstructionEmiter.AddHighRegisters (SP, R2), &ArithmeticOps.AddHighToSp); - Add ("AddHighReg (Pc)", InstructionEmiter.AddHighRegisters (PC, R2), &ArithmeticOps.AddHighToPc); - - Add ("Cmn", InstructionEmiter.Cmn (R7, R2), &ArithmeticOps.Cmn); - Add ("CmpImm", InstructionEmiter.CmpImm (R5, 66), &ArithmeticOps.CmpImmediate); - Add ("CmpRegister", InstructionEmiter.CmpRegister (R5, R0), &ArithmeticOps.CmpRegister); - Add ("CmpHighRegister", InstructionEmiter.CmpHighRegister (R11, R3), &ArithmeticOps.CmpHighRegister); - Add ("Muls", InstructionEmiter.Muls (R0, R2), &ArithmeticOps.Muls); - - // --- Bit Operations --- - Add ("Ands", InstructionEmiter.Ands (R5, R0), &BitOps.Ands); - Add ("AsrsImm5", InstructionEmiter.AsrsImm5 (R3, R2, 31), &BitOps.AsrsImm5); - Add ("AsrsRegister", InstructionEmiter.AsrsRegister (R3, R4), &BitOps.AsrsRegister); - Add ("Bics", InstructionEmiter.Bics (R0, R3), &BitOps.Bics); - Add ("Eors", InstructionEmiter.Eors (R1, R3), &BitOps.Eors); - Add ("LslsImm", InstructionEmiter.LslsImm5 (R5, R5, 18), &BitOps.LslsImm5); - Add ("LslsImmZero", InstructionEmiter.LslsImm5 (R5, R5, 0), &BitOps.LslsZero); - Add ("LslsRegister", InstructionEmiter.LslsRegister (R5, R0), &BitOps.LslsRegister); - Add ("Mvns", InstructionEmiter.Mvns (R0, R2), &BitOps.Mvns); - Add ("Orrs", InstructionEmiter.Orrs (R5, R0), &BitOps.Orrs); - Add ("Rev", InstructionEmiter.Rev (R0, R1), &BitOps.Rev); - Add ("Revsh", InstructionEmiter.Revsh (R0, R1), &BitOps.Revsh); - - // Mov Variations - Add ("Mov (Reg)", InstructionEmiter.Mov (R3, R8), &BitOps.MovRegister); - Add ("Mov (Pc)", InstructionEmiter.Mov (PC, R8), &BitOps.MovToPc); - Add ("Mov (Sp)", InstructionEmiter.Mov (SP, R8), &BitOps.MovToSp); - - // --- Flow Control --- - for (uint cond = 0; cond <= 13; cond++) { - InstructionHandler expected = cond switch { - 0x0 => &FlowOps.Beq, 0x1 => &FlowOps.Bne, 0x2 => &FlowOps.Bcs, 0x3 => &FlowOps.Bcc, - 0x4 => &FlowOps.Bmi, 0x5 => &FlowOps.Bpl, 0x6 => &FlowOps.Bvs, 0x7 => &FlowOps.Bvc, - 0x8 => &FlowOps.Bhi, 0x9 => &FlowOps.Bls, 0xA => &FlowOps.Bge, 0xB => &FlowOps.Blt, - 0xC => &FlowOps.Bgt, 0xD => &FlowOps.Ble, - _ => throw new System.Exception () - }; - Add ($"BranchConditional ({cond})", InstructionEmiter.BranchConditional (cond, 0), expected); - } - - Add ("Bl", (ushort)(InstructionEmiter.Bl (0x34) & 0xFFFF), &FlowOps.Bl); - Add ("Blx", InstructionEmiter.Blx (R3), &FlowOps.Blx); - Add ("Branch", InstructionEmiter.Branch (0xfec), &FlowOps.Branch); - Add ("Bx", InstructionEmiter.Bx (LR), &FlowOps.Bx); - - // --- System & Memory --- - Add ("Dmb", (ushort)(InstructionEmiter.Dmb & 0xFFFF), &SystemOps.Barrier); - Add ("Dsb", (ushort)(InstructionEmiter.Dsb & 0xFFFF), &SystemOps.Barrier); - Add ("Isb", (ushort)(InstructionEmiter.Isb & 0xFFFF), &SystemOps.Barrier); - Add ("Nop", InstructionEmiter.Nop, &SystemOps.Nop); - Add ("Mrs", (ushort)(InstructionEmiter.Mrs (R0, 5) & 0xFFFF), &SystemOps.Mrs); - Add ("Msr", (ushort)(InstructionEmiter.Msr (8, R0) & 0xFFFF), &SystemOps.Msr); - - Add ("Ldmia", InstructionEmiter.Ldmia (R0, (1 << R1) | (1 << R2)), &MemoryOps.Ldmia); - - // Push / Pop - Add ("Pop", InstructionEmiter.Pop (false, (1 << R4)), &MemoryOps.Pop); - Add ("Pop (PC)", InstructionEmiter.Pop (true, (1 << R4)), &MemoryOps.PopPc); - Add ("Push", InstructionEmiter.Push (false, (1 << R4)), &MemoryOps.Push); - Add ("Push (LR)", InstructionEmiter.Push (true, (1 << R4)), &MemoryOps.PushLr); - - return cases; - - void Add (string name, ushort opcode, InstructionHandler handler) - { - cases.Add (name, opcode, AddressOf (handler)); - } - } + const int R0 = 0; + const int R1 = 1; + const int R2 = 2; + const int R3 = 3; + const int R4 = 4; + const int R5 = 5; + const int R6 = 6; + const int R7 = 7; + const int R8 = 8; + const int R9 = 9; + const int R10 = 10; + const int R11 = 11; + + const int IP = 12; + const int SP = 13; + const int LR = 14; + const int PC = 15; + + static ulong AddressOf(InstructionHandler handler) => (ulong)handler; + + static readonly InstructionDecoder Decoder = InstructionDecoder.Instance; + + [Theory] + [MemberData(nameof(GetInstructionTestCases))] + public void ShouldMapCorrectly(string name, ushort opcode, ulong expectedHandlerAddress) + { + // Act + var actualHandler = (ulong)Decoder.GetHandler(opcode); + + // Assert + actualHandler + .Should() + .Be(expectedHandlerAddress, $"The instruction '{name}' should decode correctly"); + } + + public static TheoryData GetInstructionTestCases() + { + var cases = new TheoryData(); + + // --- Arithmetic Operations --- + Add("Adcs", InstructionEmiter.Adcs(R4, R4), &ArithmeticOps.Adcs); + Add("AddSpImm7", InstructionEmiter.AddSpImm7(0x10), &ArithmeticOps.AddSpImmediate7); + Add("AddSpImm8", InstructionEmiter.AddSpImm8(R1, 0x10), &ArithmeticOps.AddSpImmediate8); + Add("AddsImm3", InstructionEmiter.AddsImm3(R1, R2, 3), &ArithmeticOps.AddsImmediate3); + Add("AddsImm8", InstructionEmiter.AddsImm8(R1, 1), &ArithmeticOps.AddsImmediate8); + Add( + "AddsRegister", + InstructionEmiter.AddsRegister(R1, R2, R7), + &ArithmeticOps.AddsRegister + ); + Add("Adr", InstructionEmiter.Adr(R4, 0x50), &ArithmeticOps.Adr); + Add("SubsRegister", InstructionEmiter.SubsReg(R1, R2, R4), &ArithmeticOps.SubsRegister); + Add("SubsImm3", InstructionEmiter.SubsImm3(R1, R2, 3), &ArithmeticOps.SubsImmediate3); + Add("SubsImm8", InstructionEmiter.SubsImm8(R1, 0x10), &ArithmeticOps.SubsImmediate8); + Add("SubSp", InstructionEmiter.SubSp(0x10), &ArithmeticOps.SubSp); + + // Special Cases for AddHighRegister + Add( + "AddHighReg (Reg)", + InstructionEmiter.AddHighRegisters(R1, R2), + &ArithmeticOps.AddHighToReg + ); + Add( + "AddHighReg (Sp)", + InstructionEmiter.AddHighRegisters(SP, R2), + &ArithmeticOps.AddHighToSp + ); + Add( + "AddHighReg (Pc)", + InstructionEmiter.AddHighRegisters(PC, R2), + &ArithmeticOps.AddHighToPc + ); + + Add("Cmn", InstructionEmiter.Cmn(R7, R2), &ArithmeticOps.Cmn); + Add("CmpImm", InstructionEmiter.CmpImm(R5, 66), &ArithmeticOps.CmpImmediate); + Add("CmpRegister", InstructionEmiter.CmpRegister(R5, R0), &ArithmeticOps.CmpRegister); + Add( + "CmpHighRegister", + InstructionEmiter.CmpHighRegister(R11, R3), + &ArithmeticOps.CmpHighRegister + ); + Add("Muls", InstructionEmiter.Muls(R0, R2), &ArithmeticOps.Muls); + + // --- Bit Operations --- + Add("Ands", InstructionEmiter.Ands(R5, R0), &BitOps.Ands); + Add("AsrsImm5", InstructionEmiter.AsrsImm5(R3, R2, 31), &BitOps.AsrsImm5); + Add("AsrsRegister", InstructionEmiter.AsrsRegister(R3, R4), &BitOps.AsrsRegister); + Add("Bics", InstructionEmiter.Bics(R0, R3), &BitOps.Bics); + Add("Eors", InstructionEmiter.Eors(R1, R3), &BitOps.Eors); + Add("LslsImm", InstructionEmiter.LslsImm5(R5, R5, 18), &BitOps.LslsImm5); + Add("LslsImmZero", InstructionEmiter.LslsImm5(R5, R5, 0), &BitOps.LslsZero); + Add("LslsRegister", InstructionEmiter.LslsRegister(R5, R0), &BitOps.LslsRegister); + Add("Mvns", InstructionEmiter.Mvns(R0, R2), &BitOps.Mvns); + Add("Orrs", InstructionEmiter.Orrs(R5, R0), &BitOps.Orrs); + Add("Rev", InstructionEmiter.Rev(R0, R1), &BitOps.Rev); + Add("Revsh", InstructionEmiter.Revsh(R0, R1), &BitOps.Revsh); + + // Mov Variations + Add("Mov (Reg)", InstructionEmiter.Mov(R3, R8), &BitOps.MovRegister); + Add("Mov (Pc)", InstructionEmiter.Mov(PC, R8), &BitOps.MovToPc); + Add("Mov (Sp)", InstructionEmiter.Mov(SP, R8), &BitOps.MovToSp); + + // --- Flow Control --- + for (uint cond = 0; cond <= 13; cond++) + { + InstructionHandler expected = cond switch + { + 0x0 => &FlowOps.Beq, + 0x1 => &FlowOps.Bne, + 0x2 => &FlowOps.Bcs, + 0x3 => &FlowOps.Bcc, + 0x4 => &FlowOps.Bmi, + 0x5 => &FlowOps.Bpl, + 0x6 => &FlowOps.Bvs, + 0x7 => &FlowOps.Bvc, + 0x8 => &FlowOps.Bhi, + 0x9 => &FlowOps.Bls, + 0xA => &FlowOps.Bge, + 0xB => &FlowOps.Blt, + 0xC => &FlowOps.Bgt, + 0xD => &FlowOps.Ble, + _ => throw new System.Exception(), + }; + Add( + $"BranchConditional ({cond})", + InstructionEmiter.BranchConditional(cond, 0), + expected + ); + } + + Add("Bl", (ushort)(InstructionEmiter.Bl(0x34) & 0xFFFF), &FlowOps.Bl); + Add("Blx", InstructionEmiter.Blx(R3), &FlowOps.Blx); + Add("Branch", InstructionEmiter.Branch(0xfec), &FlowOps.Branch); + Add("Bx", InstructionEmiter.Bx(LR), &FlowOps.Bx); + + // --- System & Memory --- + Add("Dmb", (ushort)(InstructionEmiter.Dmb & 0xFFFF), &SystemOps.Barrier); + Add("Dsb", (ushort)(InstructionEmiter.Dsb & 0xFFFF), &SystemOps.Barrier); + Add("Isb", (ushort)(InstructionEmiter.Isb & 0xFFFF), &SystemOps.Barrier); + Add("Nop", InstructionEmiter.Nop, &SystemOps.Nop); + Add("Mrs", (ushort)(InstructionEmiter.Mrs(R0, 5) & 0xFFFF), &SystemOps.Mrs); + Add("Msr", (ushort)(InstructionEmiter.Msr(8, R0) & 0xFFFF), &SystemOps.Msr); + + Add("Ldmia", InstructionEmiter.Ldmia(R0, (1 << R1) | (1 << R2)), &MemoryOps.Ldmia); + + // Push / Pop + Add("Pop", InstructionEmiter.Pop(false, (1 << R4)), &MemoryOps.Pop); + Add("Pop (PC)", InstructionEmiter.Pop(true, (1 << R4)), &MemoryOps.PopPc); + Add("Push", InstructionEmiter.Push(false, (1 << R4)), &MemoryOps.Push); + Add("Push (LR)", InstructionEmiter.Push(true, (1 << R4)), &MemoryOps.PushLr); + + return cases; + + void Add(string name, ushort opcode, InstructionHandler handler) + { + cases.Add(name, opcode, AddressOf(handler)); + } + } } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs index 7d4770d..dc01857 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -6,822 +6,843 @@ namespace RP2040.tests.Cpu.Instructions; public abstract class ArithmeticOpsTests { - public class Adcs : CpuTestBase - { - [Fact] - public void Should_AddTwoRegisters_IncludingCarry () - { - // Arrange - var opcode = InstructionEmiter.Adcs (R5, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R4] = 55; - Cpu.Registers[R5] = 66; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (122u); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetNegativeAndOverflow_When_AddingMaxPositiveWithCarry () - { - // Arrange - var opcode = InstructionEmiter.Adcs (R5, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R4] = 0x7fffffff; - Cpu.Registers[R5] = 0; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0x80000000u); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_PropagateCarry_WithoutSettingOverflow_When_OperandsAreZero () - { - // Arrange - var opcode = InstructionEmiter.Adcs (R3, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 0; - Cpu.Registers[R3] = 0; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (1u); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetZeroCarryAndOverflow_When_SelfAddingMinNegative () - { - // Arrange - var opcode = InstructionEmiter.Adcs (R0, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0x80000000; - Cpu.Registers.C = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeTrue (); - } - } - - public abstract class Add - { - public class SpRelative : CpuTestBase - { - [Fact] - public void Should_IncrementStackPointer_ByImmediate7 () - { - // Arrange - var opcode = InstructionEmiter.AddSpImm7 (0x10); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers.SP = 0x10000040; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (0x10000050); - } - - [Fact] - public void Should_CalculateAddressFromStackPointer_AndStoreInRegister () - { - // Arrange - var opcode = InstructionEmiter.AddSpImm8 (R1, 0x10); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers.SP = 0x54; - Cpu.Registers[R1] = 0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (0x54); - Cpu.Registers[R1].Should ().Be (0x64); - } - } - - public class Register : CpuTestBase - { - [Fact] - public void Should_AddHighRegister_To_LowRegister () - { - // Arrange - var opcode = InstructionEmiter.AddHighRegisters (R1, IP); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 66; - Cpu.Registers[R12] = 44; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (110); - } - - [Fact] - public void Should_NotUpdateFlags_When_ResultIsZero () - { - // Arrange - var opcode = InstructionEmiter.AddHighRegisters (R3, R12); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x00002000; - Cpu.Registers[R12] = 0xffffe000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0x00000000); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_AddRegisterToStackPointer_And_PreserveFlags () - { - // Arrange - var opcode = InstructionEmiter.AddHighRegisters (SP, R8); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[SP] = 0x20030000; - Cpu.Registers.Z = true; - Cpu.Registers[R8] = 0x13; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (0x20030010); - Cpu.Registers.Z.Should ().BeTrue (); - } - - [Fact] - public void Should_AddRegisterToProgramCounter_And_AlignResult () - { - // Arrange - var opcode = InstructionEmiter.AddHighRegisters (PC, R8); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R8] = 0x11; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[PC].Should ().Be (0x20000014); - } - - [Fact] - public void Should_PreserveFlags_When_UsingEncodingT2_WithLowRegisters () - { - // Arrange - var opcode = InstructionEmiter.AddHighRegisters (R1, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 10; - Cpu.Registers[R2] = 20; - - Cpu.Registers.N = true; - Cpu.Registers.Z = true; - Cpu.Registers.C = true; - Cpu.Registers.V = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (30); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeTrue (); - } - } - } - - public abstract class Adds - { - public class Immediate3 : CpuTestBase - { - [Fact] - public void Should_AddSmallImmediate_And_ClearFlags () - { - // Arrange - var opcode = InstructionEmiter.AddsImm3 (R1, R2, 3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 2; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (5); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - public class Immediate8 : CpuTestBase - { - [Fact] - public void Should_AddLargeImmediate_And_SetZeroAndCarryFlags_OnUnsignedOverflow () - { - // Arrange - var opcode = InstructionEmiter.AddsImm8 (R1, 1); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 0xffffffff; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (0); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - public class Register : CpuTestBase - { - [Fact] - public void Should_AddTwoRegisters_StandardCase () - { - // Arrange - var opcode = InstructionEmiter.AddsRegister (R1, R2, R7); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 2; - Cpu.Registers[R7] = 27; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (29); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetOverflowFlag_When_AddingTwoPositiveNumbers_ResultsInNegative () - { - // Arrange - var opcode = InstructionEmiter.AddsRegister (R4, R4, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 0x74bc8000; - Cpu.Registers[R4] = 0x43740000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R4].Should ().Be (0xb8308000); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_GenerateCarryAndOverflow_OnSelfAddition () - { - // Arrange - var opcode = InstructionEmiter.AddsRegister (R1, R1, R1); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 0xbf8d1424; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (0x7f1a2848); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeTrue (); - } - } - } - - public class Adr : CpuTestBase - { - [Fact] - public void Should_CalculateAddress_RelativeToProgramCounter () - { - // Arrange - var opcode = InstructionEmiter.Adr (R4, 0x50); - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R4].Should ().Be (0x20000054); - } - } - - public class Cmn : CpuTestBase - { - [Fact] - public void Should_UpdateFlags_And_DiscardResult_When_AddingTwoRegisters () - { - // Arrange - const uint negativeTwo = (uint)(-2 & 0xFFFFFFFF); - var opcode = InstructionEmiter.Cmn (R7, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 1; - Cpu.Registers[R7] = negativeTwo; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be (1); - Cpu.Registers[R7].Should ().Be (negativeTwo); - - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - public abstract class Cmp - { - public class Immediate : CpuTestBase - { - [Fact] - public void Should_SetNegativeFlag_When_RegisterIsLessThanImmediate () - { - // Arrange - var opcode = InstructionEmiter.CmpImm (R5, 66); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 60; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetCarryFlag_When_RegisterIsGreaterOrEqual_ToImmediate () - { - // Arrange - var opcode = InstructionEmiter.CmpImm (R0, 0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0x80010133; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - public class Register : CpuTestBase - { - [Fact] - public void Should_SetCarryFlag_When_RnIsGreaterThanRm () - { - // Arrange - var opcode = InstructionEmiter.CmpRegister (R5, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 56; - Cpu.Registers[R5] = 60; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_ClearCarryFlag_When_UnsignedBorrowOccurs () - { - // Arrange - var opcode = InstructionEmiter.CmpRegister (R2, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xb71b0000; - Cpu.Registers[R2] = 0x00b71b00; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetOverflowFlag_When_SubtractingMinNegativeFromZero () - { - // Arrange - var opcode = InstructionEmiter.CmpRegister (R3, R7); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0; - Cpu.Registers[R7] = 0x80000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_SetCarryFlag_When_SubtractingZeroFromMinNegative () - { - // Arrange - var opcode = InstructionEmiter.CmpRegister (R3, R7); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x80000000; - Cpu.Registers[R7] = 0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - public class HighRegister : CpuTestBase - { - [Fact] - public void Should_CompareHighAndLowRegisters_And_SetCarry () - { - // Arrange - var opcode = InstructionEmiter.CmpHighRegister (R11, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x00000008; - Cpu.Registers[R11] = 0xffffffff; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_CompareTwoHighRegisters_WithPositiveResult () - { - // Arrange - var opcode = InstructionEmiter.CmpHighRegister (IP, R6); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R6] = 56; - Cpu.Registers[R12] = 60; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetCarryFlag_When_HighRegisterIsNegative_And_ComparedWithZero () - { - // Arrange - var opcode = InstructionEmiter.CmpHighRegister (R11, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0; - Cpu.Registers[R11] = 0x80000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_SetOverflowFlag_When_ZeroIsComparedWith_HighRegisterNegative () - { - // Arrange - var opcode = InstructionEmiter.CmpHighRegister (R11, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x80000000; - Cpu.Registers[R11] = 0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeTrue (); - } - } - } - - public class Muls : CpuTestBase - { - [Fact] - public void Should_MultiplyTwoRegisters_And_StoreResult () - { - // Arrange - var opcode = InstructionEmiter.Muls (R0, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 5; - Cpu.Registers[R2] = 1000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be (5000000); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - } - - [Fact] - public void Should_WrapAround_When_ResultExceeds32Bits () - { - // Arrange - var opcode = InstructionEmiter.Muls (R0, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 2654435769; - Cpu.Registers[R2] = 340573321; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be (1); - } - - [Fact] - public void Should_SetZeroFlag_When_MultiplyingByZero () - { - // Arrange - var opcode = InstructionEmiter.Muls (R0, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0; - Cpu.Registers[R2] = 1000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be (0); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - } - - [Fact] - public void Should_SetNegativeFlag_When_ResultIsInterpretedAsSignedNegative () - { - // Arrange - var opcode = InstructionEmiter.Muls (R0, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xFFFFFFFF; - Cpu.Registers[R2] = 1000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be ((uint)(-1000000 & 0xFFFFFFFF)); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public class Sub : CpuTestBase - { - [Fact] - public void Should_Execute_SubSpInstruction() - { - // Arrange - var opcode = InstructionEmiter.SubSp(0x10); - Cpu.Registers.SP = 0x10000040; - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - Cpu.Step(); - - // Assert - Cpu.Registers.SP.Should ().Be (0x10000030); - } - } - - public class Subs : CpuTestBase - { - [Fact] - public void Should_Execute_SubsR1_With_Overflow () - { - // Arrange - var opcode = InstructionEmiter.SubsImm8 (R1, 1); - Cpu.Registers[R1] = (uint)(-0x80000000 & uint.MaxValue); - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (0x7fffffff); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_Execute_SubsR5R3 () - { - // Arrange - var opcode = InstructionEmiter.SubsImm3 (R5, R3, 5); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R3 = 0; - - // Act - Cpu.Step(); - - // Assert - Cpu.Registers[R5].Should ().Be ((uint)(-5 & uint.MaxValue)); - Cpu.Registers.N.Should ().BeTrue(); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_Execute_SubsR5R3R2() - { - // Arrange - var opcode = InstructionEmiter.SubsReg (R5, R3, R2); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R3 = 6; - Cpu.Registers.R2 = 5; - - // Act - Cpu.Step(); - - // Assert - Cpu.Registers.R5.Should ().Be (1); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_Execute_SubsR3R3R2() - { - // Arrange - var opcode = InstructionEmiter.SubsReg (R3, R3, R2); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R2 = 8; - Cpu.Registers.R3 = 0xffffffff; - - // Act - Cpu.Step (); - - // Arrange - Cpu.Registers.R3.Should ().Be (0xfffffff7); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - - [Fact] - public void Should_Execute_SubsR5R3R2_And_Set_NVFlags () - { - // Arrange - var opcode = InstructionEmiter.SubsReg (R5, R3, R2); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R3 = 0; - Cpu.Registers.R2 = 0x80000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.R5.Should ().Be (0x80000000); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_Execute_SubsR5R3R2_And_Set_ZCFlags () - { - // Arrange - var opcode = InstructionEmiter.SubsReg (R3, R3, R2); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R3 = 0x80000000; - Cpu.Registers.R2 = 0x80000000; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.R5.Should ().Be (0); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeFalse (); - } - } - - + public class Adcs : CpuTestBase + { + [Fact] + public void Should_AddTwoRegisters_IncludingCarry() + { + // Arrange + var opcode = InstructionEmiter.Adcs(R5, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R4] = 55; + Cpu.Registers[R5] = 66; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(122u); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetNegativeAndOverflow_When_AddingMaxPositiveWithCarry() + { + // Arrange + var opcode = InstructionEmiter.Adcs(R5, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R4] = 0x7fffffff; + Cpu.Registers[R5] = 0; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0x80000000u); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_PropagateCarry_WithoutSettingOverflow_When_OperandsAreZero() + { + // Arrange + var opcode = InstructionEmiter.Adcs(R3, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 0; + Cpu.Registers[R3] = 0; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(1u); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetZeroCarryAndOverflow_When_SelfAddingMinNegative() + { + // Arrange + var opcode = InstructionEmiter.Adcs(R0, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0x80000000; + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeTrue(); + } + } + + public abstract class Add + { + public class SpRelative : CpuTestBase + { + [Fact] + public void Should_IncrementStackPointer_ByImmediate7() + { + // Arrange + var opcode = InstructionEmiter.AddSpImm7(0x10); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers.SP = 0x10000040; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x10000050); + } + + [Fact] + public void Should_CalculateAddressFromStackPointer_AndStoreInRegister() + { + // Arrange + var opcode = InstructionEmiter.AddSpImm8(R1, 0x10); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers.SP = 0x54; + Cpu.Registers[R1] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x54); + Cpu.Registers[R1].Should().Be(0x64); + } + } + + public class Register : CpuTestBase + { + [Fact] + public void Should_AddHighRegister_To_LowRegister() + { + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(R1, IP); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 66; + Cpu.Registers[R12] = 44; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(110); + } + + [Fact] + public void Should_NotUpdateFlags_When_ResultIsZero() + { + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(R3, R12); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x00002000; + Cpu.Registers[R12] = 0xffffe000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0x00000000); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_AddRegisterToStackPointer_And_PreserveFlags() + { + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(SP, R8); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[SP] = 0x20030000; + Cpu.Registers.Z = true; + Cpu.Registers[R8] = 0x13; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(0x20030010); + Cpu.Registers.Z.Should().BeTrue(); + } + + [Fact] + public void Should_AddRegisterToProgramCounter_And_AlignResult() + { + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(PC, R8); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R8] = 0x11; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[PC].Should().Be(0x20000014); + } + + [Fact] + public void Should_PreserveFlags_When_UsingEncodingT2_WithLowRegisters() + { + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(R1, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 10; + Cpu.Registers[R2] = 20; + + Cpu.Registers.N = true; + Cpu.Registers.Z = true; + Cpu.Registers.C = true; + Cpu.Registers.V = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(30); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeTrue(); + } + } + } + + public abstract class Adds + { + public class Immediate3 : CpuTestBase + { + [Fact] + public void Should_AddSmallImmediate_And_ClearFlags() + { + // Arrange + var opcode = InstructionEmiter.AddsImm3(R1, R2, 3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 2; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(5); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public class Immediate8 : CpuTestBase + { + [Fact] + public void Should_AddLargeImmediate_And_SetZeroAndCarryFlags_OnUnsignedOverflow() + { + // Arrange + var opcode = InstructionEmiter.AddsImm8(R1, 1); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 0xffffffff; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public class Register : CpuTestBase + { + [Fact] + public void Should_AddTwoRegisters_StandardCase() + { + // Arrange + var opcode = InstructionEmiter.AddsRegister(R1, R2, R7); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 2; + Cpu.Registers[R7] = 27; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(29); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetOverflowFlag_When_AddingTwoPositiveNumbers_ResultsInNegative() + { + // Arrange + var opcode = InstructionEmiter.AddsRegister(R4, R4, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 0x74bc8000; + Cpu.Registers[R4] = 0x43740000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R4].Should().Be(0xb8308000); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_GenerateCarryAndOverflow_OnSelfAddition() + { + // Arrange + var opcode = InstructionEmiter.AddsRegister(R1, R1, R1); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 0xbf8d1424; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(0x7f1a2848); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeTrue(); + } + } + } + + public class Adr : CpuTestBase + { + [Fact] + public void Should_CalculateAddress_RelativeToProgramCounter() + { + // Arrange + var opcode = InstructionEmiter.Adr(R4, 0x50); + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R4].Should().Be(0x20000054); + } + } + + public class Cmn : CpuTestBase + { + [Fact] + public void Should_UpdateFlags_And_DiscardResult_When_AddingTwoRegisters() + { + // Arrange + const uint negativeTwo = (uint)(-2 & 0xFFFFFFFF); + var opcode = InstructionEmiter.Cmn(R7, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 1; + Cpu.Registers[R7] = negativeTwo; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2].Should().Be(1); + Cpu.Registers[R7].Should().Be(negativeTwo); + + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public abstract class Cmp + { + public class Immediate : CpuTestBase + { + [Fact] + public void Should_SetNegativeFlag_When_RegisterIsLessThanImmediate() + { + // Arrange + var opcode = InstructionEmiter.CmpImm(R5, 66); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 60; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetCarryFlag_When_RegisterIsGreaterOrEqual_ToImmediate() + { + // Arrange + var opcode = InstructionEmiter.CmpImm(R0, 0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0x80010133; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public class Register : CpuTestBase + { + [Fact] + public void Should_SetCarryFlag_When_RnIsGreaterThanRm() + { + // Arrange + var opcode = InstructionEmiter.CmpRegister(R5, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 56; + Cpu.Registers[R5] = 60; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_ClearCarryFlag_When_UnsignedBorrowOccurs() + { + // Arrange + var opcode = InstructionEmiter.CmpRegister(R2, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xb71b0000; + Cpu.Registers[R2] = 0x00b71b00; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetOverflowFlag_When_SubtractingMinNegativeFromZero() + { + // Arrange + var opcode = InstructionEmiter.CmpRegister(R3, R7); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0; + Cpu.Registers[R7] = 0x80000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_SetCarryFlag_When_SubtractingZeroFromMinNegative() + { + // Arrange + var opcode = InstructionEmiter.CmpRegister(R3, R7); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x80000000; + Cpu.Registers[R7] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public class HighRegister : CpuTestBase + { + [Fact] + public void Should_CompareHighAndLowRegisters_And_SetCarry() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(R11, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x00000008; + Cpu.Registers[R11] = 0xffffffff; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_CompareTwoHighRegisters_WithPositiveResult() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(IP, R6); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R6] = 56; + Cpu.Registers[R12] = 60; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetCarryFlag_When_HighRegisterIsNegative_And_ComparedWithZero() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(R11, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0; + Cpu.Registers[R11] = 0x80000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_SetOverflowFlag_When_ZeroIsComparedWith_HighRegisterNegative() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(R11, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x80000000; + Cpu.Registers[R11] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeTrue(); + } + } + } + + public class Muls : CpuTestBase + { + [Fact] + public void Should_MultiplyTwoRegisters_And_StoreResult() + { + // Arrange + var opcode = InstructionEmiter.Muls(R0, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 5; + Cpu.Registers[R2] = 1000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2].Should().Be(5000000); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + } + + [Fact] + public void Should_WrapAround_When_ResultExceeds32Bits() + { + // Arrange + var opcode = InstructionEmiter.Muls(R0, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 2654435769; + Cpu.Registers[R2] = 340573321; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2].Should().Be(1); + } + + [Fact] + public void Should_SetZeroFlag_When_MultiplyingByZero() + { + // Arrange + var opcode = InstructionEmiter.Muls(R0, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0; + Cpu.Registers[R2] = 1000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2].Should().Be(0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + } + + [Fact] + public void Should_SetNegativeFlag_When_ResultIsInterpretedAsSignedNegative() + { + // Arrange + var opcode = InstructionEmiter.Muls(R0, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xFFFFFFFF; + Cpu.Registers[R2] = 1000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2].Should().Be((uint)(-1000000 & 0xFFFFFFFF)); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public class Rsbs : CpuTestBase + { + [Fact] + public void Should_NegateRegisterAndSetFlags_When_SubtractingFromZero() + { + // Arrange + var opcode = InstructionEmiter.Rsbs(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 100; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(unchecked((uint)-100)); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + } + + public class Sub : CpuTestBase + { + [Fact] + public void Should_Execute_SubSpInstruction() + { + // Arrange + var opcode = InstructionEmiter.SubSp(0x10); + Cpu.Registers.SP = 0x10000040; + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x10000030); + } + } + + public class Subs : CpuTestBase + { + [Fact] + public void Should_Execute_SubsR1_With_Overflow() + { + // Arrange + var opcode = InstructionEmiter.SubsImm8(R1, 1); + Cpu.Registers[R1] = unchecked((uint)-0x80000000); + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(0x7fffffff); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_Execute_SubsR5R3() + { + // Arrange + var opcode = InstructionEmiter.SubsImm3(R5, R3, 5); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R3 = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be((uint)(-5 & uint.MaxValue)); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_Execute_SubsR5R3R2() + { + // Arrange + var opcode = InstructionEmiter.SubsReg(R5, R3, R2); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R3 = 6; + Cpu.Registers.R2 = 5; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.R5.Should().Be(1); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_Execute_SubsR3R3R2() + { + // Arrange + var opcode = InstructionEmiter.SubsReg(R3, R3, R2); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R2 = 8; + Cpu.Registers.R3 = 0xffffffff; + + // Act + Cpu.Step(); + + // Arrange + Cpu.Registers.R3.Should().Be(0xfffffff7); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + + [Fact] + public void Should_Execute_SubsR5R3R2_And_Set_NVFlags() + { + // Arrange + var opcode = InstructionEmiter.SubsReg(R5, R3, R2); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R3 = 0; + Cpu.Registers.R2 = 0x80000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.R5.Should().Be(0x80000000); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_Execute_SubsR5R3R2_And_Set_ZCFlags() + { + // Arrange + var opcode = InstructionEmiter.SubsReg(R3, R3, R2); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R3 = 0x80000000; + Cpu.Registers.R2 = 0x80000000; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.R5.Should().Be(0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + } } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/BitOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/BitOpsTests.cs index 074b654..3cb2c8a 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/BitOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/BitOpsTests.cs @@ -8,553 +8,554 @@ namespace RP2040.tests.Cpu.Instructions; public abstract class BitOpsTests { - public class Ands : CpuTestBase - { - [Fact] - public void Should_CalculateBitwiseAnd_And_UpdateNegativeFlag () - { - // Arrange - var opcode = InstructionEmiter.Ands (R5, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0xffff0000; - Cpu.Registers[R0] = 0xf00fffff; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0xf00f0000); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public abstract class Asrs - { - public class Immediate : CpuTestBase - { - [Fact] - public void Should_SignExtend_When_ShiftingMaxNegative_By31 () - { - // Arrange - var opcode = InstructionEmiter.AsrsImm5 (R3, R2, 31); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 0x80000000; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0xffffffff); // -1 (Sign extension) - Cpu.Registers.PC.Should ().Be (0x20000002); - - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeFalse (); - } - - [Fact] - public void Should_PerformArithmeticShiftBy32_When_ImmediateIsZero () - { - // Arrange - var opcode = InstructionEmiter.AsrsImm5 (R3, R2, 0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 0x80000000; - Cpu.Registers.C = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0xffffffff); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - } - } - - public class Register : CpuTestBase - { - [Fact] - public void Should_UseOnlyBottomByteOfShiftRegister () - { - // Arrange - var opcode = InstructionEmiter.AsrsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x80000040; - Cpu.Registers[R4] = 0xff500007; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0xff000000); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - } - - [Fact] - public void Should_ResultInZero_When_ShiftingPositiveNumber_ByMoreThan31 () - { - // Arrange - var opcode = InstructionEmiter.AsrsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x40000040; - Cpu.Registers[R4] = 50; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeFalse (); - } - - [Fact] - public void Should_ShiftBy31_And_UpdateCarry_WithBit30 () - { - // Arrange - var opcode = InstructionEmiter.AsrsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x40000040; - Cpu.Registers[R4] = 31; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - } - - [Fact] - public void Should_ResultInMinusOne_When_ShiftingNegativeNumber_ByMoreThan31 () - { - // Arrange - var opcode = InstructionEmiter.AsrsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x80000040; - Cpu.Registers[R4] = 50; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0xffffffff); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - } - - [Fact] - public void Should_PreserveValue_And_PreserveCarry_When_ShiftIsZero () - { - // Arrange - var opcode = InstructionEmiter.AsrsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x80000040; - Cpu.Registers[R4] = 0; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0x80000040); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.C.Should ().BeTrue (); - } - } - } - - public class Bics : CpuTestBase - { - [Fact] - public void Should_ClearBits_WhereMaskIsSet () - { - // Arrange - var opcode = InstructionEmiter.Bics (R0, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xff; - Cpu.Registers[R3] = 0x0f; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0xf0); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeFalse (); - } - - [Fact] - public void Should_SetNegativeFlag_When_ResultHasSignBitSet () - { - // Arrange - var opcode = InstructionEmiter.Bics (R0, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xffffffff; - Cpu.Registers[R3] = 0x0000ffff; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0xffff0000); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public class Eors : CpuTestBase - { - [Fact] - public void Should_CalculateExclusiveOr_And_UpdateNegativeFlag () - { - // Arrange - var opcode = InstructionEmiter.Eors (R1, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 0xf0f0f0f0; - Cpu.Registers[R3] = 0x08ff3007; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (0xf80fc0f7); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public class Mov : CpuTestBase - { - [Fact] - public void Should_CopyValue_BetweenRegisters () - { - // Arrange - var opcode = InstructionEmiter.Mov (R3, R8); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R8] = 55; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (55); - } - - [Fact] - public void Should_ReadProgramCounter_WithPipelineOffset () - { - // Arrange - var opcode = InstructionEmiter.Mov (R3, PC); - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0x20000004); - } - - [Fact] - public void Should_MoveRegisterToStackPointer_And_EnforceAlignment () - { - // Arrange - var opcode = InstructionEmiter.Mov (SP, R8); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R8] = 55; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (52); - } - - [Fact] - public void Should_ClearLowerTwoBits_When_WritingToStackPointer () - { - // Arrange - Cpu.Registers.PC = 0x20000000; - var opcode = InstructionEmiter.Mov (SP, R5); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers.R5 = 0x53; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (0x50); - } - - [Fact] - public void Should_ClearLeastSignificantBit_When_WritingToProgramCounter () - { - // Arrange - Cpu.Registers.PC = 0x20000000; - var opcode = InstructionEmiter.Mov (PC, R5); - Bus.WriteHalfWord (0x20000000, opcode); // mov pc, r5 - Cpu.Registers.R5 = 0x53; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x52); - } - } - - public class Movs : CpuTestBase - { - [Fact] - public void Should_LoadImmediateValue_And_UpdateFlags () - { - // Arrange - var opcode = InstructionEmiter.Movs (R5, 128); - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (128); - Cpu.Registers.PC.Should ().Be (0x20000002); - } - } - - public class Mvns : CpuTestBase - { - [Fact] - public void Should_BitwiseInvert_Value_And_UpdateNegativeFlag () - { - // Arrange - var opcode = InstructionEmiter.Mvns (R4, R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x11115555; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R4].Should ().Be (0xeeeeaaaa); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public class Lsls - { - public class Immediate : CpuTestBase - { - [Fact] - public void Should_ShiftLeft_ByImmediateValue () - { - // Arrange - var opcode = InstructionEmiter.LslsImm5 (R5, R5, 18); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0b00000000000000000011; // 0b11 - Cpu.Registers.C = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0b11000000000000000000); - Cpu.Registers.C.Should ().BeFalse (); - Cpu.Registers.PC.Should ().Be (0x20000002); - } - - [Fact] - public void Should_SetCarryFlag_When_BitIsShiftedOut () - { - // Arrange - var opcode = InstructionEmiter.LslsImm5 (R5, R5, 18); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0x4001; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0x40000); - Cpu.Registers.C.Should ().BeTrue (); - } - - [Fact] - public void Should_PreserveCarry_When_ShiftIsZero () - { - // Arrange - var opcode = InstructionEmiter.LslsImm5 (R5, R5, 0); // LSLS R5, R5, #0 - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0xFFFF; - Cpu.Registers.C = true; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0xFFFF); - Cpu.Registers.C.Should ().BeTrue ("LSL #0 should preserve Carry flag (MOVS behavior)"); - Cpu.Registers.Z.Should ().BeFalse (); - Cpu.Registers.N.Should ().BeFalse (); - } - } - - public class Register : CpuTestBase - { - [Fact] - public void Should_UseOnlyBottomByte_Of_ShiftRegister () - { - // Arrange - var opcode = InstructionEmiter.LslsRegister (R5, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0b00000000000000000011; - Cpu.Registers[R0] = 0xFF003302; - Cpu.Registers.C = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0b00000000000000001100); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.C.Should ().BeFalse (); - } - - [Fact] - public void Should_ResultInZero_And_SetCarry_When_ShiftingBy32 () - { - // Arrange - var opcode = InstructionEmiter.LslsRegister (R3, R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 1; - Cpu.Registers[R4] = 0x20; - Cpu.Registers.C = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (0); - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers.N.Should ().BeFalse (); - Cpu.Registers.Z.Should ().BeTrue (); // Result is 0 - Cpu.Registers.C.Should ().BeTrue (); // Bit 0 shifted out - } - } - } - - public class Orrs : CpuTestBase - { - [Fact] - public void Should_CalculateBitwiseOr_And_UpdateNegativeFlag () - { - // Arrange - var opcode = InstructionEmiter.Orrs (R5, R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R5] = 0xf00f0000; - Cpu.Registers[R0] = 0xf000ffff; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (0xf00fffff); - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeFalse (); - } - } - - public class Rev : CpuTestBase - { - [Fact] - public void Should_ReverseByteOrder_Of_32BitWord () - { - // Arrange - var opcode = InstructionEmiter.Rev (R2, R3); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers[R3] = 0x11223344; - - // Act - Cpu.Step(); - - // Assert - Cpu.Registers.R2.Should ().Be (0x44332211); - } - } - - public class Rev16 : CpuTestBase - { - [Fact] - public void ShouldExecuteRev16R0R5Instruction() - { - // Arrange - var opcode = InstructionEmiter.Rev16 (R0, R5); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers[R5] = 0x11223344; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0x22114433); - } - } - - public class Revsh : CpuTestBase - { - [Fact] - public void Should_ReverseLowerHalfword_And_SignExtend () - { - // Arrange - var opcode = InstructionEmiter.Revsh (R1, R2); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R2] = 0xeeaa55f0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (0xfffff055); - } - } + public class Ands : CpuTestBase + { + [Fact] + public void Should_CalculateBitwiseAnd_And_UpdateNegativeFlag() + { + // Arrange + var opcode = InstructionEmiter.Ands(R5, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0xffff0000; + Cpu.Registers[R0] = 0xf00fffff; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0xf00f0000); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public abstract class Asrs + { + public class Immediate : CpuTestBase + { + [Fact] + public void Should_SignExtend_When_ShiftingMaxNegative_By31() + { + // Arrange + var opcode = InstructionEmiter.AsrsImm5(R3, R2, 31); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 0x80000000; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0xffffffff); // -1 (Sign extension) + Cpu.Registers.PC.Should().Be(0x20000002); + + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeFalse(); + } + + [Fact] + public void Should_PerformArithmeticShiftBy32_When_ImmediateIsZero() + { + // Arrange + var opcode = InstructionEmiter.AsrsImm5(R3, R2, 0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 0x80000000; + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0xffffffff); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + } + } + + public class Register : CpuTestBase + { + [Fact] + public void Should_UseOnlyBottomByteOfShiftRegister() + { + // Arrange + var opcode = InstructionEmiter.AsrsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x80000040; + Cpu.Registers[R4] = 0xff500007; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0xff000000); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + } + + [Fact] + public void Should_ResultInZero_When_ShiftingPositiveNumber_ByMoreThan31() + { + // Arrange + var opcode = InstructionEmiter.AsrsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x40000040; + Cpu.Registers[R4] = 50; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeFalse(); + } + + [Fact] + public void Should_ShiftBy31_And_UpdateCarry_WithBit30() + { + // Arrange + var opcode = InstructionEmiter.AsrsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x40000040; + Cpu.Registers[R4] = 31; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + } + + [Fact] + public void Should_ResultInMinusOne_When_ShiftingNegativeNumber_ByMoreThan31() + { + // Arrange + var opcode = InstructionEmiter.AsrsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x80000040; + Cpu.Registers[R4] = 50; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0xffffffff); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + } + + [Fact] + public void Should_PreserveValue_And_PreserveCarry_When_ShiftIsZero() + { + // Arrange + var opcode = InstructionEmiter.AsrsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x80000040; + Cpu.Registers[R4] = 0; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0x80000040); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + } + } + } + + public class Bics : CpuTestBase + { + [Fact] + public void Should_ClearBits_WhereMaskIsSet() + { + // Arrange + var opcode = InstructionEmiter.Bics(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xff; + Cpu.Registers[R3] = 0x0f; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0xf0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + } + + [Fact] + public void Should_SetNegativeFlag_When_ResultHasSignBitSet() + { + // Arrange + var opcode = InstructionEmiter.Bics(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xffffffff; + Cpu.Registers[R3] = 0x0000ffff; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0xffff0000); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public class Eors : CpuTestBase + { + [Fact] + public void Should_CalculateExclusiveOr_And_UpdateNegativeFlag() + { + // Arrange + var opcode = InstructionEmiter.Eors(R1, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 0xf0f0f0f0; + Cpu.Registers[R3] = 0x08ff3007; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(0xf80fc0f7); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public class Mov : CpuTestBase + { + [Fact] + public void Should_CopyValue_BetweenRegisters() + { + // Arrange + var opcode = InstructionEmiter.Mov(R3, R8); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R8] = 55; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(55); + } + + [Fact] + public void Should_ReadProgramCounter_WithPipelineOffset() + { + // Arrange + var opcode = InstructionEmiter.Mov(R3, PC); + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0x20000004); + } + + [Fact] + public void Should_MoveRegisterToStackPointer_And_EnforceAlignment() + { + // Arrange + var opcode = InstructionEmiter.Mov(SP, R8); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R8] = 55; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(52); + } + + [Fact] + public void Should_ClearLowerTwoBits_When_WritingToStackPointer() + { + // Arrange + Cpu.Registers.PC = 0x20000000; + var opcode = InstructionEmiter.Mov(SP, R5); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R5 = 0x53; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x50); + } + + [Fact] + public void Should_ClearLeastSignificantBit_When_WritingToProgramCounter() + { + // Arrange + Cpu.Registers.PC = 0x20000000; + var opcode = InstructionEmiter.Mov(PC, R5); + Bus.WriteHalfWord(0x20000000, opcode); // mov pc, r5 + Cpu.Registers.R5 = 0x53; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x52); + } + } + + public class Movs : CpuTestBase + { + [Fact] + public void Should_LoadImmediateValue_And_UpdateFlags() + { + // Arrange + var opcode = InstructionEmiter.Movs(R5, 128); + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(128); + Cpu.Registers.PC.Should().Be(0x20000002); + } + } + + public class Mvns : CpuTestBase + { + [Fact] + public void Should_BitwiseInvert_Value_And_UpdateNegativeFlag() + { + // Arrange + var opcode = InstructionEmiter.Mvns(R4, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x11115555; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R4].Should().Be(0xeeeeaaaa); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public class Lsls + { + public class Immediate : CpuTestBase + { + [Fact] + public void Should_ShiftLeft_ByImmediateValue() + { + // Arrange + var opcode = InstructionEmiter.LslsImm5(R5, R5, 18); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0b00000000000000000011; // 0b11 + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0b11000000000000000000); + Cpu.Registers.C.Should().BeFalse(); + Cpu.Registers.PC.Should().Be(0x20000002); + } + + [Fact] + public void Should_SetCarryFlag_When_BitIsShiftedOut() + { + // Arrange + var opcode = InstructionEmiter.LslsImm5(R5, R5, 18); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0x4001; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0x40000); + Cpu.Registers.C.Should().BeTrue(); + } + + [Fact] + public void Should_PreserveCarry_When_ShiftIsZero() + { + // Arrange + var opcode = InstructionEmiter.LslsImm5(R5, R5, 0); // LSLS R5, R5, #0 + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0xFFFF; + Cpu.Registers.C = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0xFFFF); + Cpu.Registers.C.Should() + .BeTrue("LSL #0 should preserve Carry flag (MOVS behavior)"); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.N.Should().BeFalse(); + } + } + + public class Register : CpuTestBase + { + [Fact] + public void Should_UseOnlyBottomByte_Of_ShiftRegister() + { + // Arrange + var opcode = InstructionEmiter.LslsRegister(R5, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0b00000000000000000011; + Cpu.Registers[R0] = 0xFF003302; + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0b00000000000000001100); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.C.Should().BeFalse(); + } + + [Fact] + public void Should_ResultInZero_And_SetCarry_When_ShiftingBy32() + { + // Arrange + var opcode = InstructionEmiter.LslsRegister(R3, R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 1; + Cpu.Registers[R4] = 0x20; + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0); + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); // Result is 0 + Cpu.Registers.C.Should().BeTrue(); // Bit 0 shifted out + } + } + } + + public class Orrs : CpuTestBase + { + [Fact] + public void Should_CalculateBitwiseOr_And_UpdateNegativeFlag() + { + // Arrange + var opcode = InstructionEmiter.Orrs(R5, R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R5] = 0xf00f0000; + Cpu.Registers[R0] = 0xf000ffff; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(0xf00fffff); + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeFalse(); + } + } + + public class Rev : CpuTestBase + { + [Fact] + public void Should_ReverseByteOrder_Of_32BitWord() + { + // Arrange + var opcode = InstructionEmiter.Rev(R2, R3); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers[R3] = 0x11223344; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.R2.Should().Be(0x44332211); + } + } + + public class Rev16 : CpuTestBase + { + [Fact] + public void ShouldExecuteRev16R0R5Instruction() + { + // Arrange + var opcode = InstructionEmiter.Rev16(R0, R5); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers[R5] = 0x11223344; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0x22114433); + } + } + + public class Revsh : CpuTestBase + { + [Fact] + public void Should_ReverseLowerHalfword_And_SignExtend() + { + // Arrange + var opcode = InstructionEmiter.Revsh(R1, R2); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R2] = 0xeeaa55f0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(0xfffff055); + } + } } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/FlowOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/FlowOpsTests.cs index e5890dc..eb2c2f9 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/FlowOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/FlowOpsTests.cs @@ -3,160 +3,161 @@ using RP2040.Core.Helpers; using RP2040.Core.Memory; using RP2040.tests.Fixtures; + namespace RP2040.tests.Cpu.Instructions; public abstract class FlowOpsTests { - public class Bl : CpuTestBase - { - [Fact] - public void Should_BranchForward_And_SetLinkRegister_WithThumbBit () - { - // Arrange - var opcode = InstructionEmiter.Bl (0x34); - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000038); - Cpu.Registers.LR.Should ().Be (0x20000005); - Cpu.Cycles.Should ().Be (3); - } - - [Fact] - public void Should_BranchBackward_And_SetLinkRegister () - { - // Arrange - var opcode = InstructionEmiter.Bl (-0x10); - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000004 - 0x10); - Cpu.Registers.LR.Should ().Be (0x20000005); - Cpu.Cycles.Should ().Be (3); - } - - [Fact] - public void Should_BranchBackward_WithLargeOffset_And_SignExtendCorrectly () - { - // Arrange - var opcode = InstructionEmiter.Bl (-3242); - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000004 - 3242); - Cpu.Registers.LR.Should ().Be (0x20000005); - Cpu.Cycles.Should ().Be (3); - } - } - - public class Blx : CpuTestBase - { - [Fact] - public void Should_BranchToRegisterAddress_And_UpdateLinkRegister () - { - // Arrange - var opcode = InstructionEmiter.Blx (R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x20000201; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000200); - Cpu.Registers.LR.Should ().Be (0x20000003); - Cpu.Cycles.Should ().Be (2); - } - } - - public abstract class Branch : CpuTestBase - { - public class Unconditional : CpuTestBase - { - [Fact] - public void Should_BranchBackward_Unconditionally () - { - // Arrange - var opcode = InstructionEmiter.Branch (0xfec); - Bus.WriteHalfWord (0x20000000 + 9 * 2, opcode); - - Cpu.Registers.PC = 0x20000000 + 9 * 2; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000002); - } - } - - public class Conditional : CpuTestBase - { - [Fact] - public void Should_Branch_When_ConditionIsMet () - { - // Arrange - var opcode = InstructionEmiter.BranchConditional (1, 0x1f8); - Bus.WriteHalfWord (0x20000000 + 9 * 2, opcode); - - Cpu.Registers.PC = 0x20000000 + 9 * 2; - Cpu.Registers.Z = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x2000000e); - } - - [Fact] - public void Should_ContinueToNextInstruction_When_ConditionIsNotMet() - { - // Arrange - const uint jumpOffset = 0x1f8u; - var opcode = InstructionEmiter.BranchConditional(1, jumpOffset); - - const uint startPc = 0x20000010u; - Bus.WriteHalfWord(startPc, opcode); - - Cpu.Registers.PC = startPc; - Cpu.Registers.Z = true; - - // Act - Cpu.Step(); - - // Assert - Cpu.Registers.PC.Should().Be(startPc + 2); - } - } - } - - public class Bx : CpuTestBase - { - [Fact] - public void Should_BranchToAddress_StoredInRegister () - { - // Arrange - var opcode = InstructionEmiter.Bx (LR); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers.LR = 0x10000200; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x10000200); - } - } + public class Bl : CpuTestBase + { + [Fact] + public void Should_BranchForward_And_SetLinkRegister_WithThumbBit() + { + // Arrange + var opcode = InstructionEmiter.Bl(0x34); + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000038); + Cpu.Registers.LR.Should().Be(0x20000005); + Cpu.Cycles.Should().Be(3); + } + + [Fact] + public void Should_BranchBackward_And_SetLinkRegister() + { + // Arrange + var opcode = InstructionEmiter.Bl(-0x10); + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000004 - 0x10); + Cpu.Registers.LR.Should().Be(0x20000005); + Cpu.Cycles.Should().Be(3); + } + + [Fact] + public void Should_BranchBackward_WithLargeOffset_And_SignExtendCorrectly() + { + // Arrange + var opcode = InstructionEmiter.Bl(-3242); + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000004 - 3242); + Cpu.Registers.LR.Should().Be(0x20000005); + Cpu.Cycles.Should().Be(3); + } + } + + public class Blx : CpuTestBase + { + [Fact] + public void Should_BranchToRegisterAddress_And_UpdateLinkRegister() + { + // Arrange + var opcode = InstructionEmiter.Blx(R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x20000201; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000200); + Cpu.Registers.LR.Should().Be(0x20000003); + Cpu.Cycles.Should().Be(2); + } + } + + public abstract class Branch : CpuTestBase + { + public class Unconditional : CpuTestBase + { + [Fact] + public void Should_BranchBackward_Unconditionally() + { + // Arrange + var opcode = InstructionEmiter.Branch(0xfec); + Bus.WriteHalfWord(0x20000000 + 9 * 2, opcode); + + Cpu.Registers.PC = 0x20000000 + 9 * 2; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000002); + } + } + + public class Conditional : CpuTestBase + { + [Fact] + public void Should_Branch_When_ConditionIsMet() + { + // Arrange + var opcode = InstructionEmiter.BranchConditional(1, 0x1f8); + Bus.WriteHalfWord(0x20000000 + 9 * 2, opcode); + + Cpu.Registers.PC = 0x20000000 + 9 * 2; + Cpu.Registers.Z = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x2000000e); + } + + [Fact] + public void Should_ContinueToNextInstruction_When_ConditionIsNotMet() + { + // Arrange + const uint jumpOffset = 0x1f8u; + var opcode = InstructionEmiter.BranchConditional(1, jumpOffset); + + const uint startPc = 0x20000010u; + Bus.WriteHalfWord(startPc, opcode); + + Cpu.Registers.PC = startPc; + Cpu.Registers.Z = true; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(startPc + 2); + } + } + } + + public class Bx : CpuTestBase + { + [Fact] + public void Should_BranchToAddress_StoredInRegister() + { + // Arrange + var opcode = InstructionEmiter.Bx(LR); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers.LR = 0x10000200; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x10000200); + } + } } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/MemoryOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/MemoryOpsTests.cs index 2db46a0..f164911 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/MemoryOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/MemoryOpsTests.cs @@ -2,269 +2,275 @@ using RP2040.Core.Helpers; using RP2040.Core.Memory; using RP2040.tests.Fixtures; + namespace RP2040.tests.Cpu.Instructions; public abstract class MemoryOpsTests { - public class Pop : CpuTestBase - { - const uint STACK_BASE = 0x20004000; - - public Pop () - { - Cpu.Registers.SP = STACK_BASE; - } - - [Fact] - public void Should_PopStandardRegisters_And_UpdateStackPointer () - { - // Arrange - var opcode = InstructionEmiter.Pop (false, (1 << R0) | (1 << R1) | (1 << R2)); - Bus.WriteHalfWord (0x20000000, opcode); - - Bus.WriteWord (STACK_BASE, 0x10101010); - Bus.WriteWord (STACK_BASE + 4, 0x20202020); - Bus.WriteWord (STACK_BASE + 8, 0x30303030); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_BASE + 12); - Cpu.Registers[R0].Should ().Be (0x10101010); - Cpu.Registers[R1].Should ().Be (0x20202020); - Cpu.Registers[R2].Should ().Be (0x30303030); - } - - [Fact] - public void Should_PopRegistersAndProgramCounter_HandlingThumbBit () - { - // Arrange - var opcode = InstructionEmiter.Pop (true, 1 << R4); - Bus.WriteHalfWord (0x20000000, opcode); - - Bus.WriteWord (STACK_BASE, 0x44444444); - const uint returnAddressRaw = 0x20000101; // Not aligned - Bus.WriteWord (STACK_BASE + 4, returnAddressRaw); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_BASE + 8); - Cpu.Registers[R4].Should ().Be (0x44444444); - Cpu.Registers[PC].Should ().Be (0x20000100); // Aligned - } - - [Fact] - public void Should_PopRegisters_InAscendingIndexOrder () - { - // Arrange - var opcode = InstructionEmiter.Pop (false, (1 << R7) | (1 << R0)); - Bus.WriteHalfWord (0x20000000, opcode); - - Bus.WriteWord (STACK_BASE, 0xAAAAAAAA); - Bus.WriteWord (STACK_BASE + 4, 0xBBBBBBBB); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0xAAAAAAAA); - Cpu.Registers[R7].Should ().Be (0xBBBBBBBB); - } - - [Fact] - public void Should_ConsumeExtraCycles_When_PoppingProgramCounter () - { - // Arrange - var opcode = InstructionEmiter.Pop (true, 0); - Bus.WriteHalfWord (0x20000000, opcode); - - var cyclesBefore = Cpu.Cycles; - - // Act - Cpu.Step (); - - // Assert - var cyclesTaken = Cpu.Cycles - cyclesBefore; - cyclesTaken.Should ().BeGreaterThan (2); - } - - [Fact] - public void Should_HandleStackWrapAround_WithoutThrowing () - { - // Arrange - const uint nearlyEnd = BusInterconnect.SRAM_START_ADDRESS + BusInterconnect.MASK_SRAM - 2; - Cpu.Registers.SP = nearlyEnd; - - var opcode = InstructionEmiter.Pop (false, 1 << R0); - Bus.WriteHalfWord (0x20000000, opcode); - - // Act - var act = () => Cpu.Step (); - - // Assert - act.Should ().NotThrow (); - Cpu.Registers[SP].Should ().Be (nearlyEnd + 4); - } - } - - public class Push : CpuTestBase - { - const uint STACK_INITIAL = 0x20001000; - - public Push () - { - Cpu.Registers.SP = STACK_INITIAL; - } - - [Fact] - public void Should_PushSingleRegister_ToStack () - { - // Arrange - var opcode = InstructionEmiter.Push (false, 1 << R0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xCAFEBABE; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_INITIAL - 4); - Bus.ReadWord (STACK_INITIAL - 4).Should ().Be (0xCAFEBABE); - } - - [Fact] - public void Should_PushMultipleRegisters_InAscendingOrder () - { - // Arrange - var opcode = InstructionEmiter.Push (false, (1 << R1) | (1 << R2)); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R1] = 0x11111111; - Cpu.Registers[R2] = 0x22222222; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_INITIAL - 8); - Bus.ReadWord (STACK_INITIAL - 8).Should ().Be (0x11111111); - Bus.ReadWord (STACK_INITIAL - 4).Should ().Be (0x22222222); - } - - [Fact] - public void Should_PushLinkRegister_And_OtherRegisters () - { - // Arrange - var opcode = InstructionEmiter.Push (true, 1 << R3); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[R3] = 0x33333333; - Cpu.Registers[LR] = 0xFFFFFFFF; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_INITIAL - 8); - Bus.ReadWord (STACK_INITIAL - 8).Should ().Be (0x33333333); - Bus.ReadWord (STACK_INITIAL - 4).Should ().Be (0xFFFFFFFF); - } - - [Fact] - public void Should_PushOnlyLinkRegister () - { - // Arrange - var opcode = InstructionEmiter.Push (true, 0); - Bus.WriteHalfWord (0x20000000, opcode); - - Cpu.Registers[LR] = 0xABCDEF00; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[SP].Should ().Be (STACK_INITIAL - 4); - Bus.ReadWord (STACK_INITIAL - 4).Should ().Be (0xABCDEF00); - } - - [Fact] - public void Should_ConsumeCycles_ProportionalToRegisterCount () - { - // Arrange - var opcode = InstructionEmiter.Push (true, (1 << R0) | (1 << R1) | (1 << R2)); - Bus.WriteHalfWord (0x20000000, opcode); - - var initialCycles = Cpu.Cycles; - - // Act - Cpu.Step (); - - // Assert - (Cpu.Cycles - initialCycles).Should ().Be (5); - } - } - - public class Ldmia : CpuTestBase - { - [Fact] - public void Should_LoadMultiple_And_WriteBackBaseAddress () - { - // Arrange - var opcode = InstructionEmiter.Ldmia (R0, 1 << R1 | 1 << R2); - Bus.WriteHalfWord (0x20000000, opcode); - const uint baseAddr = 0x20000010; - Cpu.Registers[R0] = baseAddr; - - Bus.WriteWord (baseAddr, 0xF00DF00D); - Bus.WriteWord (baseAddr + 4, 0x4242); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers[R0].Should ().Be (baseAddr + 8); - Cpu.Registers[R1].Should ().Be (0xF00DF00D); - Cpu.Registers[R2].Should ().Be (0x4242); - } - - [Fact] - public void Should_LoadMultiple_WithoutWriteBack_When_BaseIsLoaded () - { - // Arrange - var opcode = InstructionEmiter.Ldmia (R5, 1 << R5); - Bus.WriteHalfWord (0x20000000, opcode); - const uint baseAddr = 0x20000010; - Cpu.Registers[R5] = baseAddr; - Bus.WriteWord (baseAddr, 0xF00DF00D); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000002); - Cpu.Registers[R5].Should ().Be (0xF00DF00D); - } - - [Fact] - public void Should_ConsumeCorrectCycles_ForMultipleLoad () - { - // Arrange - var opcode = InstructionEmiter.Ldmia (R0, 1 << R1 | 1 << R2); - Bus.WriteHalfWord (0x20000000, opcode); - Cpu.Registers[R0] = 0x20000010; - - var initialCycles = Cpu.Cycles; - - // Act - Cpu.Step (); - - // Assert - (Cpu.Cycles - initialCycles).Should ().Be (3); - } - } + public class Pop : CpuTestBase + { + const uint STACK_BASE = 0x20004000; + + public Pop() + { + Cpu.Registers.SP = STACK_BASE; + } + + [Fact] + public void Should_PopStandardRegisters_And_UpdateStackPointer() + { + // Arrange + var opcode = InstructionEmiter.Pop(false, (1 << R0) | (1 << R1) | (1 << R2)); + Bus.WriteHalfWord(0x20000000, opcode); + + Bus.WriteWord(STACK_BASE, 0x10101010); + Bus.WriteWord(STACK_BASE + 4, 0x20202020); + Bus.WriteWord(STACK_BASE + 8, 0x30303030); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_BASE + 12); + Cpu.Registers[R0].Should().Be(0x10101010); + Cpu.Registers[R1].Should().Be(0x20202020); + Cpu.Registers[R2].Should().Be(0x30303030); + } + + [Fact] + public void Should_PopRegistersAndProgramCounter_HandlingThumbBit() + { + // Arrange + var opcode = InstructionEmiter.Pop(true, 1 << R4); + Bus.WriteHalfWord(0x20000000, opcode); + + Bus.WriteWord(STACK_BASE, 0x44444444); + const uint returnAddressRaw = 0x20000101; // Not aligned + Bus.WriteWord(STACK_BASE + 4, returnAddressRaw); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_BASE + 8); + Cpu.Registers[R4].Should().Be(0x44444444); + Cpu.Registers[PC].Should().Be(0x20000100); // Aligned + } + + [Fact] + public void Should_PopRegisters_InAscendingIndexOrder() + { + // Arrange + var opcode = InstructionEmiter.Pop(false, (1 << R7) | (1 << R0)); + Bus.WriteHalfWord(0x20000000, opcode); + + Bus.WriteWord(STACK_BASE, 0xAAAAAAAA); + Bus.WriteWord(STACK_BASE + 4, 0xBBBBBBBB); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0xAAAAAAAA); + Cpu.Registers[R7].Should().Be(0xBBBBBBBB); + } + + [Fact] + public void Should_ConsumeExtraCycles_When_PoppingProgramCounter() + { + // Arrange + var opcode = InstructionEmiter.Pop(true, 0); + Bus.WriteHalfWord(0x20000000, opcode); + + var cyclesBefore = Cpu.Cycles; + + // Act + Cpu.Step(); + + // Assert + var cyclesTaken = Cpu.Cycles - cyclesBefore; + cyclesTaken.Should().BeGreaterThan(2); + } + + [Fact] + public void Should_HandleStackWrapAround_WithoutThrowing() + { + // Arrange + const uint nearlyEnd = + BusInterconnect.SRAM_START_ADDRESS + BusInterconnect.MASK_SRAM - 2; + Cpu.Registers.SP = nearlyEnd; + + var opcode = InstructionEmiter.Pop(false, 1 << R0); + Bus.WriteHalfWord(0x20000000, opcode); + + // Act + var act = () => Cpu.Step(); + + // Assert + act.Should().NotThrow(); + Cpu.Registers[SP].Should().Be(nearlyEnd + 4); + } + } + + public class Push : CpuTestBase + { + const uint STACK_INITIAL = 0x20001000; + + public Push() + { + Cpu.Registers.SP = STACK_INITIAL; + } + + [Fact] + public void Should_PushSingleRegister_ToStack() + { + // Arrange + var opcode = InstructionEmiter.Push(false, 1 << R0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xCAFEBABE; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_INITIAL - 4); + Bus.ReadWord(STACK_INITIAL - 4).Should().Be(0xCAFEBABE); + } + + [Fact] + public void Should_PushMultipleRegisters_InAscendingOrder() + { + // Arrange + var opcode = InstructionEmiter.Push(false, (1 << R1) | (1 << R2)); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R1] = 0x11111111; + Cpu.Registers[R2] = 0x22222222; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_INITIAL - 8); + Bus.ReadWord(STACK_INITIAL - 8).Should().Be(0x11111111); + Bus.ReadWord(STACK_INITIAL - 4).Should().Be(0x22222222); + } + + [Fact] + public void Should_PushLinkRegister_And_OtherRegisters() + { + // Arrange + var opcode = InstructionEmiter.Push(true, 1 << R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0x33333333; + Cpu.Registers[LR] = 0xFFFFFFFF; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_INITIAL - 8); + Bus.ReadWord(STACK_INITIAL - 8).Should().Be(0x33333333); + Bus.ReadWord(STACK_INITIAL - 4).Should().Be(0xFFFFFFFF); + } + + [Fact] + public void Should_PushOnlyLinkRegister() + { + // Arrange + var opcode = InstructionEmiter.Push(true, 0); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[LR] = 0xABCDEF00; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[SP].Should().Be(STACK_INITIAL - 4); + Bus.ReadWord(STACK_INITIAL - 4).Should().Be(0xABCDEF00); + } + + [Fact] + public void Should_ConsumeCycles_ProportionalToRegisterCount() + { + // Arrange + var opcode = InstructionEmiter.Push(true, (1 << R0) | (1 << R1) | (1 << R2)); + Bus.WriteHalfWord(0x20000000, opcode); + + var initialCycles = Cpu.Cycles; + + // Act + Cpu.Step(); + + // Assert + (Cpu.Cycles - initialCycles) + .Should() + .Be(5); + } + } + + public class Ldmia : CpuTestBase + { + [Fact] + public void Should_LoadMultiple_And_WriteBackBaseAddress() + { + // Arrange + var opcode = InstructionEmiter.Ldmia(R0, 1 << R1 | 1 << R2); + Bus.WriteHalfWord(0x20000000, opcode); + const uint baseAddr = 0x20000010; + Cpu.Registers[R0] = baseAddr; + + Bus.WriteWord(baseAddr, 0xF00DF00D); + Bus.WriteWord(baseAddr + 4, 0x4242); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers[R0].Should().Be(baseAddr + 8); + Cpu.Registers[R1].Should().Be(0xF00DF00D); + Cpu.Registers[R2].Should().Be(0x4242); + } + + [Fact] + public void Should_LoadMultiple_WithoutWriteBack_When_BaseIsLoaded() + { + // Arrange + var opcode = InstructionEmiter.Ldmia(R5, 1 << R5); + Bus.WriteHalfWord(0x20000000, opcode); + const uint baseAddr = 0x20000010; + Cpu.Registers[R5] = baseAddr; + Bus.WriteWord(baseAddr, 0xF00DF00D); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000002); + Cpu.Registers[R5].Should().Be(0xF00DF00D); + } + + [Fact] + public void Should_ConsumeCorrectCycles_ForMultipleLoad() + { + // Arrange + var opcode = InstructionEmiter.Ldmia(R0, 1 << R1 | 1 << R2); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers[R0] = 0x20000010; + + var initialCycles = Cpu.Cycles; + + // Act + Cpu.Step(); + + // Assert + (Cpu.Cycles - initialCycles) + .Should() + .Be(3); + } + } } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/SystemOpTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/SystemOpTests.cs index 1199646..15e1c19 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/SystemOpTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/SystemOpTests.cs @@ -1,361 +1,368 @@ using FluentAssertions; using RP2040.Core.Helpers; using RP2040.tests.Fixtures; + namespace RP2040.tests.Cpu.Instructions; public abstract class SystemOpTests { - public class Dmb : CpuTestBase - { - [Fact] - public void Should_ExecuteDataMemoryBarrier () - { - // Arrange - var opcode = InstructionEmiter.Dmb; - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000004); - } - } - - public class Dsb : CpuTestBase - { - [Fact] - public void Should_ExecuteDataSynchronizationBarrier () - { - // Arrange - var opcode = InstructionEmiter.Dsb; - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000004); - } - } - - public class Nop : CpuTestBase - { - [Fact] - public void Should_ExecuteNoOperation () - { - // Arrange - var opcode = InstructionEmiter.Nop; - Bus.WriteWord (0x20000000, opcode); - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.PC.Should ().Be (0x20000002); - } - } - - public class Mrs : CpuTestBase - { - private const int SYSM_APSR = 0; - private const int SYSM_IPSR = 5; - private const int SYSM_MSP = 8; - private const int SYSM_PSP = 9; - private const int SYSM_PRIMASK = 16; - private const int SYSM_CONTROL = 20; - - public Mrs () - { - Cpu.Registers.CONTROL = 0; - Cpu.Registers.IPSR = 0; - } - - [Fact] - public void Should_ReadIpsr_And_MaskReservedBits () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R0, SYSM_IPSR); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 55; // Dirty value - Cpu.Registers.IPSR = 0; // Thread Mode - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0, "IPSR should be 0 in Thread Mode"); - Cpu.Registers.PC.Should ().Be (0x20000004); - } - - [Fact] - public void Should_ReadApsr_WithCurrentFlags () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R1, SYSM_APSR); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.N = true; - Cpu.Registers.Z = true; - Cpu.Registers.C = true; - Cpu.Registers.V = true; - - const uint expectedApsr = 0xF0000000; // N=1, Z=1, C=1, V=1, others 0 - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R1].Should ().Be (expectedApsr); - } - - [Fact] - public void Should_ReadMainStackPointer_WhenActive () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R2, SYSM_MSP); - Bus.WriteWord (0x20000000, opcode); - - const uint stackValue = 0x20004000; - Cpu.Registers.SP = stackValue; - Cpu.Registers[R2] = 0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R2].Should ().Be (stackValue, "MRS MSP should read current SP when using MSP"); - } - - [Fact] - public void Should_ReadProcessStackPointer_FromStorage_WhenUsingMsp () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R3, SYSM_PSP); - Bus.WriteWord (0x20000000, opcode); - - const uint stackValue = 0x20008000; - Cpu.Registers.PSP_Storage = stackValue; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R3].Should ().Be (stackValue, "MRS PSP should read Storage when using MSP"); - } - - [Fact] - public void Should_ReadPrimask () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R4, SYSM_PRIMASK); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.PRIMASK = 1; // disable interrupts - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R4].Should ().Be (1); - } - - [Fact] - public void Should_ReadControlRegister () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R5, SYSM_CONTROL); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.CONTROL = 2; // SPSEL=1 - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R5].Should ().Be (2); - } - - [Fact] - public void Should_MaskApsrFlags_WhenReadingIpsr () - { - // Arrange - var opcode = InstructionEmiter.Mrs (R0, SYSM_IPSR); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.N = true; // Dirty flag - Cpu.Registers.IPSR = 0; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers[R0].Should ().Be (0, "MRS IPSR mask should exclude APSR flags (Bit 31)"); - } - } - - public class Msr : CpuTestBase - { - private const int SYSM_APSR = 0; - private const int SYSM_IPSR = 5; - private const int SYSM_MSP = 8; - private const int SYSM_PSP = 9; - private const int SYSM_PRIMASK = 16; - private const int SYSM_CONTROL = 20; - - public Msr () - { - Cpu.Registers.CONTROL = 0; - Cpu.Registers.IPSR = 0; - } - - [Fact] - public void Should_UpdateMainStackPointer_WhenActive () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_MSP, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0x1234; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (0x1234); - Cpu.Registers.PC.Should ().Be (0x20000004); - } - - [Fact] - public void Should_AlignStackPointer_To4Bytes () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_MSP, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0x1233; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (0x1230, "Should align value to 4 bytes"); - } - - [Fact] - public void Should_UpdateProcessStackPointer_Storage_WhenInactive () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_PSP, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0x5678; - var currentSp = Cpu.Registers.SP; // MSP actual - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (currentSp, "Active SP (MSP) should not change"); - Cpu.Registers.PSP_Storage.Should ().Be (0x5678, "PSP Storage should be updated"); - } - - [Fact] - public void Should_SwitchToProcessStack_WhenSettingSpsel () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_CONTROL, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.SP = 0xAAAA0000; - Cpu.Registers.PSP_Storage = 0xBBBB0000; - Cpu.Registers[R0] = 2; // Bit 1 = SPSEL=1 (Switch to PSP) - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.CONTROL.Should ().Be (2); - Cpu.Registers.SP.Should ().Be (0xBBBB0000, "SP should execute hot-swap to PSP value"); - Cpu.Registers.MSP_Storage.Should ().Be (0xAAAA0000, "Old SP should be saved to MSP Storage"); - } - - [Fact] - public void Should_NotSwitchStacks_WhenInHandlerMode () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_CONTROL, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.IPSR = 1; // Handler Mode! - Cpu.Registers.SP = 0xAAAA0000; - Cpu.Registers.PSP_Storage = 0xBBBB0000; - Cpu.Registers.CONTROL = 0; - - Cpu.Registers[R0] = 2; // Try set SPSEL=1 - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.CONTROL.Should ().Be (0, "Register value updates"); - Cpu.Registers.SP.Should ().Be (0xAAAA0000, "Physical SP MUST NOT change in Handler Mode"); - } - - [Fact] - public void Should_UpdateFlags_WhenWritingToApsr () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_APSR, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xF0000000; // Set N, Z, C, V - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeTrue (); - Cpu.Registers.Z.Should ().BeTrue (); - Cpu.Registers.C.Should ().BeTrue (); - Cpu.Registers.V.Should ().BeTrue (); - } - - [Fact] - public void Should_IgnoreWrites_ToReadOnlyIpsr () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_IPSR, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers[R0] = 0xF0000000; - Cpu.Registers.N = false; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.N.Should ().BeFalse ("Writing to IPSR should ignore flags"); - Cpu.Registers.IPSR.Should ().Be (0, "IPSR is read-only"); - } - - [Fact] - public void Should_IgnorePrivilegedWrites_WhenInUnprivilegedMode () - { - // Arrange - var opcode = InstructionEmiter.Msr (SYSM_MSP, R0); - Bus.WriteWord (0x20000000, opcode); - - Cpu.Registers.CONTROL = 1; // nPRIV = 1 (Unprivileged) - Cpu.Registers[R0] = 0xDEADBEEF; - var originalSp = Cpu.Registers.SP; - - // Act - Cpu.Step (); - - // Assert - Cpu.Registers.SP.Should ().Be (originalSp, "Unprivileged write to MSP should be ignored"); - } - } + public class Dmb : CpuTestBase + { + [Fact] + public void Should_ExecuteDataMemoryBarrier() + { + // Arrange + var opcode = InstructionEmiter.Dmb; + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000004); + } + } + + public class Dsb : CpuTestBase + { + [Fact] + public void Should_ExecuteDataSynchronizationBarrier() + { + // Arrange + var opcode = InstructionEmiter.Dsb; + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000004); + } + } + + public class Nop : CpuTestBase + { + [Fact] + public void Should_ExecuteNoOperation() + { + // Arrange + var opcode = InstructionEmiter.Nop; + Bus.WriteWord(0x20000000, opcode); + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.PC.Should().Be(0x20000002); + } + } + + public class Mrs : CpuTestBase + { + private const int SYSM_APSR = 0; + private const int SYSM_IPSR = 5; + private const int SYSM_MSP = 8; + private const int SYSM_PSP = 9; + private const int SYSM_PRIMASK = 16; + private const int SYSM_CONTROL = 20; + + public Mrs() + { + Cpu.Registers.CONTROL = 0; + Cpu.Registers.IPSR = 0; + } + + [Fact] + public void Should_ReadIpsr_And_MaskReservedBits() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R0, SYSM_IPSR); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 55; // Dirty value + Cpu.Registers.IPSR = 0; // Thread Mode + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0, "IPSR should be 0 in Thread Mode"); + Cpu.Registers.PC.Should().Be(0x20000004); + } + + [Fact] + public void Should_ReadApsr_WithCurrentFlags() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R1, SYSM_APSR); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.N = true; + Cpu.Registers.Z = true; + Cpu.Registers.C = true; + Cpu.Registers.V = true; + + const uint expectedApsr = 0xF0000000; // N=1, Z=1, C=1, V=1, others 0 + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R1].Should().Be(expectedApsr); + } + + [Fact] + public void Should_ReadMainStackPointer_WhenActive() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R2, SYSM_MSP); + Bus.WriteWord(0x20000000, opcode); + + const uint stackValue = 0x20004000; + Cpu.Registers.SP = stackValue; + Cpu.Registers[R2] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R2] + .Should() + .Be(stackValue, "MRS MSP should read current SP when using MSP"); + } + + [Fact] + public void Should_ReadProcessStackPointer_FromStorage_WhenUsingMsp() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R3, SYSM_PSP); + Bus.WriteWord(0x20000000, opcode); + + const uint stackValue = 0x20008000; + Cpu.Registers.PSP_Storage = stackValue; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3] + .Should() + .Be(stackValue, "MRS PSP should read Storage when using MSP"); + } + + [Fact] + public void Should_ReadPrimask() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R4, SYSM_PRIMASK); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.PRIMASK = 1; // disable interrupts + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R4].Should().Be(1); + } + + [Fact] + public void Should_ReadControlRegister() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R5, SYSM_CONTROL); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.CONTROL = 2; // SPSEL=1 + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R5].Should().Be(2); + } + + [Fact] + public void Should_MaskApsrFlags_WhenReadingIpsr() + { + // Arrange + var opcode = InstructionEmiter.Mrs(R0, SYSM_IPSR); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.N = true; // Dirty flag + Cpu.Registers.IPSR = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0, "MRS IPSR mask should exclude APSR flags (Bit 31)"); + } + } + + public class Msr : CpuTestBase + { + private const int SYSM_APSR = 0; + private const int SYSM_IPSR = 5; + private const int SYSM_MSP = 8; + private const int SYSM_PSP = 9; + private const int SYSM_PRIMASK = 16; + private const int SYSM_CONTROL = 20; + + public Msr() + { + Cpu.Registers.CONTROL = 0; + Cpu.Registers.IPSR = 0; + } + + [Fact] + public void Should_UpdateMainStackPointer_WhenActive() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_MSP, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0x1234; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x1234); + Cpu.Registers.PC.Should().Be(0x20000004); + } + + [Fact] + public void Should_AlignStackPointer_To4Bytes() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_MSP, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0x1233; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(0x1230, "Should align value to 4 bytes"); + } + + [Fact] + public void Should_UpdateProcessStackPointer_Storage_WhenInactive() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_PSP, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0x5678; + var currentSp = Cpu.Registers.SP; // MSP actual + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should().Be(currentSp, "Active SP (MSP) should not change"); + Cpu.Registers.PSP_Storage.Should().Be(0x5678, "PSP Storage should be updated"); + } + + [Fact] + public void Should_SwitchToProcessStack_WhenSettingSpsel() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_CONTROL, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.SP = 0xAAAA0000; + Cpu.Registers.PSP_Storage = 0xBBBB0000; + Cpu.Registers[R0] = 2; // Bit 1 = SPSEL=1 (Switch to PSP) + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.CONTROL.Should().Be(2); + Cpu.Registers.SP.Should().Be(0xBBBB0000, "SP should execute hot-swap to PSP value"); + Cpu.Registers.MSP_Storage.Should() + .Be(0xAAAA0000, "Old SP should be saved to MSP Storage"); + } + + [Fact] + public void Should_NotSwitchStacks_WhenInHandlerMode() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_CONTROL, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.IPSR = 1; // Handler Mode! + Cpu.Registers.SP = 0xAAAA0000; + Cpu.Registers.PSP_Storage = 0xBBBB0000; + Cpu.Registers.CONTROL = 0; + + Cpu.Registers[R0] = 2; // Try set SPSEL=1 + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.CONTROL.Should().Be(0, "Register value updates"); + Cpu.Registers.SP.Should().Be(0xAAAA0000, "Physical SP MUST NOT change in Handler Mode"); + } + + [Fact] + public void Should_UpdateFlags_WhenWritingToApsr() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_APSR, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xF0000000; // Set N, Z, C, V + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeTrue(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeTrue(); + } + + [Fact] + public void Should_IgnoreWrites_ToReadOnlyIpsr() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_IPSR, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers[R0] = 0xF0000000; + Cpu.Registers.N = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.N.Should().BeFalse("Writing to IPSR should ignore flags"); + Cpu.Registers.IPSR.Should().Be(0, "IPSR is read-only"); + } + + [Fact] + public void Should_IgnorePrivilegedWrites_WhenInUnprivilegedMode() + { + // Arrange + var opcode = InstructionEmiter.Msr(SYSM_MSP, R0); + Bus.WriteWord(0x20000000, opcode); + + Cpu.Registers.CONTROL = 1; // nPRIV = 1 (Unprivileged) + Cpu.Registers[R0] = 0xDEADBEEF; + var originalSp = Cpu.Registers.SP; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.SP.Should() + .Be(originalSp, "Unprivileged write to MSP should be ignored"); + } + } } diff --git a/tests/RP2040.Core.Tests/Fixtures/CpuTestBase.cs b/tests/RP2040.Core.Tests/Fixtures/CpuTestBase.cs index fc578f0..f7c8c83 100644 --- a/tests/RP2040.Core.Tests/Fixtures/CpuTestBase.cs +++ b/tests/RP2040.Core.Tests/Fixtures/CpuTestBase.cs @@ -5,43 +5,43 @@ namespace RP2040.tests.Fixtures; public abstract class CpuTestBase : IDisposable { - protected readonly CortexM0Plus Cpu; - protected readonly BusInterconnect Bus; - - protected const int R0 = 0; - protected const int R1 = 1; - protected const int R2 = 2; - protected const int R3 = 3; - protected const int R4 = 4; - protected const int R5 = 5; - protected const int R6 = 6; - protected const int R7 = 7; - protected const int R8 = 8; - protected const int R9 = 9; - protected const int R10 = 10; - protected const int R11 = 11; - protected const int R12 = 12; + protected readonly CortexM0Plus Cpu; + protected readonly BusInterconnect Bus; - protected const int IP = 12; - protected const int SP = 13; - protected const int LR = 14; - protected const int PC = 15; + protected const int R0 = 0; + protected const int R1 = 1; + protected const int R2 = 2; + protected const int R3 = 3; + protected const int R4 = 4; + protected const int R5 = 5; + protected const int R6 = 6; + protected const int R7 = 7; + protected const int R8 = 8; + protected const int R9 = 9; + protected const int R10 = 10; + protected const int R11 = 11; + protected const int R12 = 12; - protected CpuTestBase() - { - Bus = new BusInterconnect(); - Cpu = new CortexM0Plus(Bus); - Cpu.Registers.PC = 0x20000000; - } + protected const int IP = 12; + protected const int SP = 13; + protected const int LR = 14; + protected const int PC = 15; - public void Dispose () - { - Dispose(true); - GC.SuppressFinalize(this); - } + protected CpuTestBase() + { + Bus = new BusInterconnect(); + Cpu = new CortexM0Plus(Bus); + Cpu.Registers.PC = 0x20000000; + } - protected void Dispose(bool disposing) - { - Bus.Dispose(); - } -} \ No newline at end of file + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + Bus.Dispose(); + } +} diff --git a/tests/RP2040.Core.Tests/RP2040.Core.Tests.csproj b/tests/RP2040.Core.Tests/RP2040.Core.Tests.csproj index c0e2047..acda266 100644 --- a/tests/RP2040.Core.Tests/RP2040.Core.Tests.csproj +++ b/tests/RP2040.Core.Tests/RP2040.Core.Tests.csproj @@ -1,28 +1,26 @@  + + net10.0 + enable + enable + false + RP2040.tests + true + - - net10.0 - enable - enable - false - RP2040.tests - true - + + + + + + + - - - - - - - + + + - - - - - - - - - \ No newline at end of file + + + + From c761e8170ea5a9c3847dc96905eb0e4715da54f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Mon, 2 Feb 2026 01:39:52 -0600 Subject: [PATCH 3/5] refactor(core): remove trailing blank and fix file formatting Remove an extra blank line introduced at top of src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs and normalize file whitespace. This reverts an unintended insertion of a stray newline that affected file formatting and kept the rest of the file contents unchanged. This is purely a non-functional cleanup to keep repository style consistent and avoid spurious whitespace diffs. --- .../Cpu/Instructions/ArithmeticOps.cs | 557 +++++++++--------- 1 file changed, 279 insertions(+), 278 deletions(-) diff --git a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index 810bcf5..7cb7ac3 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -1,284 +1,285 @@ using System.Runtime.CompilerServices; + namespace RP2040.Core.Cpu.Instructions; public static class ArithmeticOps { - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddsImmediate3 (ushort opcode, CortexM0Plus cpu) - { - // ADDS Rd, Rn, #imm3 - var rd = opcode & 0x7; - var rn = (opcode >> 3) & 0x7; - var imm3 = (uint)((opcode >> 6) & 0x7); - - ref var ptrRd = ref cpu.Registers[rd]; - var valRn = cpu.Registers[rn]; - - ptrRd = AddWithFlags (cpu, valRn, imm3, carryIn: 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddsImmediate8 (ushort opcode, CortexM0Plus cpu) - { - // ADDS Rd, #imm8 (Rd es Source y Destino) - var rd = (opcode >> 8) & 0x7; - var imm8 = (uint)(opcode & 0xFF); - - ref var ptrRd = ref cpu.Registers[rd]; - - ptrRd = AddWithFlags (cpu, ptrRd, imm8, carryIn: 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddsRegister (ushort opcode, CortexM0Plus cpu) - { - // ADDS Rd, Rn, Rm - var rd = opcode & 0x7; - var rn = (opcode >> 3) & 0x7; - var rm = (opcode >> 6) & 0x7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRn = cpu.Registers[rn]; - var valRm = cpu.Registers[rm]; - - ptrRd = AddWithFlags (cpu, valRn, valRm, carryIn: 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Adcs (ushort opcode, CortexM0Plus cpu) - { - // ADCS Rd, Rm (Rd es Source y Destino) - var rd = opcode & 0x7; - var rm = (opcode >> 3) & 0x7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRm = cpu.Registers[rm]; - - var carryIn = cpu.Registers.GetC (); - - ptrRd = AddWithFlags (cpu, ptrRd, valRm, carryIn); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddSpImmediate7 (ushort opcode, CortexM0Plus cpu) - { - // ADD SP, SP, #imm7 - var imm7 = (uint)((opcode & 0x7F) << 2); - cpu.Registers.SP += imm7; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddSpImmediate8 (ushort opcode, CortexM0Plus cpu) - { - // ADD Rd, SP, #imm8 - var rd = (opcode >> 8) & 0x7; - var imm8 = (uint)((opcode & 0xFF) << 2); - - cpu.Registers[rd] = cpu.Registers.SP + imm8; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddHighToPc (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - var valRm = cpu.Registers[rm]; - - ref var pc = ref cpu.Registers.PC; - - var valPcSource = pc + 2; - var result = (valPcSource + valRm) & 0xFFFFFFFE; - - pc = result; - cpu.Cycles++; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddHighToSp (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - var valRm = cpu.Registers[rm]; - - ref var sp = ref cpu.Registers.SP; - sp = (sp + valRm) & 0xFFFFFFFC; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void AddHighToReg (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - var dn = ((opcode >> 4) & 0x8) | (opcode & 0x7); - - ref var ptrDn = ref cpu.Registers[dn]; - - var valRm = cpu.Registers[rm]; - - ptrDn += valRm; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Adr (ushort opcode, CortexM0Plus cpu) - { - // ADR Rd, label - var rd = (opcode >> 8) & 0x7; - var imm8 = (uint)(opcode & 0xFF); - - var basePc = (cpu.Registers.PC + 2) & 0xFFFFFFFC; - - cpu.Registers[rd] = basePc + (imm8 << 2); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Cmn (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rn = opcode & 0x7; - - var valRm = cpu.Registers[rm]; - var valRn = cpu.Registers[rn]; - - AddWithFlags (cpu, valRn, valRm, carryIn: 0); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void CmpImmediate (ushort opcode, CortexM0Plus cpu) - { - var rn = (opcode >> 8) & 0x7; - var imm8 = (uint)(opcode & 0xFF); - - var val1 = cpu.Registers[rn]; - - SubWithFlags (cpu, val1, imm8); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void CmpRegister (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0x7; - var rn = opcode & 0x7; - - var val1 = cpu.Registers[rn]; - var val2 = cpu.Registers[rm]; - - SubWithFlags (cpu, val1, val2); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void CmpHighRegister (ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 3) & 0xF; - var rn = ((opcode >> 4) & 0x8) | (opcode & 0x7); - - var valRn = cpu.Registers[rn]; - var valRm = cpu.Registers[rm]; - - valRn += (uint)((rn + 1) >> 4) << 1; - valRm += (uint)((rm + 1) >> 4) << 1; - - SubWithFlags (cpu, valRn, valRm); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void Muls (ushort opcode, CortexM0Plus cpu) - { - var rn = (opcode >> 3) & 0x7; - var rdm = opcode & 0x7; - - ref var ptrRdm = ref cpu.Registers[rdm]; - var valRn = cpu.Registers[rn]; - - ptrRdm *= valRn; - - cpu.Registers.N = (int)ptrRdm < 0; - cpu.Registers.Z = (ptrRdm == 0); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Rsbs(ushort opcode, CortexM0Plus cpu) - { - var rn = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRn = cpu.Registers[rn]; - - ptrRd = SubWithFlags(cpu, 0, valRn); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void SubsImmediate3 (ushort opcode, CortexM0Plus cpu) - { - // SUBS Rd, Rn, #imm3 (Encoding T1) - var rd = opcode & 0x7; - var rn = (opcode >> 3) & 0x7; - var imm3 = (uint)((opcode >> 6) & 0x7); - - ref var ptrRd = ref cpu.Registers[rd]; - var valRn = cpu.Registers[rn]; - - ptrRd = SubWithFlags (cpu, valRn, imm3); - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - public static void SubsImmediate8 (ushort opcode, CortexM0Plus cpu) - { - // SUBS Rd, #imm8 (Encoding T2) - var rd = (opcode >> 8) & 0x7; - var imm8 = (uint)(opcode & 0xFF); - - ref var ptrRd = ref cpu.Registers[rd]; - - ptrRd = SubWithFlags (cpu, ptrRd, imm8); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SubSp(ushort opcode, CortexM0Plus cpu) - { - // SUB (SP minus immediate) - var imm32 = (opcode & 0x7f) << 2; - cpu.Registers.SP -= (ushort) imm32; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SubsRegister(ushort opcode, CortexM0Plus cpu) - { - var rm = (opcode >> 6) & 0x7; - var rn = (opcode >> 3) & 0x7; - var rd = opcode & 0x7; - - ref var ptrRd = ref cpu.Registers[rd]; - var valRn = cpu.Registers[rn]; - var valRm = cpu.Registers[rm]; - - ptrRd = SubWithFlags(cpu, valRn, valRm); - } - - // ============================================================= - // MATH HELPERS - // ============================================================= - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - private static uint AddWithFlags (CortexM0Plus cpu, uint op1, uint op2, uint carryIn) - { - var result64 = (ulong)op1 + op2 + carryIn; - var result32 = (uint)result64; - - cpu.Registers.N = (int)result32 < 0; - cpu.Registers.Z = (result32 == 0); - cpu.Registers.C = result64 > uint.MaxValue; - cpu.Registers.V = ((~(op1 ^ op2) & (op1 ^ result32)) & 0x80000000) != 0; - - return result32; - } - - [MethodImpl (MethodImplOptions.AggressiveInlining)] - private static uint SubWithFlags (CortexM0Plus cpu, uint op1, uint op2) - { - var result = op1 - op2; - cpu.Registers.N = (int)result < 0; - cpu.Registers.Z = (result == 0); - cpu.Registers.C = op1 >= op2; - cpu.Registers.V = (((op1 ^ op2) & (op1 ^ result)) & 0x80000000) != 0; - - return result; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddsImmediate3(ushort opcode, CortexM0Plus cpu) + { + // ADDS Rd, Rn, #imm3 + var rd = opcode & 0x7; + var rn = (opcode >> 3) & 0x7; + var imm3 = (uint)((opcode >> 6) & 0x7); + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + + ptrRd = AddWithFlags(cpu, valRn, imm3, carryIn: 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddsImmediate8(ushort opcode, CortexM0Plus cpu) + { + // ADDS Rd, #imm8 (Rd es Source y Destino) + var rd = (opcode >> 8) & 0x7; + var imm8 = (uint)(opcode & 0xFF); + + ref var ptrRd = ref cpu.Registers[rd]; + + ptrRd = AddWithFlags(cpu, ptrRd, imm8, carryIn: 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddsRegister(ushort opcode, CortexM0Plus cpu) + { + // ADDS Rd, Rn, Rm + var rd = opcode & 0x7; + var rn = (opcode >> 3) & 0x7; + var rm = (opcode >> 6) & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + var valRm = cpu.Registers[rm]; + + ptrRd = AddWithFlags(cpu, valRn, valRm, carryIn: 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Adcs(ushort opcode, CortexM0Plus cpu) + { + // ADCS Rd, Rm (Rd es Source y Destino) + var rd = opcode & 0x7; + var rm = (opcode >> 3) & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRm = cpu.Registers[rm]; + + var carryIn = cpu.Registers.GetC(); + + ptrRd = AddWithFlags(cpu, ptrRd, valRm, carryIn); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddSpImmediate7(ushort opcode, CortexM0Plus cpu) + { + // ADD SP, SP, #imm7 + var imm7 = (uint)((opcode & 0x7F) << 2); + cpu.Registers.SP += imm7; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddSpImmediate8(ushort opcode, CortexM0Plus cpu) + { + // ADD Rd, SP, #imm8 + var rd = (opcode >> 8) & 0x7; + var imm8 = (uint)((opcode & 0xFF) << 2); + + cpu.Registers[rd] = cpu.Registers.SP + imm8; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddHighToPc(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + var valRm = cpu.Registers[rm]; + + ref var pc = ref cpu.Registers.PC; + + var valPcSource = pc + 2; + var result = (valPcSource + valRm) & 0xFFFFFFFE; + + pc = result; + cpu.Cycles++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddHighToSp(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + var valRm = cpu.Registers[rm]; + + ref var sp = ref cpu.Registers.SP; + sp = (sp + valRm) & 0xFFFFFFFC; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AddHighToReg(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + var dn = ((opcode >> 4) & 0x8) | (opcode & 0x7); + + ref var ptrDn = ref cpu.Registers[dn]; + + var valRm = cpu.Registers[rm]; + + ptrDn += valRm; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Adr(ushort opcode, CortexM0Plus cpu) + { + // ADR Rd, label + var rd = (opcode >> 8) & 0x7; + var imm8 = (uint)(opcode & 0xFF); + + var basePc = (cpu.Registers.PC + 2) & 0xFFFFFFFC; + + cpu.Registers[rd] = basePc + (imm8 << 2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Cmn(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rn = opcode & 0x7; + + var valRm = cpu.Registers[rm]; + var valRn = cpu.Registers[rn]; + + AddWithFlags(cpu, valRn, valRm, carryIn: 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CmpImmediate(ushort opcode, CortexM0Plus cpu) + { + var rn = (opcode >> 8) & 0x7; + var imm8 = (uint)(opcode & 0xFF); + + var val1 = cpu.Registers[rn]; + + SubWithFlags(cpu, val1, imm8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CmpRegister(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rn = opcode & 0x7; + + var val1 = cpu.Registers[rn]; + var val2 = cpu.Registers[rm]; + + SubWithFlags(cpu, val1, val2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CmpHighRegister(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0xF; + var rn = ((opcode >> 4) & 0x8) | (opcode & 0x7); + + var valRn = cpu.Registers[rn]; + var valRm = cpu.Registers[rm]; + + valRn += (uint)((rn + 1) >> 4) << 1; + valRm += (uint)((rm + 1) >> 4) << 1; + + SubWithFlags(cpu, valRn, valRm); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Muls(ushort opcode, CortexM0Plus cpu) + { + var rn = (opcode >> 3) & 0x7; + var rdm = opcode & 0x7; + + ref var ptrRdm = ref cpu.Registers[rdm]; + var valRn = cpu.Registers[rn]; + + ptrRdm *= valRn; + + cpu.Registers.N = (int)ptrRdm < 0; + cpu.Registers.Z = (ptrRdm == 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Rsbs(ushort opcode, CortexM0Plus cpu) + { + var rn = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + + ptrRd = SubWithFlags(cpu, 0, valRn); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SubsImmediate3(ushort opcode, CortexM0Plus cpu) + { + // SUBS Rd, Rn, #imm3 (Encoding T1) + var rd = opcode & 0x7; + var rn = (opcode >> 3) & 0x7; + var imm3 = (uint)((opcode >> 6) & 0x7); + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + + ptrRd = SubWithFlags(cpu, valRn, imm3); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SubsImmediate8(ushort opcode, CortexM0Plus cpu) + { + // SUBS Rd, #imm8 (Encoding T2) + var rd = (opcode >> 8) & 0x7; + var imm8 = (uint)(opcode & 0xFF); + + ref var ptrRd = ref cpu.Registers[rd]; + + ptrRd = SubWithFlags(cpu, ptrRd, imm8); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SubSp(ushort opcode, CortexM0Plus cpu) + { + // SUB (SP minus immediate) + var imm32 = (opcode & 0x7f) << 2; + cpu.Registers.SP -= (ushort)imm32; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SubsRegister(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 6) & 0x7; + var rn = (opcode >> 3) & 0x7; + var rd = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rd]; + var valRn = cpu.Registers[rn]; + var valRm = cpu.Registers[rm]; + + ptrRd = SubWithFlags(cpu, valRn, valRm); + } + + // ============================================================= + // MATH HELPERS + // ============================================================= + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint AddWithFlags(CortexM0Plus cpu, uint op1, uint op2, uint carryIn) + { + var result64 = (ulong)op1 + op2 + carryIn; + var result32 = (uint)result64; + + cpu.Registers.N = (int)result32 < 0; + cpu.Registers.Z = (result32 == 0); + cpu.Registers.C = result64 > uint.MaxValue; + cpu.Registers.V = ((~(op1 ^ op2) & (op1 ^ result32)) & 0x80000000) != 0; + + return result32; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint SubWithFlags(CortexM0Plus cpu, uint op1, uint op2) + { + var result = op1 - op2; + cpu.Registers.N = (int)result < 0; + cpu.Registers.Z = (result == 0); + cpu.Registers.C = op1 >= op2; + cpu.Registers.V = (((op1 ^ op2) & (op1 ^ result)) & 0x80000000) != 0; + + return result; + } } From 5f8bb28f65d2b9fb6c090c5b7eb962e071ff4147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Mon, 2 Feb 2026 02:28:52 -0600 Subject: [PATCH 4/5] feat(arm-thumb): implement RSBS instruction and tests Implement Rsbs instruction encoding generation in InstructionEmiter, including argument validation for register indices. Add the RSBS opcode to the InstructionDecoder rules so the decoder recognizes the instruction. Restore RSBS-related unit tests in InstructionDecoderTests and add a new ArithmeticOps test that verifies negating zero sets Z and C flags correctly. Also remove an unused using and add a missing copyright line in LICENSE. These changes enable correct assembly emission, decoding, and execution testing of the RSBS (Negate) instruction. --- LICENSE | 1 + src/RP2040.Core/Cpu/InstructionDecoder.cs | 2 ++ src/RP2040.Core/Cpu/Instructions/BitOps.cs | 1 - src/RP2040.Core/Helpers/InstructionEmiter.cs | 6 +++++- .../Cpu/InstructionDecoderTests.cs | 9 +++++---- .../Cpu/Instructions/ArithmeticOpsTests.cs | 20 +++++++++++++++++++ 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 7bb06fa..97eedc1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2025 Iván Montiel Cardona +Copyright (c) 2025 Sergio Domínguez Rojas Copyright (c) 2025 Uri Shaked Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/src/RP2040.Core/Cpu/InstructionDecoder.cs b/src/RP2040.Core/Cpu/InstructionDecoder.cs index 59ec893..ef99387 100644 --- a/src/RP2040.Core/Cpu/InstructionDecoder.cs +++ b/src/RP2040.Core/Cpu/InstructionDecoder.cs @@ -64,6 +64,8 @@ public InstructionDecoder() new OpcodeRule(0xFFC0, 0x4380, &BitOps.Bics), // CMN (Rn, Rm) new OpcodeRule(0xFFC0, 0x42C0, &ArithmeticOps.Cmn), + // RSBS Rd, Rn, #0 (Negate) + new OpcodeRule(0xFFC0, 0x4240, &ArithmeticOps.Rsbs), // CMP Rn, Rm (Low Registers - Encoding T1) new OpcodeRule(0xFFC0, 0x4280, &ArithmeticOps.CmpRegister), // EORS Rdn, Rm diff --git a/src/RP2040.Core/Cpu/Instructions/BitOps.cs b/src/RP2040.Core/Cpu/Instructions/BitOps.cs index faf3f17..41df678 100644 --- a/src/RP2040.Core/Cpu/Instructions/BitOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/BitOps.cs @@ -1,6 +1,5 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace RP2040.Core.Cpu.Instructions; diff --git a/src/RP2040.Core/Helpers/InstructionEmiter.cs b/src/RP2040.Core/Helpers/InstructionEmiter.cs index 5c6d50c..75cb1fb 100644 --- a/src/RP2040.Core/Helpers/InstructionEmiter.cs +++ b/src/RP2040.Core/Helpers/InstructionEmiter.cs @@ -325,7 +325,11 @@ public static ushort Rev16(uint rd, uint rn) public static ushort Rsbs(uint rd, uint rn) { - return 0; + if (rd > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + if (rn > 15) + throw new ArgumentException(HighRegisterIndexOutOfRange); + return (ushort)(0x4240 | (rn & 0x7) << 3 | (rd & 0x7)); } public static ushort SubSp(uint imm) diff --git a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs index 51ec962..91cb326 100644 --- a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs @@ -59,10 +59,6 @@ public static TheoryData GetInstructionTestCases() &ArithmeticOps.AddsRegister ); Add("Adr", InstructionEmiter.Adr(R4, 0x50), &ArithmeticOps.Adr); - Add("SubsRegister", InstructionEmiter.SubsReg(R1, R2, R4), &ArithmeticOps.SubsRegister); - Add("SubsImm3", InstructionEmiter.SubsImm3(R1, R2, 3), &ArithmeticOps.SubsImmediate3); - Add("SubsImm8", InstructionEmiter.SubsImm8(R1, 0x10), &ArithmeticOps.SubsImmediate8); - Add("SubSp", InstructionEmiter.SubSp(0x10), &ArithmeticOps.SubSp); // Special Cases for AddHighRegister Add( @@ -90,6 +86,11 @@ public static TheoryData GetInstructionTestCases() &ArithmeticOps.CmpHighRegister ); Add("Muls", InstructionEmiter.Muls(R0, R2), &ArithmeticOps.Muls); + Add("Rsbs", InstructionEmiter.Rsbs(R0, R3), &ArithmeticOps.Rsbs); + Add("SubsRegister", InstructionEmiter.SubsReg(R1, R2, R4), &ArithmeticOps.SubsRegister); + Add("SubsImm3", InstructionEmiter.SubsImm3(R1, R2, 3), &ArithmeticOps.SubsImmediate3); + Add("SubsImm8", InstructionEmiter.SubsImm8(R1, 0x10), &ArithmeticOps.SubsImmediate8); + Add("SubSp", InstructionEmiter.SubSp(0x10), &ArithmeticOps.SubSp); // --- Bit Operations --- Add("Ands", InstructionEmiter.Ands(R5, R0), &BitOps.Ands); diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs index dc01857..2d2276b 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -705,6 +705,26 @@ public void Should_NegateRegisterAndSetFlags_When_SubtractingFromZero() Cpu.Registers.C.Should().BeFalse(); Cpu.Registers.V.Should().BeFalse(); } + + [Fact] + public void Should_SetZeroAndCarryFlags_When_NegatingZero() + { + // Arrange + var opcode = InstructionEmiter.Rsbs(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R3] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R0].Should().Be(0); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeTrue(); + Cpu.Registers.C.Should().BeTrue(); // No borrow en 0-0 + Cpu.Registers.V.Should().BeFalse(); + } } public class Sub : CpuTestBase From 09b645c9c54e666c4a71674eaa94ba7cac432fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Mon, 2 Feb 2026 02:34:53 -0600 Subject: [PATCH 5/5] feat(cpu): simplify and clarify fetch-cache and fast-fetch logic Remove Spanish comments and redundant notes, rename and tidy up fetch-cache handling and fast-fetch path to improve readability and keep implementation intent clear. - Strip non-English comments and extraneous TODO-like lines to unify code language and reduce. - Keep fetch cache fields and update logic intact; remove commented notes and redundant explanatory headers. - Preserve fast-guard/fallback behavior but simplify inline comments and ensure fetchPtr null check behavior remains explicit. - Minor comment wording tweaks (Spanish -> English) and a small comment fix for exception cycle wording (cycles instead of ciclos). No behavioral changes intended; changes are purely comment/annotation cleanup and minor code comment clarifications to improve maintainability. --- src/RP2040.Core/Cpu/CortexM0Plus.cs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/RP2040.Core/Cpu/CortexM0Plus.cs b/src/RP2040.Core/Cpu/CortexM0Plus.cs index 4bfbcc4..dd23ced 100644 --- a/src/RP2040.Core/Cpu/CortexM0Plus.cs +++ b/src/RP2040.Core/Cpu/CortexM0Plus.cs @@ -13,8 +13,6 @@ public unsafe class CortexM0Plus private readonly InstructionDecoder _decoder; - // CACHÉ DE FETCH LOCAL - // Apunta directamente al buffer interno de RandomAccessMemory private byte* _fetchPtr; private uint _fetchMask; private uint _currentRegionId; @@ -35,7 +33,6 @@ public void Reset() Registers.SP = Bus.ReadWord(0x00000000); Registers.PC = Bus.ReadWord(0x00000004); - // Inicializar caché UpdateFetchCache(Registers.PC); Registers.N = false; @@ -46,9 +43,6 @@ public void Reset() Cycles = 0; } - /// - /// Actualiza los punteros de caché cuando el PC salta a una región de memoria diferente. - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateFetchCache(uint pc) { @@ -69,7 +63,7 @@ private void UpdateFetchCache(uint pc) _fetchMask = BusInterconnect.MASK_BOOTROM & ~1u; break; default: - _fetchPtr = null; // Detiene el fast-fetch + _fetchPtr = null; break; } } @@ -79,8 +73,6 @@ public void Run(int instructions) { var decoder = _decoder; - // 1. Cargar caché a registros del CPU Host (Stack) - // Esto elimina la indirección 'this._fetchPtr' dentro del while var fetchPtr = _fetchPtr; var fetchMask = _fetchMask; var regionId = _currentRegionId; @@ -89,34 +81,29 @@ public void Run(int instructions) { var pc = Registers.PC; - // 2. FAST GUARD: ¿Seguimos en la misma región de memoria? - // (pc >> 28) es una operación de un solo ciclo. + // FAST GUARD if ((pc >> 28) != regionId) { - // FALLBACK: Cambio de región detectado. + // FALLBACK UpdateFetchCache(pc); - // Recargar caché local fetchPtr = _fetchPtr; fetchMask = _fetchMask; regionId = _currentRegionId; - // Si saltamos a memoria no ejecutable, abortamos el batch if (fetchPtr == null) break; } - // 3. FETCH ULTRA-RÁPIDO - // (pc & ~1u): Alineación obligatoria en ARM Thumb (elimina bit 0). - // & fetchMask: Mantiene el acceso dentro del buffer Pinned (seguridad). + // ULTRA-FAST FETCH var opcode = Unsafe.ReadUnaligned(fetchPtr + (pc & fetchMask)); - // 4. PRE-UPDATE PC (Speculative) + // PRE-UPDATE PC (Speculative) Registers.PC = pc + 2; Cycles++; - // 5. DISPATCH + // DISPATCH decoder.Dispatch(opcode, this); } @@ -125,7 +112,6 @@ public void Run(int instructions) _fetchMask = fetchMask; } - // Step simple para Debugging [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Step() { @@ -209,7 +195,7 @@ public void ExceptionEntry(uint exceptionNumber) var targetPc = Bus.ReadWord(vectorAddress); Registers.PC = targetPc & 0xFFFFFFFE; - Cycles += 12; // Exception Entry cost (aprox 12-15 ciclos) + Cycles += 12; // Exception Entry cost (aprox 12-15 cycles) } [MethodImpl(MethodImplOptions.NoInlining)]