Skip to content

Release 1.0.1-beta.2: BUSL-1.1 packages (Wireless + nanoFramework TestKit) + CI publish wiring#45

Merged
begeistert merged 21 commits into
masterfrom
feat/rp2040-pio-probe
Jun 25, 2026
Merged

Release 1.0.1-beta.2: BUSL-1.1 packages (Wireless + nanoFramework TestKit) + CI publish wiring#45
begeistert merged 21 commits into
masterfrom
feat/rp2040-pio-probe

Conversation

@begeistert

Copy link
Copy Markdown
Collaborator

Cuts the 1.0.1-beta.2 line. Brings the accumulated work since v1.0.1-beta.1 to master and, as the focus of this PR, packages and publishes the two specialized BUSL-1.1 packages alongside the MIT core.

Packaging (focus of this PR)

  • RP2040Sharp.TestKit.NanoFramework → BUSL-1.1. Overrides the MIT default from Directory.Build.props, adds package metadata, and now bundles the NanoSymbols source generator as an analyzer (analyzers/dotnet/cs) plus a buildTransitive .targets file that wires it up for NuGet consumers — mirroring the established STM32Sharp pattern.
  • RP2040.Wireless (already BUSL-1.1) is now packed and published too.
  • publish.yml gains pack steps for both BUSL packages. They pack with --no-build off the Release solution build, so the analyzer DLL and targets are present in the .nupkg.

Verified locally — both .nupkgs carry <license type="expression">BUSL-1.1</license>, and the nanoFramework package contains:

lib/net10.0/RP2040.NanoFramework.TestKit.dll
analyzers/dotnet/cs/RP2040.NanoFramework.TestKit.SourceGen.dll
buildTransitive/RP2040Sharp.TestKit.NanoFramework.targets

Also included since beta.1

CYW43439 Wi-Fi + BLE virtualization, B2 bootrom + revision selection, the nanoFramework CLR test harness (boot deployed apps, managed static/instance reads, run-until-managed-method, NanoSymbols generator), PIO FIFO probe + PIO RESET gating, SSI direct-command flash RX fix, push-model USB CDC OUT delivery, and emulator strict mode.

The MIT core (RP2040Sharp, RP2040Sharp.TestKit) stays MIT. No breaking API changes.

🤖 Generated with Claude Code

begeistert and others added 21 commits June 15, 2026 22:15
Timer INTR de-asserts NVIC lines on clear, BUSCTRL PERFSEL defaults,
DMA DREQ resume, IoBank0 GPIO_COUNT-bounded input + external-input seam,
PIO IRQ caching + TX/RX consume hooks, SIO OnGpioChanged, plus the
CircuitPython FS helper. Enables real MicroPython/CircuitPython on the
emulated Pico W.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d completions

The CDC host fabricated zero-length bulk-OUT completions on every empty
arm, a re-arm busy-loop that under load (Wi-Fi association) delivered a
completion after TinyUSB deactivated the endpoint -> hw_endpoint_xfer_continue
panic -> HardFault/lockup ('USB-deaf after associate'). Now the OUT endpoint
stays armed and is fulfilled when bytes arrive (matching the RP2350 host).
EP_ABORT also drops any deferred completion for the aborted buffer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A firmware panic() spins on BKPT; the capturing handler appended each hit
unboundedly (2 GB List -> OutOfMemoryException). Cap at 1<<16; the panic is
fully observable from the first entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pin-level gSPI slave, SDPCM/WLC ioctls, firmware download, VirtualNet
(DHCP/ARP/TCP) + VirtualSwitch, and an HCI controller with BtSharedBus +
VirtualBleRadio/BleLink so real btstack runs over a virtual BLE air.

Two precision fixes so the result is independent of host time-slicing:
- GSpiSlave.SetInterrupt does not drive DATA mid-transaction (the host-wake
  line, shared with the gSPI DATA bits); EndTransaction applies it when idle.
- read-response trailing fill is 0x00, not the host-wake 0xFF — an over-clocked
  register read under heavy cross-device traffic otherwise returns 0xFF, which
  the cyw43 driver rejects ('cyw43_kso_set: failed').
- HciController exposes a public cross-assembly bridging seam (DeliverAdvReport/
  OpenExternalLink/...) so a controller can link to a peer in another assembly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pico W brings up WLAN and scans a virtual AP; BLE reaches 'BLE True' with a
real HCI bring-up over the emulated CYW43439.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add EmuStrict, an opt-in sink (EMU_STRICT=1) that records the places the
emulator would otherwise return 0 / no-op / fall back to a benign default for
something it does not actually model. Hooked at the systematic choke points:

- ApbBridge: access to an unmapped APB peripheral
- BusInterconnect: read/write to an unmapped region
- PioPeripheral: reserved MOV/IN instruction encodings (e.g. the WAIT `_ => true`
  class that silently "never waits")

Off by default (zero cost) and behaviour-neutral (NoteRet returns the same
fallback); writes an aggregated report to EMU_STRICT_OUT on flush. Turns the
"we never noticed the PIO bug" class of silent inaccuracy into a visible list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the guest's SoftAP SSID from the bsscfg:ssid iovar (Sdpcm.ApSsid) so the
virtual air can advertise a guest-AP to other stations' scans. Mark clmload_status
(0 = DLOAD_STATUS_SUCCESS) and the empty event/mcast lists as intentionally
modelled, and route every other GET_VAR/SET_VAR iovar through EmuStrict so the
unmodelled cyw43 surface (e.g. the ignored clmload regulatory blob) is recorded
rather than silently returning bogus zeros.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HIL comparison against a real RP2040 B2 Pico (picotool save -r 0x0 0x4000) showed the emulator
embedded only the B1 bootrom (version 2) while the chip ships B2 (version 3) — ~87.5% of bytes differ.

- Embed bootrom_b2.bin (dumped from real B2 silicon) alongside bootrom_b1.bin.
- Add RP2040BootromRevision { B1, B2 } and a RP2040Machine(bootrom:) parameter, default B1.
- LoadRealBootRom now resolves the flash-helper functions (connect_internal_flash, flash_exit_xip,
  flash_flush_cache, flash_enter_cmd_xip) to patch as BX LR from each revision's ROM function table
  instead of hardcoding B1 addresses — the addresses differ on B2 (0x2490 vs 0x24A0, …). Verified the
  resolver reproduces the historical B1 addresses exactly, so B1 is byte-identical to before.
- BootromRevisionTests covers both revisions (version byte + patched addresses).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fk7wVMze7VKTsQqy2JkgvK
Same MicroPython firmware runs in the RP2040Sharp emulator and on a real Pico (1 / 1 W); both execute
the same deterministic one-liner over the REPL and the printed result is compared bit-for-bit. Opt-in
(RP2040_HIL=1); drives the board over USB-CDC (flashing from BOOTSEL via picotool when needed) and
skips cleanly when the env var, firmware UF2 or board are absent. Adds System.IO.Ports for serial.
Verified identical on emulator vs a real RP2040 B2 Pico 1 W.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fk7wVMze7VKTsQqy2JkgvK
Verified-against-source draft to file upstream: adc.ts activeChannel setter masks the channel with
CS_AINSEL_SHIFT (12) instead of CS_AINSEL_MASK (0x7), corrupting AINSEL and breaking round-robin.
README documents the verification method and the ~19 other candidates that were investigated and
dismissed (rp2040js is correct; they were the port's own fixes or convention differences).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fk7wVMze7VKTsQqy2JkgvK
The behavioral HIL differential (HilDifferentialTests) talks to a real Pico over USB-CDC; that
hardware-checking code is private and must not live in the repo. Removed from the tree and the
System.IO.Ports dependency it needed. The emulator features it informed (B2 bootrom support and its
unit tests) stay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fk7wVMze7VKTsQqy2JkgvK
Adds OnTxPush/OnRxPull events to PioPeripheral (fired on firmware TXF write / RXF read),
a PioProbe + FifoCapture in RP2040.TestKit, and AddPioProbe on RP2040TestSimulation —
mirroring RP2350Sharp so the RP2040 emulator tier has PIO FIFO-capture parity.
…orks

The SSI only enqueued an RX byte when a flash transaction was CS-asserted, and tracked the
command via a CS-delimited buffer that was cleared on CS-assert. Firmware that drives the SSI
the way the RP2040 bootrom / a nanoFramework CLR does — clocking dummy bytes with the flash
deselected (rp_flash_exit_xip), and issuing the command byte just before the IO_QSPI/SER
chip-select edge — never advanced its RXFLR-polling loops and spun forever.

Now the RX FIFO fills on every DR0 write while the SSI is enabled (independent of chip-select),
a separate RX frame tracks the command across read/deassert boundaries (so the command byte is
preserved even when clocked before the CS edge), the RX FIFO is flushed at CS deassert, and the
0xEB Quad-I/O read returns real flash data. The CS-delimited write/erase path is unchanged.

No regressions (full RP2040Sharp suite green); enables booting a nanoCLR + running a deployed
C# app end-to-end on the emulator.
…apps

A focused test kit (mirroring STM32.TestKit.NanoFramework) to run a deployed nanoFramework app on
the RP2040Sharp emulator without hand-rolling the plumbing in every test:

- NanoApp: assembles a deployment from a directory of .pe (mscorlib first, app last, libs between),
  reading each assembly's native-methods checksum from the NFMRK1 header (offset 20).
- NanoFirmware: discovers nanoBooter*/nanoCLR* + an optional firmware.manifest.json by convention,
  and boots booter + nanoCLR + deployment at the RP2040 flash layout (0x14000 / 0xFC000).
- Checksum guard: AssertCompatible() fails fast with NanoChecksumMismatchException when a .pe's
  native checksum drifts from what the firmware provides (the classic silent nanoFramework footgun).
…ymbol

Adds a harness over the deployed-app boot that drives the emulator to points inside the running
CLR, not just wall-clock slices:

- NanoFirmware gains a symbols map (from the manifest) with ResolveSymbol/SymbolizePc.
- NanoClrHarness.RunUntilNativeCall(symbol) runs the hook-aware execution path and watches every
  executed instruction's PC via the profiling observer, detecting the exact crossing into a native
  InternalCall (records LastReachedSymbol/Cycle). RunUntil(predicate) covers generic observable state.

Crucially this uses the hook-aware path (RunProfiled), not per-instruction Cpu.Step() — Step bypasses
the bootrom/flash native hooks the firmware needs to boot, so it diverges. All nanoFramework-specific;
the pure RP2040.TestKit is untouched.
Adds a ClrLayout (in-RAM struct offsets of the nanoCLR build, from its DWARF) and a ClrInspector
that walks the running CLR's memory to resolve a managed static field by name and read its value:
g_CLR_RT_TypeSystem.m_assemblies -> match m_szName -> the .pe TypeDef/FieldDef metadata tables ->
the FieldDef cross-reference slot -> the CLR_RT_HeapBlock that holds the value.

NanoClrHarness gains ReadStaticInt32(asm,type,field) and RunUntilStatic(...,predicate) — drive the
emulator until a managed variable reaches a value (resolving fresh each check, since the CLR may
relocate the assembly/statics during load). The offsets are build-specific; ClrLayout.Default
matches the vendored RP_PICO firmware (extract with gdb 'ptype /o' for another build).
- RP2040.NanoFramework.TestKit.SourceGen: a Roslyn generator that reads the app's built managed
  assembly (.exe) via System.Reflection.Metadata and emits a strongly-typed <App>Symbols class
  (Assembly + Methods.X + Fields.Y), so tests use generated constants instead of raw strings
  (typo => compile error). Ported from STM32Sharp's NanoSymbolsGenerator.
- ClrInspector: resolve a static field by name across all types (optional type qualifier), and
  ReadStatic returns a HeapValue (dataType + raw) with AsInt32/AsUInt32/AsBoolean.
- NanoClrHarness: ReadStatic/RunUntilStatic now take (assembly, field) and a HeapValue predicate.
Rounds out the STM32-style CLR inspection (same nanoCLR, so the same ClrLayout offsets):

- RunUntilManagedMethod("Assembly!Method"): watches CLR_RT_Thread::Execute_IL and matches the
  running frame's method via MethodAt (m_call.m_assm.m_szName + m_call.m_target name) — the stack
  frame is the call arg, checked in R0/R1. Pairs with the generated AppSymbols.Methods.X.
- ClrInspector.MethodAt(stackFrame), plus instance heap-object reads: ReadArrayLength /
  ReadArrayInt32 over a managed array's CLR_RT_HeapBlock_Array header.
- NanoClrHarness.StaticArrayLength / ReadStaticArrayInt32 (a static int[] = a heap array reference).
- ClrLayout gains the MethodDef/StackFrame/array offsets (TBL_MethodDef=6, MD_Size=16, SF_CallAssm=44,
  SF_CallTarget=48, OBJ_FieldsOff=12, ARR_NumElems=12, ARR_DataOff=20) — identical to the STM32 build.
…s structure

- ReadStaticInt64: reads the cell's full 8 data bytes (low | high<<32).
- Instance fields by name: TryResolveInstanceSlot (the type's iFields + the FieldDef cross-reference
  m_offset), and ReadInstance at obj + m_offset*HB_Size — m_offset already counts past the object
  header (the nanoCLR does res = Dereference(); res += m_offset), so NO extra OBJ_FieldsOff (that was
  an off-by-one). Harness ReadStaticInt64 / ReadInstance(Int32) (object held by a static ref field).
- NanoSymbols generator now also emits a Types.<Type>.Name/.Fields.<Field> structure extracted from
  the project (all non-const fields per type), so instance fields are addressed by generated symbols.
Mirror of the RP2350Sharp change. On real RP2040 a PIO powers up clock-gated until firmware clears its
RESETS bit (PIO0=10, PIO1=11); the emulator started everything out of reset, so PIO writes were no-ops
on silicon yet 'worked' here. ResetsPeripheral now starts the PIO bits in reset + fires OnUnreset/OnReset,
PioPeripheral gates Read/WriteWord on InReset, and the machine releases the block on the RESETS
transition. Isolated unit tests keep InReset=false.
…mework TestKit)

The MIT core (RP2040Sharp, RP2040Sharp.TestKit) was the only thing the publish
workflow packed. Add the two specialized BUSL-1.1 packages to the release:

- RP2040.Wireless — CYW43439 Wi-Fi + BLE virtualization (already BUSL-1.1).
- RP2040Sharp.TestKit.NanoFramework — boot/assert deployed nanoFramework apps.
  Relicensed BUSL-1.1 (overrides the MIT default) and now bundles the NanoSymbols
  source generator as an analyzer plus a buildTransitive .targets that wires it up
  for NuGet consumers, mirroring the established STM32Sharp packaging pattern.

Both pack with --no-build off the Release solution build, so the analyzer DLL and
targets are present. CHANGELOG updated for 1.0.1-beta.2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@begeistert
begeistert requested a review from lmSeryi as a code owner June 25, 2026 15:35
@begeistert
begeistert merged commit 3b1271c into master Jun 25, 2026
4 checks passed
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.

1 participant