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
+
+
+
+