Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8dc8ab3
feat(rp2040): peripheral bring-up so the Pico W CYW43 firmware boots
begeistert Jun 16, 2026
b23502c
fix(rp2040-usb): push-model CDC OUT delivery + EP_ABORT drops deferre…
begeistert Jun 16, 2026
c71be18
feat(rp2040-testkit): cap BKPT-capture list to avoid OOM on long runs
begeistert Jun 16, 2026
a37a796
feat(rp2040-wireless): CYW43439 WiFi + BLE virtualization (BUSL-1.1)
begeistert Jun 16, 2026
ca4e33b
test(rp2040): Cyw43 WiFi + BLE integration tests
begeistert Jun 16, 2026
de0c022
feat(emu): strict mode to surface silent accuracy gaps
begeistert Jun 17, 2026
ae1c9e8
feat(cyw43): capture SoftAP SSID + flag unmodelled iovars
begeistert Jun 17, 2026
ab77901
feat(bootrom): add B2 bootrom + revision selection
begeistert Jun 22, 2026
3458b52
test(hil): behavioral hardware-in-the-loop differential vs real silicon
begeistert Jun 22, 2026
df3e880
docs(upstream): candidate issue for rp2040js (ADC AINSEL mask typo)
begeistert Jun 22, 2026
74e531d
chore: move hardware-in-the-loop code out of source (kept private)
begeistert Jun 23, 2026
9daaece
feat(testkit): add PioProbe to capture PIO FIFO traffic
begeistert Jun 24, 2026
2010f4a
fix(ssi): model direct-command flash RX so bootrom/CLR flash access w…
begeistert Jun 25, 2026
e5c44c7
feat(testkit): add RP2040.NanoFramework.TestKit for booting deployed …
begeistert Jun 25, 2026
81293d0
feat(testkit): NanoClrHarness — run the CLR to a native function by s…
begeistert Jun 25, 2026
68a8d65
feat(testkit): read managed static fields + run until a managed variable
begeistert Jun 25, 2026
8c634f2
feat(testkit): NanoSymbols source generator + richer static reads
begeistert Jun 25, 2026
7434526
feat(testkit): run-until-managed-method + read instance heap objects
begeistert Jun 25, 2026
bdb30ba
feat(testkit): int64 statics, instance fields by name, generated Type…
begeistert Jun 25, 2026
e8d0c9e
feat(pio): model peripheral RESET gating for PIO blocks
begeistert Jun 25, 2026
456bb21
ci(publish): pack & publish the BUSL-1.1 packages (Wireless + nanoFra…
begeistert Jun 25, 2026
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
16 changes: 16 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ jobs:
--no-build
--output ./nupkgs

# ── BUSL-1.1 package: CYW43439 Wi-Fi + BLE virtualization ───────────────
- name: Pack RP2040.Wireless
run: >
dotnet pack src/RP2040.Wireless/RP2040.Wireless.csproj
-c Release
--no-build
--output ./nupkgs

# ── BUSL-1.1 package: bundles the NanoSymbols generator as an analyzer ───
- name: Pack RP2040Sharp.TestKit.NanoFramework
run: >
dotnet pack src/RP2040.NanoFramework.TestKit/RP2040.NanoFramework.TestKit.csproj
-c Release
--no-build
--output ./nupkgs

# ── Push to nuget.org (tags, or a manual push_public run) ───────────────
- name: Push to nuget.org
env:
Expand Down
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.1-beta.2] - 2026-06-25

Third prerelease of the `1.0.1` line. Ships two new **BUSL-1.1** packages and the CI
wiring to publish them, on top of the CYW43439 wireless stack, a second bootrom revision,
and a nanoFramework integration-test harness landed since `1.0.1-beta.1`.

No breaking changes to the MIT core — `RP2040Sharp` and `RP2040Sharp.TestKit` remain MIT.

### Added

- **`RP2040.Wireless` (BUSL-1.1)** — firmware-agnostic CYW43439 Wi-Fi + Bluetooth LE
virtualization for the Raspberry Pi Pico W (gSPI/PIO, SDIO backplane, WHD/SDPCM, BT HCI),
now packaged and published to nuget.org.
- **`RP2040Sharp.TestKit.NanoFramework` (BUSL-1.1)** — boot a deployed .NET nanoFramework
app on the emulator and assert against it: managed static/instance fields by name,
run-until-managed-method, and a bundled **NanoSymbols** source generator (shipped as an
analyzer with a `buildTransitive` targets file) that emits strongly-typed symbols for the
app's methods and static fields.
- **B2 bootrom + revision selection**, **PIO FIFO probe**, and an emulator **strict mode**
that surfaces silent accuracy gaps.

### Changed

- `publish.yml` now packs and publishes the two BUSL-1.1 packages alongside the MIT core.

### Fixed

- **SSI direct-command flash RX** so the bootrom/CLR flash-access path works.
- **USB CDC** OUT delivery moved to a push model; `EP_ABORT` no longer drops deferred
completions.

## [1.0.0] - 2026-06-06

First stable release. Promotes `1.0.0-rc.2` after the TestKit gained CI guardrails for
Expand Down Expand Up @@ -81,7 +112,8 @@ First public release candidate. A high-performance RP2040 emulator in modern C#
- Flash programming uses C# hooks rather than the SSI XIP hardware path.
- USB host support is CDC-only (sufficient for the MicroPython REPL).

[Unreleased]: https://github.com/PyMCU/RP2040Sharp/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/PyMCU/RP2040Sharp/compare/v1.0.1-beta.2...HEAD
[1.0.1-beta.2]: https://github.com/PyMCU/RP2040Sharp/compare/v1.0.1-beta.1...v1.0.1-beta.2
[1.0.0]: https://github.com/PyMCU/RP2040Sharp/compare/v1.0.0-rc.2...v1.0.0
[1.0.0-rc.2]: https://github.com/PyMCU/RP2040Sharp/compare/v1.0.0-rc.1...v1.0.0-rc.2
[1.0.0-rc.1]: https://github.com/PyMCU/RP2040Sharp/releases/tag/v1.0.0-rc.1
45 changes: 45 additions & 0 deletions RP2040.sln
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RP2040Sharp.Demo.CircuitPyt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RP2040Sharp.Runner", "src\RP2040Sharp.Runner\RP2040Sharp.Runner.csproj", "{2C4890EB-07AD-4197-BE40-6268A2C1DD94}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RP2040.Wireless", "src\RP2040.Wireless\RP2040.Wireless.csproj", "{62DDE19A-912F-4A89-BC09-747DE5E05345}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RP2040.NanoFramework.TestKit", "src\RP2040.NanoFramework.TestKit\RP2040.NanoFramework.TestKit.csproj", "{F81F075C-6A42-40ED-8061-2272EC8C3B3E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RP2040.NanoFramework.TestKit.SourceGen", "src\RP2040.NanoFramework.TestKit.SourceGen\RP2040.NanoFramework.TestKit.SourceGen.csproj", "{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -112,6 +118,42 @@ Global
{2C4890EB-07AD-4197-BE40-6268A2C1DD94}.Release|x64.Build.0 = Release|Any CPU
{2C4890EB-07AD-4197-BE40-6268A2C1DD94}.Release|x86.ActiveCfg = Release|Any CPU
{2C4890EB-07AD-4197-BE40-6268A2C1DD94}.Release|x86.Build.0 = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|x64.ActiveCfg = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|x64.Build.0 = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|x86.ActiveCfg = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Debug|x86.Build.0 = Debug|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|Any CPU.Build.0 = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|x64.ActiveCfg = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|x64.Build.0 = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|x86.ActiveCfg = Release|Any CPU
{62DDE19A-912F-4A89-BC09-747DE5E05345}.Release|x86.Build.0 = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|x64.ActiveCfg = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|x64.Build.0 = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|x86.ActiveCfg = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Debug|x86.Build.0 = Debug|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|Any CPU.Build.0 = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|x64.ActiveCfg = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|x64.Build.0 = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|x86.ActiveCfg = Release|Any CPU
{F81F075C-6A42-40ED-8061-2272EC8C3B3E}.Release|x86.Build.0 = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|x64.ActiveCfg = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|x64.Build.0 = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|x86.ActiveCfg = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Debug|x86.Build.0 = Debug|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|Any CPU.Build.0 = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|x64.ActiveCfg = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|x64.Build.0 = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|x86.ActiveCfg = Release|Any CPU
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -124,5 +166,8 @@ Global
{0B49F4F8-6B4B-40F0-978D-B8799AABC99C} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
{9D8E08C3-BF88-41FB-BC88-DE36F64A6157} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
{2C4890EB-07AD-4197-BE40-6268A2C1DD94} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
{62DDE19A-912F-4A89-BC09-747DE5E05345} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
{F81F075C-6A42-40ED-8061-2272EC8C3B3E} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
{54BD74A3-CFF4-444C-95F6-F7A3DD5D6837} = {F168743D-BA33-466E-AFAF-BFC9DD2AF698}
EndGlobalSection
EndGlobal
39 changes: 39 additions & 0 deletions rp2040js-issues/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Candidate upstream issues for rp2040js

Drafts for issues to file against [rp2040js](https://github.com/wokwi/rp2040js) (Uri Shaked),
the project RP2040Sharp / RP2350Sharp were ported from.

## Method (and a caveat)

These were sourced from the C# ports' "fix" commits and `rp2040js`-referencing comments, **then each
candidate was verified against the actual rp2040js 1.3.2 source** before being written up. That
verification step matters: most apparent "divergences" between the C# port and rp2040js turned out
**not** to be upstream bugs. They were either:

- bugs the C# port introduced itself and later fixed (rp2040js was correct all along), or
- internal representation/convention differences that are functionally equivalent
(e.g. the port counts OSR bits *remaining* where rp2040js counts bits *shifted* — `OsrCount > 0`
and `outputShiftCount < pullThreshold` express the same `!OSRE` condition).

Examples that were investigated and **dismissed** as upstream bugs (rp2040js is correct):
JMP PIN (reads `inputValue`), JMP `!OSRE`, IN/OUT shift-by-32 (handled via the `bitCount === 0`
special case — JS bit-ops are mod-32 too, but the encoding never passes a literal 32), autopull
timing (already checked at the start of OUT), autopush stall (ISR value is preserved via
`wait(..., inputShiftReg)` and re-pushed on wake), PUSH/PULL no-block, exception-return
`eventRegistered`, `IC_COMP_PARAM_1` (returns 0), PWM phase-correct start direction
(`countingUp = true` default + `TimerMode.ZigZag`).

## Confirmed issues

| File | Component | Confidence |
|---|---|---|
| `adc-rrobin-ainsel-mask.md` | ADC `activeChannel` setter masks with `CS_AINSEL_SHIFT` not `CS_AINSEL_MASK` | **High** — one-line typo, root-caused in source |

## Finding more, properly

The reliable way to surface genuine upstream bugs is **differential testing**, not diffing the port's
comments: run rp2040js and a reference (QEMU `mps2-an385` Cortex-M0, or real Pico 1 silicon) on the
same firmware/instruction stream and compare register/memory state — the same technique used to
validate RP2350Sharp bit-for-bit against QEMU. That campaign is the recommended next step if more
upstream issues are wanted; the comment-mining approach has a high false-positive rate (≈1 real bug
in ~20 candidates here).
78 changes: 78 additions & 0 deletions rp2040js-issues/adc-rrobin-ainsel-mask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ADC: `activeChannel` setter masks with `CS_AINSEL_SHIFT` instead of `CS_AINSEL_MASK` (breaks round-robin / corrupts AINSEL)

**Component:** `src/peripherals/adc.ts`
**Version checked:** 1.3.2 (also present on current `main` at time of writing)

## Summary

The private `activeChannel` setter writes the channel number into `CS.AINSEL` using the wrong
constant: it ANDs the channel with `CS_AINSEL_SHIFT` (the bit *position*, `12`) instead of
`CS_AINSEL_MASK` (the field *mask*, `0x7`). This corrupts the stored channel for most values, so
round-robin conversion advances to the wrong channel (or gets stuck), and any internal update of
`AINSEL` reads back a wrong channel.

## Location / root cause

```ts
// src/peripherals/adc.ts
const CS_AINSEL_MASK = 0x7; // line 20
const CS_AINSEL_SHIFT = 12; // line 21

private set activeChannel(channel: number) {
this.cs &= ~(CS_AINSEL_MASK << CS_AINSEL_SHIFT);
this.cs |= (channel & CS_AINSEL_SHIFT) << CS_AINSEL_SHIFT; // line 151 ← BUG
}
```

`channel & CS_AINSEL_SHIFT` is `channel & 12` (`0b1100`), not `channel & 0x7`. For the valid
channel range 0–4 this yields:

| channel in | `channel & 12` (stored) |
|---|---|
| 0 | 0 |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 4 |

So setting the active channel to 1, 2 or 3 silently stores **0**.

## Impact

The round-robin sampler computes the next channel correctly but then writes it through this
setter:

```ts
// src/peripherals/adc.ts ~line 213
const round = (this.cs >> CS_RROBIN_SHIFT) & CS_RROBIN_MASK;
if (round) {
let channel = this.activeChannel + 1;
while (!(round & (1 << channel))) {
channel = (channel + 1) % this.numChannels;
}
this.activeChannel = channel; // <-- corrupted by the buggy setter
}
```

Because the setter drops the low channel bits, `AINSEL` never lands on channels 1–3, so:
- ADC round-robin does not visit channels 1, 2, 3 as configured.
- `CS.AINSEL` read back by firmware after an internal update is wrong.
- The conversion result comes from the wrong input.

(The matching getter on line 146 is correct — it uses `CS_AINSEL_MASK` — which is why the typo is
easy to miss.)

## Suggested fix

```ts
private set activeChannel(channel: number) {
this.cs &= ~(CS_AINSEL_MASK << CS_AINSEL_SHIFT);
this.cs |= (channel & CS_AINSEL_MASK) << CS_AINSEL_SHIFT; // mask, not shift
}
```

## How it was found

Surfaced while building a C# RP2040 emulator (an independent re-implementation): an ADC round-robin
test visited only channel 0 and channel 4. Tracing the divergence to the `AINSEL` field led to the
`& CS_AINSEL_SHIFT` typo in the upstream setter, confirmed against the 1.3.2 source above.
Loading
Loading