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
56 changes: 28 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- name: Checkout pymcu-rp2040
- name: Checkout pymcu-arm
uses: actions/checkout@v4
with:
path: pymcu-rp2040
path: pymcu-arm

- name: Checkout pymcu monorepo
uses: actions/checkout@v4
with:
repository: PyMCU/PyMCU
ref: development # TODO: remove after PR #34 is merged to main
ref: main
path: pymcu
submodules: false

Expand All @@ -39,16 +39,16 @@ jobs:
- name: Build pymcuc compiler
shell: bash
working-directory: pymcu
run: dotnet publish src/compiler/PyMCU.csproj -c Release -o ../pymcu-rp2040/build/bin --nologo
run: dotnet publish src/compiler/PyMCU.csproj -c Release -o ../pymcu-arm/build/bin --nologo

- name: Build pymcuc-rp2040 backend CLI
- name: Build pymcuc-arm backend CLI
shell: bash
working-directory: pymcu-rp2040
run: dotnet publish src/csharp/cli/PyMCU.Backend.RP2040.Cli.csproj -c Release -o build/bin --nologo
working-directory: pymcu-arm
run: dotnet publish src/csharp/cli/PyMCU.Backend.ARM.Cli.csproj -c Release -o build/bin --nologo

# ---------------------------------------------------------------------------
# Full integration tests (RP2040 LLVM pipeline). Linux only — uses native
# LLVM tools downloaded by pymcu-rp2040-toolchain for the linux-x64 platform.
# LLVM tools downloaded by pymcu-arm-toolchain for the linux-x64 platform.
# ---------------------------------------------------------------------------
integration:
name: Integration Tests (RP2040)
Expand All @@ -57,18 +57,18 @@ jobs:
contents: read

steps:
- name: Checkout pymcu-rp2040
- name: Checkout pymcu-arm
uses: actions/checkout@v4
with:
path: pymcu-rp2040
path: pymcu-arm

# Checkout monorepo to build pymcuc (compiler binary).
# TODO: replace with `pip install pymcu-compiler` once published to PyPI.
- name: Checkout pymcu monorepo
uses: actions/checkout@v4
with:
repository: PyMCU/PyMCU
ref: development # TODO: remove after PR #34 is merged to main
ref: main
path: pymcu
submodules: recursive

Expand All @@ -91,51 +91,51 @@ jobs:
working-directory: pymcu
run: |
dotnet publish src/compiler/PyMCU.csproj \
-c Release -o ../pymcu-rp2040/build/bin --nologo
-c Release -o ../pymcu-arm/build/bin --nologo

- name: Build pymcuc-rp2040 backend CLI
working-directory: pymcu-rp2040
- name: Build pymcuc-arm backend CLI
working-directory: pymcu-arm
run: |
dotnet publish src/csharp/cli/PyMCU.Backend.RP2040.Cli.csproj \
dotnet publish src/csharp/cli/PyMCU.Backend.ARM.Cli.csproj \
-c Release -o build/bin --nologo

- name: Install Python dependencies
working-directory: pymcu-rp2040
working-directory: pymcu-arm
run: |
uv venv .venv
uv pip install --python .venv pymcu-sdk
uv pip install --python .venv -e ../pymcu/lib # development stdlib
uv pip install --python .venv pymcu-rp2040-toolchain
uv pip install --python .venv pymcu-arm-toolchain
uv pip install --python .venv -e ../pymcu
uv pip install --python .venv -e . --no-deps
uv pip install --python .venv -e ../pymcu/external/pymcu-micropython
uv pip install --python .venv -e ../pymcu/external/pymcu-circuitpython

- name: Configure environment
working-directory: pymcu-rp2040
working-directory: pymcu-arm
run: |
echo "$GITHUB_WORKSPACE/pymcu-rp2040/.venv/bin" >> $GITHUB_PATH
echo "$GITHUB_WORKSPACE/pymcu-rp2040/build/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/pymcu-rp2040/.venv" >> $GITHUB_ENV
echo "$GITHUB_WORKSPACE/pymcu-arm/.venv/bin" >> $GITHUB_PATH
echo "$GITHUB_WORKSPACE/pymcu-arm/build/bin" >> $GITHUB_PATH
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/pymcu-arm/.venv" >> $GITHUB_ENV

- name: Pre-seed RP2040 toolchain cache
- name: Pre-seed ARM toolchain cache
# get_tool() downloads 300+ MB from GitHub Releases on first call.
# Run it here (outside the 120s test timeout) so the tools are cached
# before the integration tests trigger them via Rp2040LlvmToolchain.
working-directory: pymcu-rp2040
working-directory: pymcu-arm
run: |
"$GITHUB_WORKSPACE/pymcu-rp2040/.venv/bin/python3" -c "
import pymcu_rp2040_toolchain as tc
"$GITHUB_WORKSPACE/pymcu-arm/.venv/bin/python3" -c "
import pymcu_arm_toolchain as tc
for name in tc.TOOLS:
print(name, '->', tc.get_tool(name))
"

- name: Restore integration test packages
working-directory: pymcu-rp2040
working-directory: pymcu-arm
run: dotnet restore tests/integration/PyMCU.IntegrationTests.csproj --nologo

- name: Run integration tests
working-directory: pymcu-rp2040
working-directory: pymcu-arm
run: |
dotnet test tests/integration/PyMCU.IntegrationTests.csproj \
--no-restore \
Expand All @@ -151,5 +151,5 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: integration-test-results
path: pymcu-rp2040/test-results/
path: pymcu-arm/test-results/
retention-days: 15
46 changes: 10 additions & 36 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ jobs:
os: windows-latest

steps:
- name: Checkout pymcu-rp2040
# Both repos checked out as siblings so the relative csproj path
# ../../../../pymcu/extensions/pymcu-sdk/csharp/PyMCU.Backend.SDK.csproj
# resolves correctly from src/csharp/lib/ inside pymcu-arm/.
- name: Checkout pymcu-arm
uses: actions/checkout@v4
with:
path: pymcu-arm

- name: Checkout pymcu monorepo (SDK dependency)
uses: actions/checkout@v4
Expand All @@ -39,16 +44,18 @@ jobs:
path: pymcu
sparse-checkout: |
extensions/pymcu-sdk/csharp
global.json

- name: Set up .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.x"
global-json-file: pymcu/global.json

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Build wheel
working-directory: pymcu-arm
env:
DOTNET_RID: ${{ matrix.dotnet_rid }}
WHEEL_PLATFORM_TAG: ${{ matrix.wheel_platform_tag }}
Expand All @@ -58,7 +65,7 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: dist-${{ matrix.name }}
path: dist/*.whl
path: pymcu-arm/dist/*.whl
retention-days: 15

publish-pypi:
Expand All @@ -82,36 +89,3 @@ jobs:

- name: Publish to PyPI
run: uv publish dist/*

publish-gitea:
name: Publish to Gitea (private index)
needs: [build-wheels]
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
environment: gitea
permissions:
contents: read

steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- name: Connect to Headscale network
uses: tailscale/github-action@v4
with:
authkey: ${{ secrets.HEADSCALE_AUTH_KEY }}
args: --login-server ${{ secrets.HEADSCALE_URL }}

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Publish to Gitea
run: |
uv publish \
--publish-url "${{ secrets.PRIVATE_PYPI_URL }}" \
--username "${{ secrets.PRIVATE_PYPI_USERNAME }}" \
--password "${{ secrets.PRIVATE_PYPI_TOKEN }}" \
dist/*
48 changes: 33 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
# pymcu-rp2040
# pymcu-arm

RP2040 (Raspberry Pi Pico, dual ARM Cortex-M0+) support for the PyMCU compiler.
ARM (Cortex-M) support for the PyMCU compiler: LLVM-IR codegen backend + LLVM toolchain driver.

Unlike the AVR/PIC/RISC-V backends, this backend does **not** emit assembly
directly. It lowers the architecture-agnostic PyMCU IR (`.mir`) to **LLVM IR
text (`.ll`)** and hands it to LLVM, so register allocation, instruction
selection, the AAPCS calling convention and all optimization passes are done by
LLVM targeting `thumbv6m-none-eabi` (`-mcpu=cortex-m0plus`).
Unlike the AVR/PIC/RISC-V backends, this backend does **not** emit assembly directly. It lowers PyMCU's architecture-agnostic IR (`.mir`) to **LLVM IR text (`.ll`)** and hands it to LLVM — so register allocation, instruction selection, the AAPCS calling convention, and all optimization passes are done by LLVM.

Currently supported targets:

| Target | Triple | CPU |
|---|---|---|
| RP2040 (Raspberry Pi Pico) | `thumbv6m-none-eabi` | `cortex-m0plus` |

The codegen is chip-agnostic: only `-mtriple`/`-mcpu` change per target.

## Pipeline

```
pymcuc --emit-ir -> firmware.mir (target-agnostic IR, 32-bit pointers)
pymcuc-rp2040 (this pkg) -> firmware.ll (LLVM IR text)
LLVM toolchain -> opt -> llc -> ld.lld -> llvm-objcopy
+ generic boot2 (crc32) + crt0 -> firmware.bin
pymcuc --emit-ir firmware.mir (target-agnostic IR, 32-bit pointers)
pymcuc-arm (this pkg) firmware.ll (LLVM IR text)
LLVM toolchain opt llc ld.lld llvm-objcopy
+ generic boot2 (crc32) + crt0 firmware.bin
```

The flat flash image (`firmware.bin`, boot2 at offset 0) is what the RP2040Sharp
emulator's `PicoSimulation.LoadFlash(...)` consumes in the integration tests.

## Installation

```bash
pip install pymcu-arm
```

LLVM tools are resolved (in order) from the `pymcu-arm-toolchain` wheel cache,
common system install paths (`/opt/homebrew/opt/llvm/bin`, `/usr/lib/llvm/bin`),
or `PATH`. A system LLVM (`brew install llvm lld` / `apt install llvm lld`) also
works with no extra package.

## Layout

- `src/python/pymcu/backend/rp2040/` - backend plugin (wraps the AOT binary)
- `src/csharp/lib/` - `Rp2040BackendProvider` + `Targets/RP2040/Rp2040LlvmCodeGen`
- `src/csharp/cli/` - `pymcuc-rp2040` runner CLI
- `src/runtime/` - generic boot2, crt0 startup, linker script
```
src/python/pymcu/backend/rp2040/ backend plugin — wraps pymcuc-arm
src/python/pymcu/toolchain/rp2040/ LLVM toolchain driver (opt → firmware.bin)
src/csharp/lib/ Rp2040BackendProvider + Targets/RP2040/Rp2040LlvmCodeGen
src/csharp/cli/ pymcuc-arm runner CLI
src/runtime/ generic boot2 (crc32), crt0, rp2040.ld linker script
```

## Status

Expand Down
10 changes: 5 additions & 5 deletions hatch_build.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# hatch_build.py
# Custom hatchling build hook: compiles the pymcuc-rp2040 AOT binary and places
# it at src/python/pymcu/backend/rp2040/pymcuc-rp2040 before wheel packaging.
# Custom hatchling build hook: compiles the pymcuc-arm AOT binary and places
# it at src/python/pymcu/backend/rp2040/pymcuc-arm before wheel packaging.
#
# Environment variables:
# PYMCU_SKIP_DOTNET_BUILD=1
# Skip dotnet publish and use an existing binary at build/bin/pymcuc-rp2040.
# Skip dotnet publish and use an existing binary at build/bin/pymcuc-arm.
# When no binary exists there (e.g. sdist-only builds), the hook returns
# early and produces a source-only package.
# DOTNET_RID
Expand All @@ -30,7 +30,7 @@ class CustomBuildHook(BuildHookInterface):

def initialize(self, version: str, build_data: dict) -> None:
root = Path(self.root)
binary_name = "pymcuc-rp2040.exe" if sys.platform == "win32" else "pymcuc-rp2040"
binary_name = "pymcuc-arm.exe" if sys.platform == "win32" else "pymcuc-arm"

dst = root / "src" / "python" / "pymcu" / "backend" / "rp2040" / binary_name
src = root / "build" / "bin" / binary_name
Expand All @@ -49,7 +49,7 @@ def initialize(self, version: str, build_data: dict) -> None:
return
else:
csproj = (
root / "src" / "csharp" / "cli" / "PyMCU.Backend.RP2040.Cli.csproj"
root / "src" / "csharp" / "cli" / "PyMCU.Backend.ARM.Cli.csproj"
)
publish_dir = root / "build" / "bin"
publish_dir.mkdir(parents=True, exist_ok=True)
Expand Down
22 changes: 11 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ requires = ["hatchling>=1.25.0"]
build-backend = "hatchling.build"

[project]
name = "pymcu-rp2040"
name = "pymcu-arm"
version = "0.1.0a1"
description = "RP2040 (Cortex-M0+) support for pymcu-compiler: LLVM-IR codegen backend + LLVM toolchain driver"
description = "ARM (Cortex-M) support for pymcu-compiler: LLVM-IR codegen backend + LLVM toolchain driver"
readme = "README.md"
authors = [
{ name = "Iván Montiel Cardona", email = "contact@pymcu.org" }
Expand All @@ -15,14 +15,14 @@ license = { text = "MIT" }
dependencies = [
"pymcu-sdk>=0.1.0a1",
# The vendored LLVM toolchain lives in its own repository
# (~/Repos/pymcu-rp2040-toolchain, published to PyPI as
# pymcu-rp2040-toolchain, analogous to avr-gcc-build -> pymcu-avr-toolchain).
# (~/Repos/pymcu-arm-toolchain, published to PyPI as
# pymcu-arm-toolchain, analogous to avr-gcc-build -> pymcu-avr-toolchain).
# It bundles opt/llc/llvm-mc/ld.lld/llvm-objcopy and Rp2040LlvmToolchain
# prefers it via `import pymcu_rp2040_toolchain`. Until the wheel is on PyPI
# prefers it via `import pymcu_arm_toolchain`. Until the wheel is on PyPI
# the driver falls back to a system LLVM on PATH (e.g. `brew install llvm
# lld`). Once published, add the gated dependency so the rp2040 extra is
# lld`). Once published, add the gated dependency so the arm extra is
# self-contained:
# "pymcu-rp2040-toolchain>=19.1.7; (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64') or (sys_platform == 'darwin' and platform_machine == 'arm64')",
# "pymcu-arm-toolchain>=19.1.7; (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64') or (sys_platform == 'darwin' and platform_machine == 'arm64')",
]

[project.entry-points."pymcu.backends"]
Expand All @@ -36,13 +36,13 @@ pymcu-sdk = {git = "https://github.com/PyMCU/pymcu", subdirectory = "extensions/


[project.urls]
Homepage = "https://github.com/PyMCU/pymcu-rp2040"
Repository = "https://github.com/PyMCU/pymcu-rp2040"
"Bug Tracker" = "https://github.com/PyMCU/pymcu-rp2040/issues"
Homepage = "https://github.com/PyMCU/pymcu-arm"
Repository = "https://github.com/PyMCU/pymcu-arm"
"Bug Tracker" = "https://github.com/PyMCU/pymcu-arm/issues"

[tool.hatch.build.hooks.custom]
path = "hatch_build.py"

[tool.hatch.build.targets.wheel]
packages = ["src/python/pymcu"]
artifacts = ["src/python/pymcu/backend/rp2040/pymcuc-rp2040*"]
artifacts = ["src/python/pymcu/backend/rp2040/pymcuc-arm*"]
Loading
Loading