velxio/backend/sdk/velxio-chip.h

226 lines
8.5 KiB
C

/*
* velxio-chip.h — Public API for Velxio custom chips.
*
* Independent, clean-room API. No code from third-party simulators.
* License: MIT (Velxio project).
*
* A chip is a WebAssembly module that imports the host functions declared
* here and exports `chip_setup()`. The host calls `chip_setup()` once per
* chip instance to register pins, attributes, I2C/UART/SPI peripherals,
* and timers. After setup, the chip is purely reactive: it runs only
* inside callbacks invoked by the host (pin watch, I2C bus, timer fire).
*/
#ifndef VELXIO_CHIP_H
#define VELXIO_CHIP_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/* ─── Pins ──────────────────────────────────────────────────────────────── */
typedef int32_t vx_pin;
typedef enum {
VX_INPUT = 0,
VX_OUTPUT = 1,
VX_INPUT_PULLUP = 2,
VX_INPUT_PULLDOWN = 3,
VX_ANALOG = 4,
/* Output that initializes the pin to a specific level — eliminates the brief
* window between vx_pin_register() and the first vx_pin_write() during which
* a plain VX_OUTPUT pin would default to LOW. */
VX_OUTPUT_LOW = 16,
VX_OUTPUT_HIGH = 17,
} vx_pin_mode;
typedef enum {
VX_LOW = 0,
VX_HIGH = 1,
} vx_pin_value;
typedef enum {
VX_EDGE_RISING = 1,
VX_EDGE_FALLING = 2,
VX_EDGE_BOTH = 3,
} vx_edge;
/** Register a logical pin on the chip. The host wires it via the diagram. */
extern vx_pin vx_pin_register(const char* name, vx_pin_mode mode);
/** Read the digital value (0 or 1) of a pin. */
extern int vx_pin_read(vx_pin p);
/** Drive a digital value on an OUTPUT pin. */
extern void vx_pin_write(vx_pin p, int value);
/** Read the analog voltage (0.0 .. supply_volts) of a pin. */
extern double vx_pin_read_analog(vx_pin p);
/** Drive an analog voltage (volts) on an OUTPUT/ANALOG pin (DAC). */
extern void vx_pin_dac_write(vx_pin p, double voltage);
/** Change a pin's mode after registration. Useful for bidirectional I/O. */
extern void vx_pin_set_mode(vx_pin p, vx_pin_mode mode);
/**
* Watch a pin for edge events. The callback is dispatched inside the
* simulation loop when the pin state crosses the requested edge.
*/
extern void vx_pin_watch(
vx_pin p,
vx_edge edge,
void (*cb)(void* user_data, vx_pin pin, int value),
void* user_data
);
/** Stop watching a pin. Removes every callback registered for it. */
extern void vx_pin_watch_stop(vx_pin p);
/* ─── Attributes (user-editable parameters from the diagram editor) ─────── */
typedef int32_t vx_attr;
extern vx_attr vx_attr_register(const char* name, double default_val);
extern double vx_attr_read(vx_attr a);
/* ─── I2C slave ─────────────────────────────────────────────────────────── */
typedef int32_t vx_i2c;
typedef struct {
uint8_t address; /* 7-bit I2C address */
uint8_t _pad[3]; /* padding to 4-byte alignment of next field */
vx_pin scl;
vx_pin sda;
bool (*on_connect)(void* user_data, uint8_t addr, bool is_read);
uint8_t(*on_read)(void* user_data);
bool (*on_write)(void* user_data, uint8_t byte);
void (*on_stop)(void* user_data);
void* user_data;
uint32_t reserved[8]; /* forward-compat — must be zeroed by chip */
} vx_i2c_config;
_Static_assert(sizeof(vx_i2c_config) == 64, "vx_i2c_config must be 64 bytes");
/** Attach an I2C slave to the bus. Call only from chip_setup(). */
extern vx_i2c vx_i2c_attach(const vx_i2c_config* cfg);
/* ─── UART ──────────────────────────────────────────────────────────────── */
typedef int32_t vx_uart;
typedef struct {
vx_pin rx;
vx_pin tx;
uint32_t baud_rate;
void (*on_rx_byte)(void* user_data, uint8_t byte);
void (*on_tx_done)(void* user_data);
void* user_data;
uint32_t reserved[8];
} vx_uart_config;
_Static_assert(sizeof(vx_uart_config) == 56, "vx_uart_config must be 56 bytes");
extern vx_uart vx_uart_attach(const vx_uart_config* cfg);
extern bool vx_uart_write(vx_uart u, const uint8_t* buffer, uint32_t count);
/* ─── SPI slave ─────────────────────────────────────────────────────────── */
typedef int32_t vx_spi;
/**
* SPI configuration. The CS pin is GPIO and watched by the chip directly —
* the runtime starts/stops transactions automatically based on its level.
*
* `on_done` fires after every `count` bytes received via vx_spi_start():
* - Before the call, `buffer` contains the chip's outgoing MISO bytes.
* - After the call, `buffer` contains the master's MOSI bytes received.
*/
typedef struct {
vx_pin sck;
vx_pin mosi;
vx_pin miso;
vx_pin cs;
uint32_t mode; /* 0..3 (SPI mode) */
void (*on_done)(void* user_data, uint8_t* buffer, uint32_t count);
void* user_data;
uint32_t reserved[8];
} vx_spi_config;
_Static_assert(sizeof(vx_spi_config) == 60, "vx_spi_config must be 60 bytes");
extern vx_spi vx_spi_attach(const vx_spi_config* cfg);
/** Begin a transfer of `count` bytes. Buffer is bidirectional (MISO out, MOSI in). */
extern void vx_spi_start(vx_spi s, uint8_t* buffer, uint32_t count);
/** Abort an in-flight transfer. Fires `on_done` with whatever was received so far. */
extern void vx_spi_stop(vx_spi s);
/* ─── Time and timers ───────────────────────────────────────────────────── */
typedef int32_t vx_timer;
extern uint64_t vx_sim_now_nanos(void);
extern vx_timer vx_timer_create(void (*cb)(void* user_data), void* user_data);
extern void vx_timer_start(vx_timer t, uint64_t period_nanos, bool repeat);
extern void vx_timer_stop(vx_timer t);
/* ─── Display / framebuffer ─────────────────────────────────────────────── */
typedef int32_t vx_buffer;
/**
* Acquire the chip's display framebuffer. Width and height are returned
* via out-pointers — they are taken from the `display: { width, height }`
* field of the chip's chip.json, so the chip and the diagram editor agree
* on dimensions.
*
* The buffer is laid out as RGBA8888, row-major, no padding:
* pixel(x,y) starts at byte offset (y * width + x) * 4
* bytes: R G B A
*
* Call only from chip_setup().
*/
extern vx_buffer vx_framebuffer_init(uint32_t* out_width, uint32_t* out_height);
/** Write `data_len` bytes into the framebuffer at the given byte offset. */
extern void vx_buffer_write(vx_buffer buf, uint32_t offset, const void* data, uint32_t data_len);
/* ─── Logging ───────────────────────────────────────────────────────────── */
/** Emit a message to the host's chip log. printf() also works via WASI. */
extern void vx_log(const char* msg);
/* ─── External ROM blob ─────────────────────────────────────────────────── */
/**
* Read a chip's external ROM blob. The blob is injected by the host before
* `chip_setup()` runs, sourced from the `romBytes` property of the chip's
* component (base64-encoded bytes in the diagram editor, or compiled from
* a chip-program file like .s / .hex / .bin).
*
* Typical use — a CPU emulator chip loads its emulated program once at boot:
*
* uint32_t rom_len = vx_rom_size();
* if (rom_len) vx_rom_read(0, my_rom_buf, rom_len);
*
* If no ROM is provided, vx_rom_size() returns 0 and vx_rom_read() is a no-op
* — chips can fall back to a built-in default in that case.
*/
extern uint32_t vx_rom_size(void);
/** Copy `len` bytes from offset `offset` of the external ROM into `dst`.
* Reads past the end of the ROM are silently truncated. */
extern void vx_rom_read(uint32_t offset, uint8_t* dst, uint32_t len);
/* ─── Lifecycle (chip exports) ──────────────────────────────────────────── */
/** Required: called once per chip instance after the simulator boots. */
void chip_setup(void);
#endif /* VELXIO_CHIP_H */