Skip to content

External peripheral API

An external peripheral is a folder containing manifest.json and, when needed, SVG files. FPGALab reads it at startup or when installed from the catalog. A package does not execute its own Python code. API version 1 is available in FPGALab 0.1.0rc4. API version 2 adds edge streams in the development version after RC4; it is not part of the RC4 artifacts.

Repository examples live under examples/peripherals/<peripheral-id>/. When installed, a package is copied to the user catalog; it is not copied into fpga_lab/peripherals/, which is reserved for bundled components.

Start from an example

The simple relay is the smallest digital output package. The button, LED bar, PWM meter, and servo demonstrate the other reusable behaviors.

simple_relay/
├── manifest.json
├── icon.svg
├── off.svg
└── on.svg

id must match the folder name and be unique among bundled and installed peripherals. For new shareable packages, a namespaced ID such as your_name.relay is a good choice; the API does not yet enforce this format. label is the user-facing name.

manifest.json fields

Field Type Purpose
api_version optional integer Format version: 1 or 2; omission defaults to 1. Version 2 is required for edge streams.
id required string Stable identifier matching the folder name.
label required string Name displayed in the catalog.
category optional string Catalog category, defaulting to output; new categories are allowed.
description optional string Description shown in search and the catalog.
keywords optional string list Additional search terms.
icon optional relative path Catalog SVG such as icon.svg; this is not the workbench artwork.
simulation required object Signal class and optional temporal observation.
terminals optional list Logical pins connected to the board.
properties optional object Editable fields on each instance.
visual required object Renderer, size, and artwork configuration.
package optional object Version, attribution, license, and compatibility for sharing and updating.

package: distribution and updates

When package is present, it requires version (a semantic version such as 1.0.0), author.name, license (an SPDX-style identifier), and compatibility.minimum_fpgalab (for example 0.1.0rc4). author.url and repository are optional HTTP(S) URLs.

contributors and maintainers are optional lists of objects with a required name and optional HTTP(S) url. author identifies the original creator; list other people only after they have contributed. To update an installed package, keep its id, increment package.version, and install it again. FPGALab validates the replacement and asks for confirmation; it rejects changed files with the same or an older version.

terminals: connections

Each terminal has name and direction. input means into the FPGA (for example, a button); output means from the FPGA (for example, an LED). width is a positive integer defaulting to 1; required is a boolean defaulting to true. Optional supplies may contain only GND or VCC. Names must be unique.

simulation: available signals

class Purpose
gpio_driven An interaction drives an FPGA input.
gpio_sampled The latest output level is read at each interface update.
gpio_temporal Outputs are observed over simulated cycles. External examples use "temporal": {"mode": "per_terminal"}.
streaming_sink Specialized path used by VGA; it is not yet a general declarative API for UART, I²C, or SPI.
edge_stream API v2: capture timestamped one-bit output transitions and optionally schedule changes on one-bit FPGA inputs.

A temporal terminal can expose duty_cycle (fraction from 0 to 1), edge_rate_hz, and pulse_high_seconds (seconds of the last completed high pulse). Measurements use virtual time and the configured sampling rate; faster signals may alias. pulse_high_seconds becomes available only after a falling edge. The display_common mode exists for bundled displays but is not the recommended starting point for external packages.

For edge_stream, declare "channels": ["rx"] under simulation. Each named channel must be a distinct one-bit output terminal. Up to 16 capture channels may be active in one simulation, including channels from other installed peripherals. The native queue holds up to 16,384 transitions between interface updates; an overflow is reported to the renderer so it can discard a partial protocol frame. Timestamps are virtual FPGA cycles, not wall-clock time. Signals are sampled on each rising clock edge, so transitions shorter than one cycle are not observable. This capture path is independent of the lower-rate temporal measurement setting.

To drive FPGA inputs at exact virtual cycles, add "drives": ["tx"] to the same simulation object. Each drive names a distinct one-bit input terminal; "drive_idle": {"tx": 1} sets its idle level. The native scheduler reserves that input bit while preserving other GPIO bits in the same port. It applies queued changes before each rising clock edge, including across interface-update boundaries. The queue is bounded to 8,192 transitions. The stock UART terminal uses this capability, but external packages still cannot provide arbitrary protocol code.

Internally, protocol encoders can submit a relative-time sequence for several drive channels with one shared start cycle. A rejected sequence does not reserve cycles; a reset restores each channel's configured idle level and clears pending changes. This is not yet an extension hook for package-defined encoders or renderers.

properties: editable fields

Each property declares type and may add label and default. Supported types are color, color_map, enum, boolean, string, and key_sequence. A color_map uses keys and a color default for each key; an enum uses values and may declare presets. Declare only properties used by the selected renderer. The name position is reserved by the workbench.

visual: reusable renderers

All renderers require renderer and size: [width, height] with positive integers. chrome: "compact" removes the card frame and places the instance name below the artwork. SVG and interaction coordinates refer to the artwork area; normally the last 24 pixels of height are reserved for the name.

Renderer Driving data Main fields
state_svg Digital levels and interactive inputs states, default_state, state_rules, interactions
led_array Brightness of several outputs terminals, orientation, indicator_shape, show_labels, color_property
measured_svg Duty cycle or pulse width terminal, measurement, ranges, transform, and two SVGs
uart_terminal API v2 edge stream and timed input channel, baud_property, optional tx_channel; receives and sends UART 8N1 text
spi_monitor API v2 synchronized edge stream Clock, chip-select, and MOSI channels; displays decoded master-output bytes

state_svg maps state names to SVG files in states, for example {"off": "off.svg", "on": "on.svg"}. default_state selects the initial state. Each state_rules entry has state and when: {"terminal": "signal", "equals": 1}; rules run in order. For controls, each interaction declares terminal, region: [x, y, width, height], and one event/action pair: click/toggle or press_release/momentary. The region must fit within the artwork.

led_array lists unique output terminals in visual.terminals. orientation can be horizontal or vertical; indicator_shape, circle or rectangle; show_labels is boolean. color_property must refer to a color property (default name color).

measured_svg composes a fixed base_svg and a movable moving_svg. Both SVGs use the artwork size and coordinate system. terminal names a one-bit output; measurement is duty_cycle or pulse_high_seconds; input_range and output_range are numeric pairs mapping the measurement to the visual position. transform supports rotate (degrees) or scale_x (horizontal scale); origin: [x, y] sets the transform point. Measurements are clamped to the input range. For duty cycle with output [0, 1], value_label: {"format": "percent", "rect": [x, y, width, height]} displays a percentage. The servo rotates; the PWM meter scales horizontally.

The earlier signal_meter and pulse_servo renderers remain available for existing packages. Their artwork is defined in FPGALab; use measured_svg for new designs with packaged graphics.

Serial example: UART terminal

The UART terminal example is an external package with only a manifest and catalog icon. Connect its rx terminal to the FPGA's serial TX output and its optional tx terminal to the FPGA's serial RX input, then select the same baud rate as the HDL. Terminal directions are from the FPGA's perspective: rx is an output, tx is an input. Its relevant fields are:

{
  "api_version": 2,
  "simulation": {"class": "edge_stream", "channels": ["rx"], "drives": ["tx"], "drive_idle": {"tx": 1}},
  "terminals": [
    {"name": "rx", "direction": "output", "width": 1},
    {"name": "tx", "direction": "input", "width": 1, "required": false}
  ],
  "properties": {"baud": {"type": "enum", "default": "115200", "values": ["9600", "115200"]}},
  "visual": {"renderer": "uart_terminal", "size": [320, 240], "channel": "rx", "tx_channel": "tx", "baud_property": "baud"}
}

The terminal receives ASCII text and can send up to 512 UTF-8 bytes per submission. Its scrollable receive history has Copy and Clear controls and keeps up to 65,536 displayed characters or 4,096 lines. Output remains readable after Stop; a new Run creates a fresh monitor. Its send field is active only while the simulation runs; the optional tx must be connected to an FPGA input. The built-in encoder schedules start, eight least-significant-bit-first data bits, and stop at the configured virtual clock rate; it does not use GUI timers. The package cannot supply executable Python or define an arbitrary protocol decoder. package.compatibility.minimum_fpgalab alone does not guarantee an older build can load a package: RC4 rejects api_version: 2 even if the minimum version field says 0.1.0rc4.

SPI master-output monitor

The development-version SPI monitor example captures SCK, MOSI, and chip select from FPGA outputs. Select modes 0–3, MSB/LSB first, and active-low/high chip select in its properties. It shows completed bytes in hexadecimal, grouped by chip-select transaction. Edges from all channels are delivered in virtual-cycle order, including across interface updates. Transitions sampled in the same FPGA cycle use the new data level; HDL should still provide normal SPI setup time. A partial byte is discarded when chip select releases or the capture queue overflows.

This first example is read-only and does not monitor MISO when MISO is an FPGA input: API v2 currently captures HDL outputs only. An optional MISO channel can be used only if the HDL exposes a separate output mirror. Full-duplex monitoring and SPI input driving remain future work. The SPI renderer is available only in the development version, not in the RC4 artifacts.

Validate, install, and share

From the repository root, validate one or more folders:

python -m fpga_lab.peripherals.validate examples/peripherals/simple_relay

The command checks the manifest, renderer, SVG resources, folder name, and minimum FPGALab version without installing anything. In the application, open the catalog and use the install icon beside search. Choose a folder or a ZIP containing one root folder with manifest.json and its resources; SVG files must stay inside the package. The catalog can uninstall user packages without deleting their Lab instances. See Architecture and contribution to integrate new renderers into the core.

You can also copy the complete folder and restart FPGALab. The user catalog lives at ~/.local/share/FPGALab/peripherals/ (Linux), %APPDATA%\FPGALab\peripherals\ (Windows), or ~/Library/Application Support/FPGALab/peripherals/ (macOS). For development and testing, FPGALAB_PERIPHERALS_DIR temporarily selects a different catalog folder. Labs keep their instances and connections when a package is uninstalled; a missing type appears unavailable until reinstalled.