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/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..dd23ced 100644 --- a/src/RP2040.Core/Cpu/CortexM0Plus.cs +++ b/src/RP2040.Core/Cpu/CortexM0Plus.cs @@ -2,249 +2,251 @@ 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; + + 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); + + UpdateFetchCache(Registers.PC); + + Registers.N = false; + Registers.Z = false; + Registers.C = false; + Registers.V = false; + + Cycles = 0; + } + + [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; + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public void Run(int instructions) + { + var decoder = _decoder; + + var fetchPtr = _fetchPtr; + var fetchMask = _fetchMask; + var regionId = _currentRegionId; + + while (instructions-- > 0) + { + var pc = Registers.PC; + + // FAST GUARD + if ((pc >> 28) != regionId) + { + // FALLBACK + UpdateFetchCache(pc); + + fetchPtr = _fetchPtr; + fetchMask = _fetchMask; + regionId = _currentRegionId; + + if (fetchPtr == null) + break; + } + + // ULTRA-FAST FETCH + var opcode = Unsafe.ReadUnaligned(fetchPtr + (pc & fetchMask)); + + // PRE-UPDATE PC (Speculative) + Registers.PC = pc + 2; + + Cycles++; + + // DISPATCH + decoder.Dispatch(opcode, this); + } + + _currentRegionId = regionId; + _fetchPtr = fetchPtr; + _fetchMask = fetchMask; + } + + [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 cycles) + } + + [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..ef99387 100644 --- a/src/RP2040.Core/Cpu/InstructionDecoder.cs +++ b/src/RP2040.Core/Cpu/InstructionDecoder.cs @@ -6,242 +6,239 @@ 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), + // 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 + 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/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index 56aad08..7cb7ac3 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -1,272 +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 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; + } } diff --git a/src/RP2040.Core/Cpu/Instructions/BitOps.cs b/src/RP2040.Core/Cpu/Instructions/BitOps.cs index 7b9bfe7..41df678 100644 --- a/src/RP2040.Core/Cpu/Instructions/BitOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/BitOps.cs @@ -1,284 +1,284 @@ using System.Buffers.Binary; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; 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..75cb1fb 100644 --- a/src/RP2040.Core/Helpers/InstructionEmiter.cs +++ b/src/RP2040.Core/Helpers/InstructionEmiter.cs @@ -1,295 +1,370 @@ 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) + { + 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) + { + 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..91cb326 100644 --- a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs @@ -8,123 +8,163 @@ 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); + + // 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); + 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); + 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..2d2276b 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -6,822 +6,863 @@ 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(); + } + + [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 + { + [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 + + + +