From cfa8acad566e5ea70949f498174540e20d6ce1ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Dom=C3=ADnguez?= Date: Mon, 2 Feb 2026 03:33:59 -0600 Subject: [PATCH 1/3] Implements SBCS instruction Adds the SBCS (Subtract with Carry) instruction to the instruction decoder and arithmetic operations. This instruction performs subtraction with carry, taking into account the carry flag in the CPSR. A new opcode rule is added to the instruction decoder to map the instruction. Additionally, a test case is added to verify the instruction's correctness. --- src/RP2040.Core/Cpu/InstructionDecoder.cs | 2 ++ .../Cpu/Instructions/ArithmeticOps.cs | 12 ++++++++++ src/RP2040.Core/Helpers/InstructionEmiter.cs | 11 ++++++--- .../Cpu/InstructionDecoderTests.cs | 1 + .../Cpu/Instructions/ArithmeticOpsTests.cs | 24 +++++++++++++++++++ 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/RP2040.Core/Cpu/InstructionDecoder.cs b/src/RP2040.Core/Cpu/InstructionDecoder.cs index ef99387..c41969c 100644 --- a/src/RP2040.Core/Cpu/InstructionDecoder.cs +++ b/src/RP2040.Core/Cpu/InstructionDecoder.cs @@ -84,6 +84,8 @@ public InstructionDecoder() new OpcodeRule(0xFFC0, 0xBAC0, &BitOps.Revsh), // REV (Rd, Rn) new OpcodeRule(0xFFC0, 0xBA00, &BitOps.Rev), + // SBCS (Rn, Rm) + new OpcodeRule(0xFFC0, 0x4180, &ArithmeticOps.Sbcs), // ================================================================ // GROUP 4: Mask 0xFF87 (High Register Special Cases) // CRITICAL: These are specific cases of the 0xFF00 generic group. diff --git a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index 7cb7ac3..e7986f9 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -205,6 +205,18 @@ public static void Rsbs(ushort opcode, CortexM0Plus cpu) ptrRd = SubWithFlags(cpu, 0, valRn); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Sbcs(ushort opcode, CortexM0Plus cpu) + { + var rm = (opcode >> 3) & 0x7; + var rdn = opcode & 0x7; + + ref var ptrRd = ref cpu.Registers[rdn]; + var valRn = cpu.Registers[rm]; + + ptrRd = SubWithFlags(cpu, ptrRd, valRn + (uint)(1 - (cpu.Registers.C ? 1 : 0))); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SubsImmediate3(ushort opcode, CortexM0Plus cpu) { diff --git a/src/RP2040.Core/Helpers/InstructionEmiter.cs b/src/RP2040.Core/Helpers/InstructionEmiter.cs index 75cb1fb..adc24b2 100644 --- a/src/RP2040.Core/Helpers/InstructionEmiter.cs +++ b/src/RP2040.Core/Helpers/InstructionEmiter.cs @@ -325,13 +325,18 @@ public static ushort Rev16(uint rd, uint rn) public static ushort Rsbs(uint rd, uint rn) { - if (rd > 15) - throw new ArgumentException(HighRegisterIndexOutOfRange); - if (rn > 15) + if (rd > 15 || rn > 15) throw new ArgumentException(HighRegisterIndexOutOfRange); return (ushort)(0x4240 | (rn & 0x7) << 3 | (rd & 0x7)); } + public static ushort Sbcs(uint rn, uint rm) + { + if (rm > 7 || rn > 7) + throw new ArgumentException(HighRegisterIndexOutOfRange); + return (ushort)(0x4180 | ((rm & 0x7) << 3) | (rn & 0x7)); + } + public static ushort SubSp(uint imm) { if (imm > 508) diff --git a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs index 91cb326..b500cfc 100644 --- a/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/InstructionDecoderTests.cs @@ -87,6 +87,7 @@ public static TheoryData GetInstructionTestCases() ); Add("Muls", InstructionEmiter.Muls(R0, R2), &ArithmeticOps.Muls); Add("Rsbs", InstructionEmiter.Rsbs(R0, R3), &ArithmeticOps.Rsbs); + Add("Sbcs", InstructionEmiter.Sbcs(R0, R3), &ArithmeticOps.Sbcs); 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); diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs index 2d2276b..df3edc2 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -865,4 +865,28 @@ public void Should_Execute_SubsR5R3R2_And_Set_ZCFlags() Cpu.Registers.V.Should().BeFalse(); } } + + public class Sbcs : CpuTestBase + { + [Fact] + public void Should_Execute_SbcsR0R3() + { + // Arrange + var opcode = InstructionEmiter.Sbcs(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R0 = 100; + Cpu.Registers.R3 = 55; + Cpu.Registers.C = false; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.R0.Should().Be(44); + Cpu.Registers.N.Should().BeFalse(); + Cpu.Registers.Z.Should().BeFalse(); + Cpu.Registers.C.Should().BeTrue(); + Cpu.Registers.V.Should().BeFalse(); + } + } } From 0357725415da1a320b901d0a6ad61a5da933956f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Dom=C3=ADnguez?= Date: Mon, 2 Feb 2026 23:18:45 -0600 Subject: [PATCH 2/3] Adding test left Implementing test when R0 should be 0 and also checking the substraction of negative result. --- .../Cpu/Instructions/ArithmeticOps.cs | 6 +++--- src/RP2040.Core/Helpers/InstructionEmiter.cs | 2 +- .../Cpu/Instructions/ArithmeticOpsTests.cs | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index e7986f9..f90cec7 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -211,10 +211,10 @@ public static void Sbcs(ushort opcode, CortexM0Plus cpu) var rm = (opcode >> 3) & 0x7; var rdn = opcode & 0x7; - ref var ptrRd = ref cpu.Registers[rdn]; - var valRn = cpu.Registers[rm]; + ref var ptrRdn = ref cpu.Registers[rdn]; + var valRm = cpu.Registers[rm]; - ptrRd = SubWithFlags(cpu, ptrRd, valRn + (uint)(1 - (cpu.Registers.C ? 1 : 0))); + ptrRdn = SubWithFlags(cpu, ptrRdn, valRm + (uint)(1 - (cpu.Registers.C ? 1 : 0))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/RP2040.Core/Helpers/InstructionEmiter.cs b/src/RP2040.Core/Helpers/InstructionEmiter.cs index adc24b2..24de245 100644 --- a/src/RP2040.Core/Helpers/InstructionEmiter.cs +++ b/src/RP2040.Core/Helpers/InstructionEmiter.cs @@ -333,7 +333,7 @@ public static ushort Rsbs(uint rd, uint rn) public static ushort Sbcs(uint rn, uint rm) { if (rm > 7 || rn > 7) - throw new ArgumentException(HighRegisterIndexOutOfRange); + throw new ArgumentException(LowRegisterIndexOutOfRange); return (ushort)(0x4180 | ((rm & 0x7) << 3) | (rn & 0x7)); } diff --git a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs index df3edc2..7265fb1 100644 --- a/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040.Core.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -888,5 +888,26 @@ public void Should_Execute_SbcsR0R3() Cpu.Registers.C.Should().BeTrue(); Cpu.Registers.V.Should().BeFalse(); } + + [Fact] + public void Should_Execute_SbcsR0R3_To_Be_0() + { + // Arrange + var opcode = InstructionEmiter.Sbcs(R0, R3); + Bus.WriteHalfWord(0x20000000, opcode); + Cpu.Registers.R0 = 0; + Cpu.Registers.R3 = 0xffffffff; + 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().BeFalse(); + Cpu.Registers.V.Should().BeFalse(); + } } } From 32fc16123972c2fe8eb4e5586f09a34aedd9719a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Montiel=20Cardona?= Date: Sat, 14 Feb 2026 22:59:19 -0600 Subject: [PATCH 3/3] fix(arm): correct SBC implementation to use AddWithFlags Fix the subtract-with-carry (SBC) operation in ArithmeticOps.cs to use AddWithFlags with the two's-complement operand and an explicit carry value. Previously the code attempted to compute SBC by subtracting (valRm + (1 - C)) via SubWithFlags, which produced incorrect flag behavior and was harder to reason about. Changes: - read carry into a local uint (carry = C ? 1 : 0) - call AddWithFlags(cpu, rdn, ~valRm, carry) to implement rdn - rm - (1 - C) using two's-complement addition This makes the implementation correct for borrow/carry handling and aligns with canonical SBC semantics while preserving CPU flags. --- src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs index f90cec7..2be500b 100644 --- a/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs +++ b/src/RP2040.Core/Cpu/Instructions/ArithmeticOps.cs @@ -213,8 +213,9 @@ public static void Sbcs(ushort opcode, CortexM0Plus cpu) ref var ptrRdn = ref cpu.Registers[rdn]; var valRm = cpu.Registers[rm]; + var carry = cpu.Registers.C ? 1u : 0u; - ptrRdn = SubWithFlags(cpu, ptrRdn, valRm + (uint)(1 - (cpu.Registers.C ? 1 : 0))); + ptrRdn = AddWithFlags(cpu, ptrRdn, ~valRm, carry); } [MethodImpl(MethodImplOptions.AggressiveInlining)]