An unaligned word or halfword access is a HardFault on ARMv6-M. The emulator instead stitches bytes across the two neighbouring words and carries on in Thread mode, so firmware that computes a bad address keeps running on a plausible, wrong value rather than stopping where the real chip stops.
This is not a theoretical gap. It is what made #68 invisible: a two-byte arithmetic error in ADD (register) handed mpy_init a GOT pointer at 0x20013F7A, and instead of the HardFault a Pico takes there, the emulated ldr returned a word assembled from the tail of one GOT slot and the head of the next. The import reported success and the module came back empty. The alignment fault would have pointed at the faulting instruction on the first run.
Where
BusInterconnect performs every access with Unsafe.ReadUnaligned / Unsafe.WriteUnaligned and never checks the low address bits:
ReadHalfWord (src/RP2040Sharp/Core/Memory/BusInterconnect.cs:87), ReadWord (:104)
WriteWord (:138), WriteHalfWord (:177)
The CPU handlers in Core/Cpu/Instructions/MemoryOps.cs call straight into them, so no layer raises the fault. LDRB/STRB are byte accesses and are never affected.
Reproduction
A console app referencing RP2040Sharp, no firmware needed:
using var bus = new BusInterconnect();
var cpu = new CortexM0Plus(bus);
bus.WriteWord(0x20000100, 0xAABBCCDD);
bus.WriteWord(0x20000104, 0x11223344);
// LDR r0, [r1, r2] with r1 + r2 = 0x20000102 (halfword-aligned, not word-aligned)
bus.WriteHalfWord(0x20000000, 0x5800 | (2 << 6) | (1 << 3) | 0);
cpu.Registers.PC = 0x20000000;
cpu.Registers[1] = 0x20000102;
cpu.Registers[2] = 0;
cpu.Step();
// r0 = 0x3344AABB, PC = 0x20000002, IPSR = 0, IsLockedUp = false
// STR r0, [r1, r2] with r1 + r2 = 0x20000112
bus.WriteHalfWord(0x20000020, 0x5000 | (2 << 6) | (1 << 3) | 0);
cpu.Registers.PC = 0x20000020;
cpu.Registers[0] = 0xDEADBEEF;
cpu.Registers[1] = 0x20000112;
cpu.Registers[2] = 0;
cpu.Step();
// mem[0x20000110] = 0xBEEF0000, mem[0x20000114] = 0x0000DEAD, IPSR = 0
Measured output:
LDR from 0x20000102 -> r0=0x3344AABB PC=0x20000002 IPSR 0->0 lockedUp=False
STR to 0x20000112 -> mem[0x20000110]=0xBEEF0000 mem[0x20000114]=0x0000DEAD PC=0x20000022 IPSR=0 lockedUp=False
The store is the worse half: one misaligned str silently corrupts two unrelated words that no instruction addressed.
Expected
ARMv6-M ARM §A3.2 (Alignment support): the architecture does not support unaligned data accesses. Every word access must be word-aligned and every halfword access halfword-aligned; an access that is not generates an alignment fault. ARMv6-M has no UsageFault, so the fault is taken as HardFault (exception 3) via the usual escalation, with the exception taken before the access has any effect, so no memory is written and the destination register is unchanged.
So the emulator should, for LDR/STR/LDRH/STRH and the SP-relative and literal forms, raise EXC_HARDFAULT when the effective address is not naturally aligned, leaving registers and memory untouched. LDM/STM/PUSH/POP and the vector table fetch have the same requirement on their word addresses.
Notes
An unaligned word or halfword access is a HardFault on ARMv6-M. The emulator instead stitches bytes across the two neighbouring words and carries on in Thread mode, so firmware that computes a bad address keeps running on a plausible, wrong value rather than stopping where the real chip stops.
This is not a theoretical gap. It is what made #68 invisible: a two-byte arithmetic error in
ADD (register)handedmpy_inita GOT pointer at0x20013F7A, and instead of the HardFault a Pico takes there, the emulatedldrreturned a word assembled from the tail of one GOT slot and the head of the next. The import reported success and the module came back empty. The alignment fault would have pointed at the faulting instruction on the first run.Where
BusInterconnectperforms every access withUnsafe.ReadUnaligned/Unsafe.WriteUnalignedand never checks the low address bits:ReadHalfWord(src/RP2040Sharp/Core/Memory/BusInterconnect.cs:87),ReadWord(:104)WriteWord(:138),WriteHalfWord(:177)The CPU handlers in
Core/Cpu/Instructions/MemoryOps.cscall straight into them, so no layer raises the fault.LDRB/STRBare byte accesses and are never affected.Reproduction
A console app referencing
RP2040Sharp, no firmware needed:Measured output:
The store is the worse half: one misaligned
strsilently corrupts two unrelated words that no instruction addressed.Expected
ARMv6-M ARM §A3.2 (Alignment support): the architecture does not support unaligned data accesses. Every word access must be word-aligned and every halfword access halfword-aligned; an access that is not generates an alignment fault. ARMv6-M has no UsageFault, so the fault is taken as HardFault (exception 3) via the usual escalation, with the exception taken before the access has any effect, so no memory is written and the destination register is unchanged.
So the emulator should, for
LDR/STR/LDRH/STRHand the SP-relative and literal forms, raiseEXC_HARDFAULTwhen the effective address is not naturally aligned, leaving registers and memory untouched.LDM/STM/PUSH/POPand the vector table fetch have the same requirement on their word addresses.Notes
EmuStrictis a judgement call for the maintainer.EmuStrictalready exists for silent gaps, and this is exactly one, but an alignment fault is architectural behaviour rather than an unmodelled corner, and firmware relies on it the way it relies on any other fault.