|
| 1 | +# CircuitPython WASM Port Design Philosophy |
| 2 | + |
| 3 | +## The Triple Nature of WASM |
| 4 | + |
| 5 | +WASM is simultaneously three things that are separate in hardware: |
| 6 | + |
| 7 | +| Layer | Hardware | WASM | |
| 8 | +|-------|----------|------| |
| 9 | +| **Port** | ARM Cortex-M, RISC-V, etc. | Emscripten/WebAssembly compilation | |
| 10 | +| **Silicon** | The actual chip (RP2040, SAMD51, etc.) | Virtual hardware simulation (`virtual_hardware.c`) | |
| 11 | +| **Board** | PCB with specific pins/peripherals wired | Configuration of which peripherals exist | |
| 12 | + |
| 13 | +In hardware, these are physically separate: |
| 14 | +- **Port**: The CPU architecture (different compiler, different instructions) |
| 15 | +- **Silicon**: The actual chip design (GPIO registers at specific addresses) |
| 16 | +- **Board**: How the chip is wired on a PCB (which pins go where) |
| 17 | + |
| 18 | +**In WASM, these collapse into one:** |
| 19 | +- We compile to WASM (port) |
| 20 | +- We simulate the chip (silicon) |
| 21 | +- We configure what exists (board) |
| 22 | + |
| 23 | +All in the same codebase. |
| 24 | + |
| 25 | +## Proposed Layer Architecture |
| 26 | + |
| 27 | +### Layer 1: Port (`ports/webassembly/`) |
| 28 | +**Responsibility**: WASM/Emscripten-specific infrastructure that would be the same regardless of what we're simulating. |
| 29 | + |
| 30 | +**Files:** |
| 31 | +- `Makefile` - Build orchestration (Emscripten flags, JS library integration) |
| 32 | +- `mpconfigport.h` - Python VM configuration for WASM |
| 33 | +- `mphalport.c/h` - HAL for WASM/Emscripten (timing, stdout, etc.) |
| 34 | +- `main.c` - Entry point, REPL setup |
| 35 | +- **C/JS Boundary:** |
| 36 | + - `library.js` - Emscripten library functions (exported to WASM) |
| 37 | + - `api.js` - JavaScript API (what developers use) |
| 38 | + |
| 39 | +### Layer 2: Silicon (`ports/webassembly/silicon/`) |
| 40 | +**Responsibility**: The virtual hardware simulation - the "chip" we're simulating. |
| 41 | + |
| 42 | +**Files:** |
| 43 | +- `virtual_hardware.c/h` - Hardware simulation (GPIO, I2C, SPI, UART state) |
| 44 | +- `shared_memory.h` - C/JS shared memory layout (the "hardware registers") |
| 45 | +- `virtual_clock.c/h` - Virtual timing system |
| 46 | +- **Common-HAL implementations** (`common-hal/*/`) |
| 47 | + - These call into virtual_hardware functions |
| 48 | + - This is where Python `pin.value = True` becomes `virtual_gpio_set_value()` |
| 49 | + |
| 50 | +**JavaScript Runtime Integration:** |
| 51 | +- `virtual_clock.js` - JS side of timing |
| 52 | +- `virtual_gpio.js` - JS side of GPIO (future) |
| 53 | +- `virtual_i2c.js` - JS side of I2C simulation (future) |
| 54 | + |
| 55 | +### Layer 3: Variant (`ports/webassembly/variants/VARIANT/`) |
| 56 | +**Responsibility**: Different simulation configurations, NOT different physical boards. |
| 57 | + |
| 58 | +**Variants we might have:** |
| 59 | +- `standard/` - Full CircuitPython with all common modules |
| 60 | +- `minimal/` - Bare minimum for testing (like MicroPython unix/minimal) |
| 61 | +- `display/` - Standard + displayio for graphics testing |
| 62 | +- `networking/` - Standard + wifi/socketpool for network testing |
| 63 | +- `sensors/` - Standard + common sensor libraries pre-loaded |
| 64 | + |
| 65 | +**Files in each variant:** |
| 66 | +- `mpconfigvariant.mk` - Which CircuitPython modules to enable |
| 67 | +- `mpconfigvariant.h` - Variant-specific Python VM settings (if needed) |
| 68 | + |
| 69 | +**NO board-specific pins.c** - Pins are virtual, defined once at silicon level. |
| 70 | + |
| 71 | +## The C/JS Boundary |
| 72 | + |
| 73 | +This is critical and currently unclear. Here's the proposed clean separation: |
| 74 | + |
| 75 | +### C Side Exports (WASM → JavaScript) |
| 76 | +**In `library.js` (Emscripten `--js-library`):** |
| 77 | +```c |
| 78 | +// These are the ONLY functions C calls into JavaScript: |
| 79 | +EM_JS(void, js_gpio_changed, (int pin, int value), { |
| 80 | + // Notify JS that GPIO changed |
| 81 | +}); |
| 82 | + |
| 83 | +EM_JS(void, js_i2c_transaction, (int address, uint8_t* data, int len), { |
| 84 | + // Notify JS of I2C transaction |
| 85 | +}); |
| 86 | +``` |
| 87 | +
|
| 88 | +### JavaScript Side Exports (JavaScript → WASM) |
| 89 | +**In `api.js` (loaded via `--pre-js`):** |
| 90 | +```javascript |
| 91 | +// These are the ONLY functions JavaScript calls into C: |
| 92 | +export async function loadCircuitPython(options) { |
| 93 | + const Module = await _createCircuitPythonModule(); |
| 94 | +
|
| 95 | + return { |
| 96 | + // High-level API |
| 97 | + runPython: (code) => Module.ccall('mp_js_do_str', ...), |
| 98 | + runFile: (path) => Module.ccall('mp_js_do_file', ...), |
| 99 | +
|
| 100 | + // Hardware access (read-only from JS) |
| 101 | + getGPIOValue: (pin) => Module._virtual_gpio_get_value(pin), |
| 102 | + getI2CState: () => Module._virtual_i2c_get_state(), |
| 103 | +
|
| 104 | + // Virtual clock control (JS drives the clock) |
| 105 | + advanceTime: (ms) => Module._virtual_clock_advance(ms), |
| 106 | +
|
| 107 | + // Filesystem |
| 108 | + FS: Module.FS, |
| 109 | + }; |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +## Ownership and Responsibility |
| 114 | + |
| 115 | +| Component | Owner | Responsibility | |
| 116 | +|-----------|-------|----------------| |
| 117 | +| **CircuitPython VM** | CircuitPython core | Python execution | |
| 118 | +| **Supervisor** | CircuitPython core | Boot sequence, timing, safe mode | |
| 119 | +| **Common-HAL** | Port (webassembly) | Hardware abstraction API implementation | |
| 120 | +| **Virtual Hardware** | Port (silicon layer) | Simulated chip state | |
| 121 | +| **Shared Memory** | Port (silicon layer) | C/JS data bridge | |
| 122 | +| **JavaScript Runtime** | Port (api.js) | Browser/Node.js integration | |
| 123 | +| **Virtual Devices** | TypeScript/JS | Simulated I2C devices, displays, etc. | |
| 124 | + |
| 125 | +## Build Flow |
| 126 | + |
| 127 | +``` |
| 128 | +User runs: make VARIANT=standard |
| 129 | +
|
| 130 | +1. circuitpy_mkenv.mk loads variant configuration |
| 131 | +2. mpconfigvariant.mk sets CIRCUITPY_* flags |
| 132 | +3. circuitpy_defns.mk discovers modules to build based on flags |
| 133 | +4. Port Makefile compiles: |
| 134 | + - CircuitPython core (py/) |
| 135 | + - Supervisor (supervisor/) |
| 136 | + - Common-HAL implementations (common-hal/) |
| 137 | + - Virtual hardware (silicon/) |
| 138 | + - Port infrastructure (mphalport.c, etc.) |
| 139 | +5. Emscripten links everything to circuitpython.wasm |
| 140 | +6. api.js wraps WASM in JavaScript API |
| 141 | +7. Output: build-VARIANT/circuitpython.mjs + circuitpython.wasm |
| 142 | +``` |
| 143 | + |
| 144 | +## Why Variant, Not Board? |
| 145 | + |
| 146 | +**Board abstraction implies:** |
| 147 | +- Different physical hardware layouts |
| 148 | +- Different pin mappings (board.D0 on Pico vs Feather) |
| 149 | +- Different peripheral availability (this board has WiFi, that doesn't) |
| 150 | + |
| 151 | +**Variant abstraction implies:** |
| 152 | +- Different feature sets of the same virtual platform |
| 153 | +- Different module combinations for different use cases |
| 154 | +- Same "hardware" (all 64 GPIO, all peripherals), just different software builds |
| 155 | + |
| 156 | +For WASM, we're NOT representing different physical boards. We're representing different **software configurations** of the same virtual platform. That's a variant, not a board. |
| 157 | + |
| 158 | +## Next Steps |
| 159 | + |
| 160 | +1. Restructure to variant-based build |
| 161 | +2. Move virtual_hardware to `silicon/` subdirectory for clarity |
| 162 | +3. Define clean C/JS boundary with documented exports |
| 163 | +4. Create multiple variants (minimal, standard, display) |
| 164 | +5. Document the "silicon specification" - what hardware we're simulating |
| 165 | + |
| 166 | +This gives us: |
| 167 | +- **Clear ownership** - Who owns each layer |
| 168 | +- **Clean boundaries** - Well-defined interfaces between layers |
| 169 | +- **Honest naming** - We're not pretending to be physical boards |
| 170 | +- **Flexibility** - Easy to add new simulation features or variants |
0 commit comments