Skip to content

feat: rp2040js parity — GDB stub, dual-core fixes, I2C slave mode; remove unused USB MSC/HID#31

Merged
begeistert merged 115 commits into
masterfrom
copilot/analyze-code-improve-performance
Jun 6, 2026
Merged

feat: rp2040js parity — GDB stub, dual-core fixes, I2C slave mode; remove unused USB MSC/HID#31
begeistert merged 115 commits into
masterfrom
copilot/analyze-code-improve-performance

Conversation

@begeistert

Copy link
Copy Markdown
Collaborator

Description

Brings RP2040Sharp to functional parity with the upstream rp2040js emulator and
fixes a dormant dual-core defect, then trims dead code so the full test suite is green.

Highlights

  • GDB Remote Serial Protocol server (feat(gdb)) — the last major parity gap with
    rp2040js. Debug Core 0 with arm-none-eabi-gdb via target remote :3333: registers
    (g/p/P, incl. xPSR + MSP/PSP/PRIMASK/CONTROL), memory (m/M), c, s/vCont,
    D (detach), and BKPT-driven stop replies with PC rewind. AOT/trim-clean. The demo
    gains a --gdb flag. Verified end-to-end against arm-none-eabi-gdb (registers,
    memory, stepi, detach).

  • Dual-core now actually runs (fix(multicore)) — the SIO launch handshake was gated
    on _cpu1 == null, but Cpu1 is always wired at construction, so the §2.8.3 handshake
    never fired and Core 1 never started. Gated on a real launch flag instead. Also: Run()
    now advances time-aware peripherals by max(core0, core1) cycles, not Core 0 alone.

  • I2C slave-mode simulation (feat(i2c)) — lets external circuit simulators drive an
    I2C0/1 instance as a slave (RD_REQ / RX_FULL / TX_EMPTY / STOP_DET, multi-byte
    slave-transmit), per the DW_apb_i2c datasheet.

  • Remove unused USB MSC/HID host drivers (refactor(usb)) — the only consumer
    (iCircuit's RP2040Elm) uses UsbCdcHost alone. The MSC/HID drivers were dead code
    that never worked end-to-end (delivering a CBW during boot spun CircuitPython's USB
    ISR), which kept 5 integration tests permanently red. Removing them deletes the
    liability and turns the integration suite fully green. UsbCdcHost and FatVolume
    are untouched.

  • Release prep (docs(release)) — honest roadmap (dual-core & GDB done; PLL/XOSC/etc.
    marked as register stubs), GDB usage docs, and IsPackable=false on the demo projects
    so dotnet pack publishes only RP2040Sharp and RP2040Sharp.TestKit.

Test results: Unit 445/445 ✅ · Integration 149/149 ✅

Type of change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 🧹 Refactoring (no functional changes, just code cleanup)

💥 The USB cleanup removes public API (UsbMscHost, UsbHidHost, UsbMscProbe,
UsbHidProbe, the TestKit UsbMsc/UsbHid members, and UsbCdcHost.ExtractAllInterfaces).
No known consumer depends on these, but it is technically source-breaking.

Checklist

  • 🧪 Tests passed locally (dotnet test)
  • ✅ Added new tests for this feature/fix
  • 📝 Documentation updated (if applicable)
  • 🚫 No "Magic Strings" or hardcoded values
  • 💾 Validated against the RP2040 Datasheet (if applicable)

Screenshots / Hex Dumps (Optional)

GDB session against the running demo (--gdb):

(gdb) target remote :3333
(gdb) info registers r0
r0 0x11223344 287454020
(gdb) x/2xh 0x20000000
0x20000000: 0xbf00 0xbf00
(gdb) stepi
0x20000002 in ?? ()
(gdb) detach
[Inferior 1 (Remote target) detached]

begeistert and others added 30 commits April 30, 2026 19:27
Add all missing Thumb/Thumb-2 instructions required for general
C/C++ program execution:

Memory:
- STR/STRB/STRH (imm5, SP-relative, register variants)
- LDRB/LDRH (imm5, register variants)
- LDRSB/LDRSH (sign-extend, register only)
- STMIA with unconditional write-back

Bit operations:
- ROR (register), CLZ (Thumb-2 32-bit)
- SXTB, SXTH, UXTB, UXTH (sign/zero extend)

Control flow:
- CBZ / CBNZ (compare-and-branch, mask 0xFB00)

System:
- CPSID/CPSIE (interrupt enable/disable)
- WFI/WFE/SEV (wait/event hints)
- BKPT (configurable OnBreakpoint callback)
- SVC (triggers pending SVCall exception)

Infrastructure:
- Add interrupt state fields to Registers struct (VTOR, pending
  bitmaps, priority registers, WFI waiting flag)
- Fix VTOR TODO in ExceptionEntry (read from Registers.VTOR)
- Add CheckForInterrupts() with NoInlining (cold path)
- Add interrupt check in Run() loop (predictable branch)
- Add SetInterrupt/TriggerNmi/TriggerSysTick/TriggerPendSv helpers
- Add exception number constants to CortexM0Plus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add encoding helpers for ROR, SXTH, SXTB, UXTH, UXTB, CLZ (Thumb-2),
CBZ, CBNZ, STR/STRB/STRH (imm/SP/reg), LDRB/LDRH/LDRSB/LDRSH, STMIA,
BKPT, SVC, CPSIE, CPSID, WFI, WFE, SEV.
Cover BitOps (ROR, SXTH, SXTB, UXTH, UXTB, CLZ), FlowOps (CBZ, CBNZ),
MemoryOps stores/loads (STR/STRB/STRH, LDRB/LDRH/LDRSB/LDRSH, STMIA,
round-trips) and SystemOps (CPSID/CPSIE, WFI, WFE, SEV, BKPT, SVC).
256 tests passing.
Add ITickable interface and PpbPeripheral covering:
- SysTick (CSR/RVR/CVR), fires TriggerSysTick() on rollover when TICKINT=1
- NVIC ISER/ICER/ISPR/ICPR and IPR0-IPR7 priority registers
- SCB CPUID (0x410CC601), ICSR, VTOR, AIRCR, SHPR2/SHPR3
Update RP2040.Peripherals.csproj to reference RP2040.Core with AOT/unsafe.
GPIO OUT/OE with atomic SET/CLR/XOR, hardware divider (8-cycle latency,
division-by-zero handling for signed/unsigned), and 32 spinlocks.
ApbBridge routes region 0x4 by 16 KiB slots (bits [21:14]).
UartPeripheral: PL011 with OnByteTransmit callback and 32-byte RX FIFO.
TimerPeripheral: 64-bit microsecond counter with 4 alarms, ITickable.
IoBank0Peripheral: GPIO_STATUS/CTRL with FUNCSEL.
GpioPin: typed view into SIO GPIO state for tests/assertions.
RP2040Machine: assembles all peripherals, exposes Run()/LoadFlash()/Reset().
RP2040TestSimulation: WithBinary/WithFrequency/AddUart/AddGpio, Run*,
RunUntil, RunToBreak, Step, Reset — all chainable.
UartProbe: capture TX bytes, inject RX, Text/Lines/ByteCount.
Assertions: CortexM0Assertions (HaveRegister/PC/SP/Cycles/flags),
UartProbeAssertions (Contain/StartWith/ContainLine/HaveByteCount),
GpioAssertions (BeHigh/BeLow/BeOutput/BeInput).
PicoSimulation: pre-configured 125 MHz Pico board with UART0/1 probed.
- DmaPeripheral: 12 channels, synchronous transfer on CTRL_TRIG, chaining,
  MULTI_CHAN_TRIGGER, IRQ0/1 routing; mapped to bus region 0x5
- PwmPeripheral: 8 slices with 8.4 fixed-point clock divisor, wrap interrupt
  per slice (ITickable), CC A/B readout helpers
- AdcPeripheral: 5 channels with injectable ReadChannel callback, START_ONCE
  trigger, 12-bit result, READY flag
- RP2040Machine: wire DMA (Bus.MapDevice 0x5), PWM and ADC via ApbBridge,
  add PwmPeripheral to _tickables list
- SpiPeripheral: PL022 registers (SSPCR0/1, SSPDR, SSPSR, SSPCPSR, SSPIMSC),
  8-entry TX/RX FIFOs, synchronous transfer via OnTransfer callback, InjectByte
- I2cPeripheral: DesignWare DW_apb_i2c registers (IC_CON, IC_TAR, IC_DATA_CMD,
  IC_STATUS, FIFO levels), write/read callbacks, InjectByte for RX simulation
- RP2040Machine: register SPI0/1 @ 0x4003C000/0x40040000,
  I2C0/1 @ 0x40044000/0x40048000 via ApbBridge
- PioStateMachine: ISR/OSR shift registers, TX/RX FIFOs, CLKDIV with 8.8
  fixed-point fractional accumulator, EXECCTRL wrap/jmp-pin, SHIFTCTRL
  autopush/autopull thresholds
- PioPeripheral: 2 PIO blocks (PIO0 @ 0x50200000, PIO1 @ 0x50300000),
  32-word instruction memory, 4 state machines each; full instruction set:
  JMP (all 8 conditions), WAIT, IN, OUT, PUSH/PULL (block/non-block,
  autopush/autopull), MOV (with invert/bit-reverse), IRQ, SET
- AhbBridge: 1 MB granularity sub-router for region 0x5 (index by address
  bits [27:20]); hosts DMA @ 0x50000000, PIO0/1 @ 0x50200000/0x50300000
- RP2040Machine: wire PIO0/1 via AhbBridge, add to _tickables list
Wrap NativeMemory.AllocZeroed and NativeMemory.Free with #if !BROWSER
so the library compiles for net10.0-browser targets. On WASM, _pageTable
and _maskTable fall back to pinned managed arrays (byte*[] / uint[]),
which are supported by Blazor AOT since .NET 6. All pointer indexing
and read/write paths are identical between targets.
- Add Directory.Build.props with Authors, RepositoryUrl, license, tags,
  README.md inclusion, and MinVer PackageReference (PrivateAssets=all)
- Add PackageId + Description to RP2040.Core, RP2040.Peripherals, RP2040.TestKit
- New publish.yml: triggers on v* tags, runs tests then publishes to nuget.org
  using NUGET_API_KEY secret; --skip-duplicate prevents re-publish errors
- test.yml: add Pack step at the end to verify packability on every PR/push
Eliminates vtable dispatch overhead on interface calls to these types,
allowing the JIT to devirtualize IMemoryMappedDevice calls in the hot path.
… AhbBridge

All RP2040 peripherals expose atomic-alias windows:
  +0x1000 = XOR, +0x2000 = SET, +0x3000 = CLR

ApbBridge detects atomicType = (address >> 12) & 0x3 and performs the
read-modify-write before passing the stripped offset to the device.
AhbBridge does the same, stripping bits [13:12] before forwarding.
Applies to WriteWord, WriteHalfWord, and WriteByte.
New peripherals (all IMemoryMappedDevice):
- SysInfo  (0x40000000): CHIP_ID=0x10029927, PLATFORM=ASIC
- SysCfg   (0x40004000): NMI masks, PROC_CONFIG, PROC_IN_SYNC_BYPASS
- RESETS   (0x4000C000): RESET/WDSEL/RESET_DONE — DONE = ~RESET & 0x1FFFFFF
- Clocks   (0x40008000): 8 clock domains; SELECTED always returns 1, FC0 returns 125 MHz
- XOSC     (0x40024000): always STABLE+ENABLED; dormancy is a no-op in simulation
- PSM      (0x40010000): DONE always returns ALL_BITS (all subsystems on)
- Watchdog (0x40058000): SCRATCH0-7, TICK (RUNNING), CTRL; timer never fires
- RTC      (0x4005C000): full register set; time injected via SetDateTime()
- PADS_BANK0/QSPI (0x4001C000/0x40020000): IE/OD/DRIVE/PUE/PDE pad control
- BUSCTRL  (0x40030000): BUS_PRIORITY, PERFSEL0-3 (counters return 0)
- TBMAN    (0x4006C000): PLATFORM=ASIC (allows firmware to detect ASIC vs FPGA)

Firmware init sequences that busy-wait on RESET_DONE, Clocks SELECTED,
XOSC STABLE, or PSM DONE will now complete without spinning forever.
…K_ST

- CPUID (0x000): returns 0 (always Core0 in single-core simulation)
- INTERP0/1 (0x080/0x0C0): complete RP2040 interpolator implementation
  — SHIFT, MASK_LSB/MSB, SIGNED, CROSS_INPUT/RESULT, ADD_RAW
  — PEEK_LANE0/1/FULL: read-only result computation
  — POP_LANE0/1/FULL: result + accumulator feedback
  — ACCUM0/1_ADD, BASE_1AND0 write operations
- Multicore FIFO (0x050–0x058): FIFO_ST returns RDY=1 always; FIFO_WR
  discards silently (Core1 absent); FIFO_RD returns 0
- SPINLOCK_ST (0x05C): returns bitmask of currently claimed spinlocks
- GPIO_HI_IN/OUT/OE with SET/CLR/XOR aliases for QSPI GPIO pads
- Size extended to 0x10000 to cover spinlocks at 0x100–0x17C
Added to APB bus:
  SysInfo@0x40000000, SysCfg@0x40004000, Clocks@0x40008000,
  Resets@0x4000C000, Psm@0x40010000, PadsBank0@0x4001C000,
  PadsQspi@0x40020000, Xosc@0x40024000, Busctrl@0x40030000,
  Watchdog@0x40058000, Rtc@0x4005C000, Tbman@0x4006C000

All peripherals are exposed as public properties for test access.
Existing peripherals (UART, SPI, I2C, ADC, PWM, Timer, DMA, PIO,
IO_BANK0, PPB, SIO) retain their addresses unchanged.
- PioStateMachine: add SidesetCount/Base, InBase, SideEn, SidePinDir, StatusSel/N helpers
- PioPeripheral: sideset application, FDEBUG tracking (TXOVER), dynamic FLEVEL, dynamic INTR
  (RX not empty / TX not full per SM + IRQ flags), CheckInterrupts→NVIC (PIO0_IRQ0=7..PIO1_IRQ1=10),
  MOV STATUS source=5 using EXECCTRL STATUS_SEL/N, WAIT PIN source=1 relative to IN_BASE
- DmaPeripheral: ring buffer (RING_SIZE/RING_SEL), alias registers AL1/AL2/AL3 with _TRIG variants,
  TIMER0-3 storage, fix IRQ number (DMA_IRQ0=11, DMA_IRQ1=12)
- AdcPeripheral: FCS/FIFO 4-deep, SHIFT, OVER/UNDER (write-clear), THRESH, dynamic INTR
- New stubs: PllPeripheral (CS.LOCK=1), RoscPeripheral (STATUS.STABLE+ENABLED),
  VregPeripheral (1.1V default), SsiPeripheral (SR always ready), IoQspiPeripheral (6 QSPI pins)
- RP2040Machine: register PLL_SYS@0x40028000, PLL_USB@0x4002C000, IO_QSPI@0x40018000,
  ROSC@0x40060000, VREG@0x40064000; expose Ssi property (0x18000000 in XIP region, not bus-mapped)
- PIO: delay cycles de instrucción (campo bits[12:8] menos sideset bits),
  DelayCounter en PioStateMachine; se descuenta antes de ejecutar siguiente instr
- DMA: EN no se borra al completar transfer (hardware real lo mantiene),
  BSWAP (bit 22 CTRL), CHAN_ABORT (0x444), SNIFF_CTRL/DATA (0x434/0x438),
  N_CHANNELS (0x448), FIFO_LEVELS (0x440)
- Timer: fix INTF force (almacena _intf, dispara NVIC), fix INTS incluye INTF,
  eliminadas constantes duplicadas LOCKED/SOURCE que solapaban INTR/INTE
- SIO FIFO: TX FIFO depth-8 con WOF en overflow, RX FIFO con ROE en underflow,
  FIFO_ST retorna VLD/RDY/WOF/ROE correctos, InjectFifoRx()/TryDequeueTx() públicos
- IO_BANK0: IRQ edge/level detection por pin (LEVEL_LOW/HIGH, EDGE_LOW/HIGH),
  INTR0-3 (raw, write-1-to-clear edge), PROC0_INTE/INTF/INTS0-3, PROC1_INTE/INTF
  (single-core: almacena pero no dispara), IO_IRQ_BANK0=IRQ13 via NVIC,
  UpdatePinInput() para notificar cambios de pin externo, SIZE ampliado a 0x160
- RP2040Machine: pasa Cpu a IoBank0Peripheral
- PWM: CSR_PH_CORRECT (modo phase-correct zigzag), CSR_A_INV/B_INV (inversión de
  salida aplicada en GetDutyA/B), CSR_DIVMODE almacenado, CSR_PH_ADV/PH_RET como
  strobe bits (no se almacenan), IsCountingUp() expuesto como helper
- UartPeripheral: add UARTIFLS, UARTDMACR, PL011 ID registers (0xFE0-0xFFC),
  CortexM0Plus+IRQ constructor params, CheckInterrupts() NVIC routing
- SpiPeripheral: add PL022 ID registers (0xFE0-0xFFC), loopback mode (LBM bit),
  SSPDMACR, CortexM0Plus+IRQ constructor params, CheckInterrupts()
- I2cPeripheral: add IC_SAR, full CLR register set (IC_CLR_RX_UNDER/OVER/TX_OVER/
  RX_DONE/ACTIVITY/GEN_CALL/RESTART_DET), IC_SLV_DATA_NACK_ONLY, IC_DMA_CR/TDLR/RDLR,
  IC_SDA_SETUP, IC_ACK_GENERAL_CALL, IC_FS_SPKLEN, stored SCL HCNT/LCNT,
  CortexM0Plus+IRQ params, CheckInterrupts()
- RP2040Machine: wire CPU+IRQ to Uart0(20), Uart1(21), Spi0(18), Spi1(19),
  I2c0(23), I2c1(24)
…G (Fase 4)

New test project with 71 tests covering:
- SIO hardware divider (unsigned/signed/div-by-zero/save/dirty), spinlocks
- Timer alarms (arm/fire/disarm/clear), interrupt routing (INTS/INTF force)
- UART baud rate, LCR_H, TX/RX FIFO, interrupt masking, PL011 ID registers, UARTDMACR
- DMA single/multi-word transfer, half-word, chaining, INTR clear
- PIO SET/JMP/PULL/PUSH/FLEVEL/INSTR EXEC instruction tests
- PWM counter advance/wrap, interrupts, duty readback, PH_ADV/PH_RET strobes

Bug fixes discovered via tests:
- PioPeripheral: JMP was off-by-one (added PcJumped flag to skip auto-increment)
- PioPeripheral: MOV PC destination now sets PcJumped
- PioPeripheral: FDEBUG TXSTALL (bits [3:0]) now set on blocking PULL stall
- PioPeripheral: FDEBUG TXOVER corrected to bits [11:8] (was [19:16])
- UsbPeripheral covers 0x50100000 (DPRAM 4KB) and 0x50110000 (REGS) in AHB slot 1
- Implements ADDR_ENDP0-15, MAIN_CTRL, SIE_CTRL/STATUS, BUFF_STATUS, USB_MUXING/PWR,
  USBPHY_DIRECT/TRIM, INTR/INTE/INTF/INTS with W1C semantics
- SignalBusReset() and SignalSetupPacket() helpers for test/simulation
- WriteDpram()/ReadDpram() byte-block helpers
- Wired into RP2040Machine at AHB 0x50100000 with Cpu IRQ 5
- 17 new USB tests (DPRAM, registers, interrupts); total 344 tests all passing
…se 6)

- Add TxDepth/RxDepth properties to PioStateMachine (SHIFTCTRL bits 31/30)
  FJOIN_TX: TX depth=8, RX disabled; FJOIN_RX: RX depth=8, TX disabled
- Replace all hardcoded depth=4 checks in PioPeripheral with dynamic depth:
  TXF write guard, InjectRxData, BuildFstat (full detection), BuildIntr, DoPush
- All existing stubs (XOSC, Clocks, Resets, Watchdog, SysInfo, SysCfg, PSM) verified complete
- 3 new FJOIN tests; total 347 tests all passing
- DmaPeripheral.RegisterDreq(index, Func<bool>) registers a DREQ source (indexes 0-62)
- ExecuteChannel extracts TREQ_SEL from CTRL[20:15]; beats gated by DREQ when source registered
- If DREQ stalls mid-transfer: remaining count saved, channel stays BUSY until next trigger
- TREQ=63 (PERMANENT) always ready without source lookup (zero overhead)
- Add UartPeripheral.RxDataAvailable, SpiPeripheral.RxDataAvailable, PioPeripheral.TxFifoNotFull
- Wire PIO0/1 TX/RX (DREQ 0-15), SPI0/1 TX/RX (16-19), UART0/1 TX/RX (20-23) in RP2040Machine
- 3 new DREQ tests; total 350 tests all passing
…n, PIO GPIO routing (Fase 8)

- RTC: full ITickable implementation — 1 second per 125M cycles, calendar carry with DateTime.DaysInMonth, alarm IRQ 25 with selective field matching (YEAR/MONTH/DAY/DOTW/HOUR/MIN/SEC), MATCH_ACTIVE W1C; fix ENA bit positions to avoid overlap with value fields (DAY@5, MONTH@12, HOUR@21, MIN@14, SEC@6)
- PWM: fix IRQ number — all 8 slices share IRQ 4 (PWM_IRQ_WRAP), not per-slice IRQs 4-11
- SIO: InjectFifoRx now fires IRQ 15 (SIO_IRQ_PROC0) when data available
- IoBank0: PROC1_INTS registers now return correct masked value
- ADC: add HasFifoData property for DMA DREQ integration
- GpioPin: ForceInput notifies IoBank0.UpdatePinInput for edge/level interrupts
- PIO: wire ReadGpioIn/WriteGpioPins/WriteGpioDirs callbacks to SIO+IoBank0 in RP2040Machine; fix ExecIrq REL flag (smIdx-relative); fix ExecIn/ExecOut/ExecSet/ExecMov to use PINCTRL fields and invoke GPIO callbacks; add OutCount/SetCount PINCTRL properties; FIFO depth now uses dynamic TxDepth/RxDepth (FJOIN support)
- Tests: 12 new RTC tests covering tick, CTRL LOAD latch, CTRL ACTIVE, alarm match, MATCH_ACTIVE W1C
…tests (Fase 9)

PIO ApplySideset (Fix A+B):
- SideEn enable gate is always bit 4 (0x10) of 5-bit field — not 1<<sidesetCount
- PINCTRL.SIDESET_COUNT is inclusive of enable bit, remove erroneous +1
- Sideset fires only on completing cycle (moved inside !sm.Stalled block)
- ApplySideset is now an instance method to invoke WriteGpioPins/WriteGpioDirs callbacks

Watchdog ITickable (Fix C):
- Full countdown in µs: 1µs = 125 CPU cycles at CLK_SYS=125 MHz
- CTRL_TRIGGER as write-only strobe sets REASON_FORCE and invokes OnReset
- Timer expiry sets REASON_TIMER, clears CTRL_ENABLE, invokes OnReset
- Writing LOAD reloads countdown and resets accumulator
- WatchdogPeripheral added to _tickables[] in RP2040Machine

SIO Interpolators (formula fix):
- ComputeLane: RESULT = (shifted & mask) + BASE per TRM §2.3.1.7 (was bitwise merge)
- PopLane/PopFull: update both ACCUM0=RESULT0 and ACCUM1=RESULT1 per TRM

New tests (37):
- PIO Sideset: drives pins, nonzero base, delay counter, stall no-fire,
  SideEn gate=0, SideEn gate=1
- Watchdog: SCRATCH0-7, TICK register, CTRL_TRIGGER, countdown, REASON_TIMER,
  disable-after-fire, LOAD reload
- Bus/AtomicAlias: XOR/SET/CLR aliases via APBBridge (11 tests)
- SIO INTERP: ACCUM/BASE rw, BASE_1AND0, PEEK lane0, POP advances ACCUM,
  signed sign-extend, ACCUM_ADD, INTERP1 independent, CROSS_INPUT
…tion (Fase 10)

Flash size configurable (A):
- BusInterconnect: constructor param flashSizeBytes=2MB (default), exposes FlashSize/MaskFlash
- RP2040Machine: constructor param flashSize=2MB propagated to BusInterconnect
- CortexM0Plus: _fetchMask uses Bus.MaskFlash (instance) instead of static const
- LoadFlash: validates against Bus.FlashSize with dynamic error message
- Enables boards with >2 MB Flash (e.g. RP2040-Zero 16 MB): new RP2040Machine(16*1024*1024)

Directory.Build.props consolidation (B):
- Add AllowUnsafeBlocks, LangVersion=latest, Nullable=enable, MinVerDefaultPreReleaseIdentifiers
- Remove AllowUnsafeBlocks and Nullable from each individual .csproj (covered by props)
- NuGet pipeline already in place: publish.yml + MinVer produces versioned packages on git tag v*
begeistert and others added 24 commits May 7, 2026 09:30
MicroPythonBootTests: replace raw banner capture with sys.version query
to avoid races where the banner is emitted before USB-CDC enumeration.

MicroPythonReplTests: use ExecuteCompound for 'def' statement so the
continuation prompt ('... ') is handled correctly.

MicroPythonUartTests: use ExecuteCompound for 'for' loop.

MicroPythonScriptTests (new): three tests that write Python files to
the LittleFS filesystem via WriteFile and verify auto-execution after
SoftReset:
  - Script_MainPy_RunsAutomaticallyOnBoot
  - Script_MainPy_ComputesAndPrintsArithmetic
  - Script_BootPy_RunsBeforeMainPy
…n tests

CircuitPythonBootTests: 6 tests covering no-hard-fault startup, REPL
prompt, USB-CDC enumeration, version header, sys.platform, and Hello
World output for CircuitPython 9.2.1.

CircuitPythonReplTests: 8 tests covering arithmetic, sys.version, function
definition, multi-command sequences, for-loop, board.LED pin access, and
GPIO LED control (set high / set low).

CircuitPythonScriptTests: 4 tests using the read-side FAT filesystem (no
write persistence required, as CircuitPython FAT flushes via SSI which is
not yet fully emulated):
  - Script_DefaultCodePy_RunsOnSoftReset
  - Script_Filesystem_ListdirShowsCodePy
  - Script_DefaultCodePy_ContentIsReadable
  - Script_DefaultCodePy_ExecRunsInSession
Add pre-compiled pico-examples UF2 binaries as embedded resources and
update the project file to include Firmware/**/*.uf2.

PicoExamplesFirmware: helper class to load embedded UF2 firmware by name.

New integration test suites covering pico-examples firmware:
  - AdcTests       : ADC reads, temperature sensor, UART output
  - BlinkTests     : GPIO 25 blink timing, toggle count
  - ClocksTests    : 48 MHz clock switch, UART frequency output
  - DividerTests   : hardware divider results, no stack overflow
  - DmaTests       : DMA transfer, channel IRQ, CPU health
  - GpioTests      : blink-simple output, GPIO IRQ edge injection
  - InterpTests    : interpolator results, numeric UART output
  - MulticoreTests : multicore hello, FIFO IRQ (skipped until core1 ready)
  - PioTests       : PIO state machine, UART-TX PIO, blink via PIO
  - PwmTests       : PWM init, LED fade duty-cycle variation
  - ResetTests     : peripheral reset/release, UART output
  - RtcTests       : RTC tick, alarm firing, multi-tick capture
  - SystemTests    : unique_board_id hex output, CPU health
  - TimerTests     : timer fired output, multiple firings, 3 s survival
  - UartTests      : hello_uart and hello_serial output
  - UsbTests       : CDC enumeration, hello_usb repeated transmission
  - WatchdogTests  : watchdog scratch, reboot reason, CPU survival
…roPython PIO

Agent-Logs-Url: https://github.com/begeistert/RP2040Sharp/sessions/d53aa4e1-1886-48d8-bd53-54788a75d61b

Co-authored-by: begeistert <36460223+begeistert@users.noreply.github.com>
Add full W25Q command set to SsiPeripheral:
- WRITE_ENABLE (0x06), WRITE_DISABLE (0x04)
- SECTOR_ERASE 4KB (0x20), BLOCK_ERASE 32KB (0x52),
  BLOCK_ERASE 64KB (0xD8), CHIP_ERASE (0xC7/0x60)
- PAGE_PROGRAM (0x02)
- READ_DATA (0x03), FAST_READ (0x0B)
- READ_STATUS_REGISTER1/2 (0x05/0x35)

Wire IoQspiPeripheral to monitor SS OUTOVER transitions and forward
CS assert/deassert signals to the SSI.  Attach flash pointer to SSI
in RP2040Machine constructor.
…jection

Implement CircuitPythonRunner.CreateWithWritableFsAsync():
- Phase 1: boot CircuitPython once so it initialises the FAT filesystem.
- Phase 2: snapshot the full 2 MB flash image, inject boot.py directly
  into the FAT12 partition (using fixed BPB parameters discovered
  empirically: BPS=512 SPC=1 RSVD=1 NFAT=1 SPF=7 RDE=512).
  boot.py contains 'storage.disable_usb_drive()' which suppresses the
  USB-MSC descriptor so the CIRCUITPY drive is writable from Python.
- Phase 3: restart with the modified flash image; boot.py runs on the
  hard reset, before TinyUSB initialises, enabling FS writes.

Add FAT12 helpers Fat12Get/Fat12Set for reading/writing 12-bit cluster
entries packed across byte boundaries.

PicoSimulation gains an optional withUsbCdc parameter for future use.

New integration tests (all pass):
- Script_Filesystem_IsWritableFromPython
- Script_WriteCodePy_RunsAfterSoftReset
- Script_WriteCodePy_ComputesAndPrintsArithmetic
…mprove-performance

# Conflicts:
#	src/RP2040.TestKit/Boards/PicoSimulation.cs

Co-authored-by: begeistert <36460223+begeistert@users.noreply.github.com>
UsbHidHost subscribed to OnEndpointRead and replied with an empty
EndpointReadDone() every time the device armed its HID OUT endpoint.
On CircuitPython that endpoint is sparse (host only writes when
SendReport() is called), so the empty-completion arm/re-arm cycle
became a tight loop that starved the firmware of CPU and prevented
the REPL prompt from ever appearing.

Drop the OnEndpointRead subscription entirely; outgoing reports are
delivered explicitly via SendReport(), which queues the data through
EndpointReadDone() and lets the firmware consume it on the next arm.

Restores 17 CircuitPython integration tests that depend on
WaitForPrompt().
The autopull check ran AFTER bit extraction and only when OsrCount==0,
which ignored the configured PULL_THRESH and let the first OUT after
RESTART extract garbage from a stale OSR. With autopull enabled and a
threshold below 32 (e.g. 8 bits), this meant programs that relied on
autopull to pre-fill the OSR would emit zeros until OsrCount eventually
hit zero.

- Move the autopull check to the start of ExecOut, gated on
  shifts-so-far ≥ PULL_THRESH (RP2040 datasheet §3.5.4.4).
  When the TX FIFO is empty, stall the SM and set FDEBUG.TXSTALL.
- Initialise OsrCount=0 on RESTART (shift count = 32) so the first OUT
  after restart triggers autopull (datasheet §3.5.4.2.1), instead of
  running on the post-reset OSR=0 placeholder.
- Drop the redundant post-OUT non-blocking pull, which would have
  refilled the OSR on threshold-exact extractions even when no further
  OUT was pending.
The test asserted that rp2.PIO.OUT_LOW prints "0", but in MicroPython's
rp2 module on RP2040 the pin-init enum is IN_LOW=0, IN_HIGH=1,
OUT_LOW=2, OUT_HIGH=3 (see ports/rp2/modrp2.c). Update the expectation
to "2" and add a comment pointing at the upstream definition so the
constant is no longer ambiguous.
@rp2.asm_pio() defaults out_shiftdir to SHIFT_LEFT, so out(pins, 8)
extracts the high byte of OSR first. With sm.put(0xA5) the OSR is
0x000000A5 and the high byte is 0x00, which is what the loopback was
actually writing to GPIO. Adding out_shiftdir=PIO.SHIFT_RIGHT extracts
the low byte first so the 0xA5 sentinel reaches the pins (and SM1's
in_(pins, 8) reads it back) as the test expects.
New executable RP2040Sharp.Demo.CircuitPython.Blink that boots
CircuitPython 9.2.1 on the emulated Pico, pastes the Adafruit blink
example into the REPL via paste mode (Ctrl-E … Ctrl-D), and monitors
GPIO 25 (board.LED) while the script toggles it for ≥ 20 s of
simulated time.

The demo prints a real-time, time-stamped log of every LED state
change and a final summary (transition count, simulated runtime,
wall-clock time). Firmware is downloaded once from
downloads.circuitpython.org and cached under the system temp dir,
mirroring the existing MicroPython demo's pattern.

Run with: dotnet run --project src/RP2040Sharp.Demo.CircuitPython.Blink
Lets external circuit simulators drive an I2C0/1 instance as a slave:

- SimulateIncomingAddress/Data/Stop and SlaveAddressChanged expose the
  slave side to host code.
- Slave-transmit now queues every byte firmware writes to IC_DATA_CMD
  until STOP, so multi-byte master reads are captured in order (was a
  single overwritten byte).
- Raises RD_REQ / RX_FULL / TX_EMPTY / STOP_DET to match DW_apb_i2c.

Adds I2cTests covering slave address, receive, and transmit flows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two dual-core defects that left Core1 effectively dead:

- The SIO launch handshake was gated on `_cpu1 == null`, but RP2040Machine
  always wires Cpu1 at construction, so the §2.8.3 handshake never ran and
  Core1 never started (Core0 spun forever waiting for the FIFO echo). Gate
  on a dedicated `_core1Launched` flag instead, cleared on Reset().

- Run() advanced time-aware peripherals by Core0's cycles alone; with both
  cores live, wall-clock elapsed is max(core0, core1). Expose it via
  LastElapsedCycles and Core1Launched.

Adds DualCore_ElapsedTime_IsMaxNotSum, which now actually exercises a
running Core1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings RP2040Sharp to parity with rp2040js's GDB stub, the last major
feature gap. Debug Core 0 with arm-none-eabi-gdb via `target remote :3333`.

- GdbUtils, GdbServer, GdbConnection, IGdbTarget, GdbTcpServer under
  RP2040.Gdb (AOT/trim-clean — TcpListener verified to add no warnings).
- Supports ?, qSupported/qXfer:target.xml/qRegisterInfo/qHostInfo, g, p/P
  (incl. xPSR + MSP/PSP/PRIMASK/CONTROL), m/M, c, D (detach), vCont;c/s,
  and BKPT-driven stop replies with PC rewind.
- Registers.SetxPsr lets a debugger write the flags/IPSR.
- Demo gains a --gdb flag that serves Core 0 alongside the MicroPython REPL.

Verified end-to-end against arm-none-eabi-gdb (registers, memory, stepi,
detach) plus 13 in-process GdbServerTests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Roadmap now reflects reality: dual-core and GDB done; XOSC/ROSC/PLL/
  PSM/VREG honestly marked as register stubs (no frequency model).
- Features/usage cover I2C slave mode, USB HID/MSC host, and the --gdb
  flag with an arm-none-eabi-gdb quick-start.
- IsPackable=false on both demo projects so `dotnet pack` publishes only
  RP2040Sharp and RP2040Sharp.TestKit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The only consumer (iCircuit's RP2040Elm) uses UsbCdcHost alone for the
MicroPython REPL; nothing uses the MSC or HID host drivers. They also never
worked end-to-end: delivering a CBW to the MSC OUT endpoint during boot spun
CircuitPython's USB ISR, leaving the 5 CircuitPython MSC/HID integration tests
permanently red (a deep USB-device emulation bug). Removing the dead feature
deletes that liability and turns the integration suite fully green.

- Delete UsbMscHost, UsbHidHost, UsbMscProbe, UsbHidProbe and their tests.
- Strip MSC/HID endpoint discovery from UsbCdcHost; ExtractAllInterfaces
  collapses back to a CDC-only ExtractEndpointNumbers.
- Drop UsbMsc/UsbHid from the TestKit (RP2040TestSimulation, PicoSimulation)
  and WriteFileViaMsc from CircuitPythonRunner. FatVolume stays (CDC-agnostic).

Integration suite: 149/149 green. Unit: 445 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@begeistert
begeistert requested a review from lmSeryi as a code owner June 6, 2026 07:01
@begeistert begeistert self-assigned this Jun 6, 2026
begeistert and others added 2 commits June 6, 2026 01:04
Drop the SonarQube/SonarCloud analysis from the Unit Tests job: the JDK setup,
scanner install, begin/end steps, and the opencover coverage collection that only
fed Sonar. Tests now run as a plain dotnet test. Also removes the Quality Gate
badge from the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructure the NuGet publish workflow:

- Runs only on `v*` tags or a manual workflow_dispatch (not on every push).
- Pack each package explicitly (RP2040Sharp, RP2040Sharp.TestKit) instead of
  packing the whole solution, so output is deterministic.
- Run unit tests only (the old workflow ran the full suite, pulling in the
  firmware-downloading integration tests).
- Push to nuget.org is guarded: only for `v*` tags or a manual run with
  push_public=true, and only on github.com with an API key present — so Gitea
  mirror runs don't publish.
- Always upload the packed .nupkgs as an artifact.

Versioning stays with MinVer (fetch-depth: 0): `v*` tags release a stable
version, a manual run produces a preview.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@begeistert
begeistert merged commit 1dfe8e1 into master Jun 6, 2026
4 checks passed
@begeistert
begeistert deleted the copilot/analyze-code-improve-performance branch June 6, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants