/* * 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 #include #include /* ─── 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); /* ─── Lifecycle (chip exports) ──────────────────────────────────────────── */ /** Required: called once per chip instance after the simulator boots. */ void chip_setup(void); #endif /* VELXIO_CHIP_H */