velxio/docs/CUSTOM_CHIPS.md

239 lines
8.7 KiB
Markdown

# Custom Chips — Velxio Developer Guide
Velxio supports **user-defined chips** written in C and compiled to WebAssembly.
Drop a chip on the canvas, wire its pins like any other component, and your chip
runs alongside the simulated Arduino sketch.
This is Velxio's equivalent of Wokwi's Custom Chips API — but with a clean-room
header (`velxio-chip.h`), our own runtime, and zero code from third-party
simulators. The toolchain (clang + WASI-SDK) is 100 % open source.
---
## Table of contents
- [What you get](#what-you-get)
- [The 30-second example: an inverter](#the-30-second-example-an-inverter)
- [Anatomy of a chip](#anatomy-of-a-chip)
- [The 11 example chips](#the-11-example-chips)
- [Where to read next](#where-to-read-next)
---
## What you get
A custom chip can:
| Feature | Use it for |
|---|---|
| **Digital GPIO** (`vx_pin_*`) | Logic gates, level translators, level-triggered effects |
| **Pin watch with edge detection** (`vx_pin_watch`) | Reactive chips: counters, debouncers, edge-triggered FSMs |
| **Initialized output state** (`VX_OUTPUT_LOW`/`HIGH`) | Pin defaults to a known value at chip boot — no glitch |
| **Analog out (DAC)** (`vx_pin_dac_write`) | Programmable voltage references, function generators |
| **I2C slave** (`vx_i2c_attach`) | EEPROMs, RTCs, IO expanders, sensors |
| **SPI slave** (`vx_spi_attach`/`start`/`stop`) | Shift registers, ADCs, displays, flash chips |
| **UART** (`vx_uart_attach`/`vx_uart_write`) | GPS modules, BT/WiFi modems, anything that talks Serial |
| **User-editable attributes** (`vx_attr_*`) | Knobs the user tweaks: gain, threshold, baud, etc. |
| **Timers** (`vx_timer_*`) | Periodic events, oscillators, watchdogs |
| **Framebuffer / display** (`vx_framebuffer_init`/`vx_buffer_write`) | Custom LCDs, OLEDs, dot-matrix displays |
| **Logging** (`vx_log` and `printf`) | Debug output to the chip console |
Every API call is documented in
[`wiki/custom-chips-api-reference.md`](./wiki/custom-chips-api-reference.md).
---
## The 30-second example: an inverter
A chip with one input and one output. `OUT` is always the inverse of `IN`.
### `inverter.c`
```c
#include "velxio-chip.h"
#include <stdlib.h>
typedef struct {
vx_pin in;
vx_pin out;
} chip_state_t;
static void on_in_change(void *ud, vx_pin pin, int value) {
chip_state_t *s = (chip_state_t*)ud;
vx_pin_write(s->out, value ? VX_LOW : VX_HIGH);
}
void chip_setup(void) {
chip_state_t *s = (chip_state_t*)malloc(sizeof(chip_state_t));
s->in = vx_pin_register("IN", VX_INPUT);
s->out = vx_pin_register("OUT", VX_OUTPUT);
vx_pin_write(s->out, vx_pin_read(s->in) ? VX_LOW : VX_HIGH);
vx_pin_watch(s->in, VX_EDGE_BOTH, on_in_change, s);
vx_log("inverter ready");
}
```
### `inverter.chip.json`
```json
{
"schema": "velxio-chip/v1",
"name": "Inverter",
"author": "you",
"license": "MIT",
"description": "OUT = !IN.",
"pins": ["IN", "OUT", "GND", "VCC"],
"attributes": []
}
```
### Try it in the editor
1. Open Velxio. Click **Add Component** → search "Custom Chip" → select.
2. The Custom Chip Designer opens. Switch to the **Examples** tab.
3. Click **Inverter** — code loads in the editor.
4. Click **Compile** → "✓ Compiled — 61.8 KB".
5. Click **Save & Place** — the chip appears on the canvas.
6. Wire `IN` to your Arduino's pin 13 and `OUT` to a LED.
7. Run the blink sketch → the LED toggles inverse to the built-in LED.
---
## Anatomy of a chip
A chip is **two files**:
```
mychip.c // C source — the logic of the chip
mychip.chip.json // Metadata — pin layout, attributes, optional display
```
### Lifecycle
The chip exports exactly **one** function:
```c
void chip_setup(void);
```
Velxio calls `chip_setup()` once per chip instance after the simulation starts.
Inside it the chip:
1. **Allocates state** — typically one `malloc(sizeof(chip_state_t))` per instance.
2. **Registers pins** with `vx_pin_register(name, mode)`.
3. **Attaches peripherals** if needed: `vx_i2c_attach`, `vx_uart_attach`, `vx_spi_attach`.
4. **Subscribes to events** with `vx_pin_watch` and/or `vx_timer_create` + `vx_timer_start`.
5. Returns. **No event loop.** The chip is purely reactive.
After setup, the chip runs **only** inside callbacks the host invokes:
- A pin watch fires → your callback runs → maybe writes other pins.
- The I2C bus addresses your slave → your `on_connect`/`read`/`write`/`stop` runs.
- A timer expires → your callback runs.
- A UART byte arrives → your `on_rx_byte` runs.
This means your chip never blocks, never loops forever, and uses zero CPU
between events.
### State per instance
If the user drops two of your chips on the canvas, they each get their own
`WebAssembly.Instance` with **separate memory**. Inside `chip_setup()` you
`malloc` a new state struct — there's no shared global state to worry about.
### `chip.json` schema
```json
{
"schema": "velxio-chip/v1",
"name": "Display name",
"author": "Your name",
"license": "MIT",
"description": "Short text shown in the picker tooltip",
"pins": [
"IN", // string — auto-laid out left/right
"OUT",
{ "name": "SCL", "x": 0, "y": 24 }, // object — explicit position
{ "name": "SDA", "x": 0, "y": 36 }
],
"attributes": [
{ "name": "threshold", "type": "int", "default": 4, "min": 1, "max": 1024 },
{ "name": "gain", "type": "float", "default": 1.0, "min": 0, "max": 10, "step": 0.1 }
],
"display": { "width": 128, "height": 64 } // optional — for chips with a screen
}
```
Empty strings in `pins` skip a slot (so your DIP layout matches a real chip).
`attributes` show up as sliders/inputs in the chip's properties dialog.
`display` enables the framebuffer API.
---
## The 11 example chips
The Custom Chip designer ships a gallery with 11 ready-to-go examples,
covering every protocol the runtime supports:
| Chip | Protocol | What it shows |
|---|---|---|
| **Inverter** | GPIO + watch | Simplest possible chip — start here |
| **XOR Gate** | GPIO + 2 inputs | Multiple watches, recompute output on any edge |
| **CD4094** | GPIO state machine | Edge detection (RISING vs BOTH), multi-pin shift register |
| **Pulse Counter** | GPIO + attributes | User-configurable threshold via slider |
| **74HC595** | SPI slave | SPI transfer with re-arm pattern |
| **MCP3008** | SPI + analog | SPI ADC: read voltage, return 10-bit result |
| **24C01 EEPROM** | I2C slave | Tiny memory device with pointer auto-increment |
| **24LC256 EEPROM** | I2C slave | 16-bit addressing + page writes |
| **PCF8574** | I2C IO expander | Reading/writing 8 pins atomically |
| **DS3231 RTC** | I2C state | 19 registers, BCD encoding, register pointer |
| **ROT13 UART** | UART | Receive a byte, transmit transformed byte |
Each is fully explained in
[`wiki/custom-chips-examples.md`](./wiki/custom-chips-examples.md).
---
## Where to read next
| If you want to … | Read |
|---|---|
| Understand every host function in detail | [API reference](./wiki/custom-chips-api-reference.md) |
| See the 11 chip examples worked through | [Examples walkthrough](./wiki/custom-chips-examples.md) |
| Set up the toolchain or write tests | [Build & test guide](./wiki/custom-chips-build-and-test.md) |
| Run custom chips on ESP32 (backend runtime architecture) | [ESP32 backend runtime](./wiki/custom-chips-esp32-backend-runtime.md) |
| Know which boards support which protocols | [Board support matrix](../test/autosearch/07_multi_board_support.md) |
### Quick links to source
- C SDK header — [`backend/sdk/velxio-chip.h`](../backend/sdk/velxio-chip.h)
- Frontend runtime — [`frontend/src/simulation/customChips/`](../frontend/src/simulation/customChips/)
- Example chips — [`test/test_custom_chips/sdk/examples/`](../test/test_custom_chips/sdk/examples/)
- Backend compile service — [`backend/app/services/chip_compile.py`](../backend/app/services/chip_compile.py)
- Sandbox test suite — [`test/test_custom_chips/`](../test/test_custom_chips/)
---
## Design philosophy
A few decisions worth knowing:
- **Reactive, not procedural.** Your chip never has a `loop()`. The host calls
your callbacks; you do small bits of work and return. This guarantees the
chip can't hang the simulator.
- **Shared-nothing memory.** Each chip instance gets its own WASM linear memory.
Two instances of the same chip can't accidentally share state — no globals
to worry about.
- **The C type system is your friend.** `velxio-chip.h` declares concrete
types (`vx_pin`, `vx_attr`, `vx_i2c_config`) and uses `_Static_assert` to
guarantee the struct layouts match between your chip and the host. If you
add a field, the assertion fires and you fix the runtime accordingly.
- **No dependency on Wokwi.** This is a clean-room implementation. The
toolchain is `clang` + `wasi-sdk`, both Apache-2.0. Our header and runtime
are Velxio-original.