diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f07af..a76d808 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed: high-register `ADD` read PC as the instruction address + 2 instead of + 4 + +ARMv6-M §A6.7.3 defines the value read from R15 by a high-register data-processing instruction as +the instruction address plus 4 (no alignment — that is the ADR form). The `ADD` high-register forms +(`ADD Rdn, PC`, `ADD SP, PC`, `ADD PC, PC`) read `Registers[15]` raw, which during dispatch holds +`insn + 2`, so every GCC/binutils PIC idiom (`ldr rN, [pc, #off]` + `add rN, pc`) and rustc/LLVM's +halfword jump tables landed 2 bytes short — e.g. MicroPython native modules computed a wrong GOT base +and branched wild. `MOV`/`CMP` high-register forms already compensated; `ADD` now matches them, and +tests pin the PC read of all three (including an instruction at a halfword-aligned address). + ### Fixed: garbage firmware could crash or corrupt the embedding host instead of faulting the emulated CPU The core is embedded in third-party apps (iCircuit), so a corrupt, truncated, random or merely buggy diff --git a/tests/RP2040Sharp.Tests/Cpu/Instructions/ArithmeticOpsTests.cs b/tests/RP2040Sharp.Tests/Cpu/Instructions/ArithmeticOpsTests.cs index 71473a7..61bdee0 100644 --- a/tests/RP2040Sharp.Tests/Cpu/Instructions/ArithmeticOpsTests.cs +++ b/tests/RP2040Sharp.Tests/Cpu/Instructions/ArithmeticOpsTests.cs @@ -228,6 +228,42 @@ public void Should_ReadProgramCounterAsInstructionAddressPlusFour_When_RmIsPc() Cpu.Registers[R0].Should().Be(0x20000014); } + [Fact] + public void Should_ReadPcAsInstructionPlus4_When_SourceIsPc() + { + // ARMv6-M §A6.7.3 ADD (register): a source read of R15 yields the + // address of the instruction plus 4 — no alignment, unlike ADR. + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(R6, PC); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R6] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R6].Should().Be(0x20000004); + } + + [Fact] + public void Should_ReadPcAsInstructionPlus4_When_SourceIsPc_AtHalfwordAlignedAddress() + { + // Same read at an address that is 2 mod 4: still insn + 4, never aligned down. + // Arrange + var opcode = InstructionEmiter.AddHighRegisters(R6, PC); + Bus.WriteHalfWord(0x20000002, opcode); + + Cpu.Registers.PC = 0x20000002; + Cpu.Registers[R6] = 0; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R6].Should().Be(0x20000006); + } + [Fact] public void Should_PreserveFlags_When_UsingEncodingT2_WithLowRegisters() { @@ -622,6 +658,39 @@ public void Should_SetOverflowFlag_When_ZeroIsComparedWith_HighRegisterNegative( Cpu.Registers.C.Should().BeFalse(); Cpu.Registers.V.Should().BeTrue(); } + + [Fact] + public void Should_ReadPcAsInstructionPlus4_When_ComparingAgainstPc() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(R6, PC); + Bus.WriteHalfWord(0x20000000, opcode); + + Cpu.Registers[R6] = 0x20000004; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.Z.Should().BeTrue(); + } + + [Fact] + public void Should_ReadPcAsInstructionPlus4_When_ComparingAgainstPc_AtHalfwordAlignedAddress() + { + // Arrange + var opcode = InstructionEmiter.CmpHighRegister(R6, PC); + Bus.WriteHalfWord(0x20000002, opcode); + + Cpu.Registers.PC = 0x20000002; + Cpu.Registers[R6] = 0x20000006; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers.Z.Should().BeTrue(); + } } } diff --git a/tests/RP2040Sharp.Tests/Cpu/Instructions/BitOpsTests.cs b/tests/RP2040Sharp.Tests/Cpu/Instructions/BitOpsTests.cs index 40bda9f..17f98c7 100644 --- a/tests/RP2040Sharp.Tests/Cpu/Instructions/BitOpsTests.cs +++ b/tests/RP2040Sharp.Tests/Cpu/Instructions/BitOpsTests.cs @@ -286,6 +286,22 @@ public void Should_ReadProgramCounter_WithPipelineOffset() Cpu.Registers[R3].Should().Be(0x20000004); } + [Fact] + public void Should_ReadProgramCounter_WithPipelineOffset_AtHalfwordAlignedAddress() + { + // Arrange + var opcode = InstructionEmiter.Mov(R3, PC); + Bus.WriteHalfWord(0x20000002, opcode); + + Cpu.Registers.PC = 0x20000002; + + // Act + Cpu.Step(); + + // Assert + Cpu.Registers[R3].Should().Be(0x20000006); + } + [Fact] public void Should_MoveRegisterToStackPointer_Without_ForcedAlignment() {