Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions tests/RP2040Sharp.Tests/Cpu/Instructions/ArithmeticOpsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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();
}
}
}

Expand Down
16 changes: 16 additions & 0 deletions tests/RP2040Sharp.Tests/Cpu/Instructions/BitOpsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading