Files
diodeinc__pcb/README.md
Lenny Khazan e852477546 Initial release (#5)
* Initial release

* cargo-dist

* build and test CI

* update ubuntu workflow

* fmt

* ubuntu: install deps

* clippy: 0.1.88 fixes

* format

* install clippy

* Update README.md

* Don't run cargo-dist on PRs
2025-07-01 04:06:08 -04:00

14 KiB

Zener

A modern PCB design toolchain powered by Starlark

License Rust

Zener is a next-generation PCB design toolchain that brings modern software engineering practices to hardware design.

Warning

We're still in the early days of getting this out into the world; expect breaking changes and better documentation in the next few days.

Features

  • Starlark-based Design - Write PCB designs in a Python-like language with strong typing and deterministic evaluation
  • KiCad Integration - Export schematics and layouts into KiCad for industry-standard PCB design workflows
  • Modular Components - Create reusable component libraries and share them across projects
  • Interface Templates - Define and reuse connection patterns with type-safe interfaces
  • Integrated Toolchain - From schematic to layout generation in a single tool
  • LSP Support - Full language server protocol support for intelligent code completion and diagnostics
  • Type Safety - Catch wiring errors at compile time with strongly-typed net connections
  • Fast Iteration - Instant feedback with live error reporting and diagnostics

Table of Contents

Installation

From Source

# Clone the repository
git clone https://github.com/diodeinc/pcb.git
cd pcb

# Install using the provided script
./install.sh

Note

Package manager installation coming soon.

Requirements

  • Rust 2024 edition or later
  • KiCad (for generating and editing layouts)

Quick Start

1. Create Your First Design

Create a file called blinky.star:

# Load standard library
load("@github/diodeinc/stdlib:main/properties.star", "Layout")

Resistor = Module("@github/diodeinc/stdlib:main/generics/Resistor.star")
LED = Module("@github/diodeinc/stdlib:main/generics/LED.star")

# Define power nets
vcc = Net("VCC")
gnd = Net("GND")
led_anode = Net("LED_ANODE")

# Create components
Resistor(
    name = "R1",
    value = "1kohm",
    package = "0402",
    P1 = vcc,
    P2 = led_anode
)

LED(
    name = "D1",
    package = "0402",
    color = "red",
    A = led_anode,
    K = gnd
)

Layout("layout", "layout/")

2. Build Your Design

# Compile the design and check for errors
pcb build blinky.star

# Output:
# ✓ blinky.star (2 components)

3. Generate Layout

# Generate PCB layout files
pcb layout blinky.star

# Output:
# ✓ blinky.star (layout/blinky.kicad_pcb)

4. Open in KiCad

# Open the generated layout
pcb open blinky.star

Core Concepts

Components

Components are the building blocks of your design. They define physical parts with pins, footprints, and properties:

Component(
    name = "U1",
    type = "microcontroller",
    footprint = "QFP-48",
    pin_defs = {
        "VDD": "1",
        "GND": "2",
        "PA0": "3",
        # ... more pins
    },
    pins = {
        "VDD": vcc_3v3,
        "GND": gnd,
        "PA0": led_control,
    }
)

Nets

Nets represent electrical connections between component pins:

# Create named nets
power_5v = Net("5V")
ground = Net("GND")
data_bus = Net("SPI_MOSI")

# Nets are type-safe and tracked across the design

Interfaces

Interfaces define reusable connection patterns:

# Define a power interface
PowerInterface = interface(
    vcc = Net,
    gnd = Net,
)

# Define an SPI interface
SPIInterface = interface(
    clk = Net,
    mosi = Net,
    miso = Net,
    cs = Net,
)

Modules

Modules enable hierarchical design and reusability. A module is a .star file that defines configuration parameters and IO interfaces:

# power_supply.star
# Configuration parameters
input_voltage = config("input_voltage", float, default = 12.0)
output_voltage = config("output_voltage", float, default = 3.3)

# IO interfaces
input = io("input", PowerInterface)
output = io("output", PowerInterface)

# Module implementation
Regulator(
    name = "REG1",
    vin = input.vcc,
    vout = output.vcc,
    gnd = input.gnd,
    # ... component configuration
)

# main.star
PowerSupply = Module("power_supply.star")

PowerSupply(
    name = "PSU1",
    input_voltage = 9.0,
    output_voltage = 5.0,
    input = system_power_in,
    output = regulated_power,
)

Module Configuration with config()

The config() function defines configuration parameters at the module level:

# sensor_module.star
# Required configuration
i2c_address = config("i2c_address", int)

# Optional configuration with defaults
sample_rate = config("sample_rate", int, default = 100)
gain = config("gain", float, default = 1.0)

# Configuration with type conversion
threshold = config("threshold", float, convert = lambda x: float(x))

# Enum configuration
Package = enum("QFN", "TQFP", "BGA")
package_type = config("package", Package, convert = Package)

Module IO with io()

The io() function defines input/output interfaces at the module level:

# uart_bridge.star
# Define IO interfaces
uart_in = io("uart_in", UARTInterface)
uart_out = io("uart_out", UARTInterface)
power = io("power", PowerInterface)

# Simple net IO
enable = io("enable", Net)

# Module implementation uses the IO
Bridge(
    name = "U1",
    rx_in = uart_in.rx,
    tx_in = uart_in.tx,
    rx_out = uart_out.rx,
    tx_out = uart_out.tx,
    vcc = power.vcc,
    gnd = power.gnd,
    en = enable,
)

Command Reference

pcb build

Build and validate PCB designs from .star files.

pcb build [PATHS...]

Arguments:
  [PATHS...]     One or more .star files or directories containing .star files
                 When omitted, all .star files in the current directory are built
                 Directories are scanned non-recursively

Examples:
  pcb build                    # Build all .star files in current directory
  pcb build board.star         # Build specific file
  pcb build designs/           # Build all .star files in designs/ directory (non-recursive)
  pcb build a.star b.star      # Build multiple specific files

The build command:

  • Validates your Starlark code
  • Reports any errors or warnings with detailed diagnostics
  • Shows component count for successful builds
  • Exits with error code if any file fails to build

pcb layout

Generate PCB layout files from .star designs.

pcb layout [OPTIONS] [PATHS...]

Options:
  -s, --select      Always prompt to choose a layout even when only one exists
      --no-open     Skip opening the layout file after generation
  -h, --help        Show help information

Arguments:
  [PATHS...]        One or more .star files to process for layout generation
                    When omitted, all .star files in the current directory are processed

Examples:
  pcb layout                   # Generate layouts for all .star files
  pcb layout board.star        # Generate layout for specific file
  pcb layout --no-open         # Generate without opening in KiCad
  pcb layout -s                # Force layout selection prompt

The layout command:

  • First builds the .star file (same as pcb build)
  • Generates KiCad PCB layout files if a Layout() directive exists
  • Shows warnings for files without layout directives
  • Opens the generated layout in KiCad by default (unless --no-open)
  • Prompts for selection when multiple layouts exist (or with -s)

pcb open

Open existing PCB layout files in KiCad.

pcb open [PATHS...]

Arguments:
  [PATHS...]     One or more .star files to find and open layouts for
                 When omitted, searches current directory for .kicad_pcb files

Examples:
  pcb open                     # Open layout files in current directory
  pcb open board.star          # Open layout associated with board.star
  pcb open *.star              # Open layouts for all .star files

The open command:

  • Builds .star files to find their associated layout paths
  • Falls back to searching for .kicad_pcb files if no .star files specified
  • Prompts for selection when multiple layouts are found
  • Opens the selected layout in your system's default PCB editor (typically KiCad)

pcb lsp

Start the Language Server Protocol server for editor integration.

pcb lsp

The LSP command:

  • Starts the LSP server for Starlark PCB files
  • Provides intelligent code completion, diagnostics, and go-to-definition
  • Typically launched automatically by your editor's LSP client
  • Supports eager evaluation for real-time feedback

Project Structure

A typical Zener project structure:

my-pcb-project/
├── main.star              # Main board definition
├── components/            # Reusable components
│   ├── mcu.star
│   ├── power.star
│   └── connectors.star
├── modules/               # Reusable modules
│   ├── usb_interface.star
│   └── power_supply.star
├── libs/                  # External libraries
│   └── stdlib.star
├── eda/                   # KiCad symbols and footprints
│   ├── symbols/
│   └── footprints/
└── layout/                # Generated layouts
    └── main.kicad_pcb

Architecture

Zener is built as a modular Rust workspace with specialized crates:

Core Language & Runtime

  • pcb-star - Main Starlark runtime with PCB-specific extensions, LSP server, and DAP support
  • pcb-star-core - Core language features including components, modules, nets, interfaces, and the type system
  • pcb-star-wasm - WebAssembly bindings for running Starlark PCB designs in the browser

Schematic & Layout

  • pcb-sch - Schematic representation, netlist structures, and KiCad export formats
  • pcb-layout - PCB layout generation from schematics and KiCad file creation
  • pcb-kicad - KiCad file format parsing and generation utilities

Language Server & Editor Support

  • pcb-starlark-lsp - Language Server Protocol implementation for Starlark with PCB extensions
  • pcb - Main CLI tool providing build, layout, open, and lsp commands

EDA Integration & Utilities

  • pcb-eda - EDA tool integration for processing symbols and footprints from various sources
  • pcb-sexpr - S-expression parser for KiCad file formats
  • pcb-ui - Terminal UI components including spinners, progress bars, and styled output
  • pcb-command-runner - Utility for running external commands with proper output capture

Key Design Principles

  1. Modular Architecture - Each crate has a focused responsibility, enabling reuse and testing
  2. Type Safety - Strong typing throughout, from Starlark evaluation to netlist generation
  3. WASM Compatibility - Core functionality can run in browsers via WebAssembly
  4. Extensibility - Clean interfaces between components allow for future expansion
  5. Developer Experience - Rich diagnostics, LSP support, and beautiful terminal output

Examples

Simple LED Circuit

load("@github/diodeinc/stdlib:main/properties.star", "Layout")

Resistor = Module("@github/diodeinc/stdlib:main/generics/Resistor.star")
LED = Module("@github/diodeinc/stdlib:main/generics/LED.star")
Capacitor = Module("@github/diodeinc/stdlib:main/generics/Capacitor.star")

vcc = Net("VCC")
gnd = Net("GND")
led = Net("LED")

# Power supply filtering
Capacitor(
    name = "C1",
    value = "100nF",
    package = "0402",
    P1 = vcc,
    P2 = gnd
)

# Current limiting resistor
Resistor(
    name = "R1",
    value = "330ohm",
    package = "0402",
    P1 = vcc,
    P2 = led
)

# Status LED
LED(
    name = "D1",
    color = "red",
    package = "0402",
    A = led,
    K = gnd
)

Layout("layout", "layout/")

Module with Configuration

# voltage_regulator.star
input_voltage = config("input_voltage", float)
output_voltage = config("output_voltage", float, default = 3.3)
max_current = config("max_current", float, default = 1.0)

input = io("input", PowerInterface)
output = io("output", PowerInterface)
enable = io("enable", Net)

# Create the regulator component
Component(
    name = "REG",
    type = "voltage_regulator",
    footprint = "SOT-23-5",
    pin_defs = {
        "VIN": "1",
        "GND": "2",
        "EN": "3",
        "VOUT": "4",
        "FB": "5",
    },
    pins = {
        "VIN": input.vcc,
        "GND": input.gnd,
        "EN": enable,
        "VOUT": output.vcc,
        "FB": Net("FEEDBACK"),
    },
    properties = {
        "input_voltage": input_voltage,
        "output_voltage": output_voltage,
    }
)

# main.star
load("@github/diodeinc/stdlib:main/interfaces.star", "PowerInterface")
VoltageRegulator = Module("voltage_regulator.star")

# Define power rails
input_power = PowerInterface(prefix = "VIN")
output_power = PowerInterface(prefix = "3V3")

# Create voltage regulator
VoltageRegulator(
    name = "VREG1",
    input_voltage = 5.0,
    output_voltage = 3.3,
    max_current = 0.5,
    input = input_power,
    output = output_power,
    enable = Net("VREG_EN"),
)

Complex System with Multiple Modules

load("@github/diodeinc/stdlib:main/properties.star", "Layout")
load("@github/diodeinc/stdlib:main/interfaces.star", "PowerInterface", "SPIInterface", "I2CInterface")

# Load modules
MCU = Module("stm32f4.star")
Sensor = Module("bmi270.star")
Flash = Module("w25q128.star")

# Power distribution
system_power = PowerInterface(prefix = "3V3")

# Communication buses
spi_bus = SPIInterface(prefix = "SPI1")
i2c_bus = I2CInterface(prefix = "I2C1")

# Microcontroller
MCU(
    name = "U1",
    power = system_power,
    spi1 = spi_bus,
    i2c1 = i2c_bus,
)

# IMU Sensor
Sensor(
    name = "U2",
    power = system_power,
    i2c = i2c_bus,
    i2c_address = 0x68,
    sample_rate = 400,  # 400Hz
)

# Flash Memory
Flash(
    name = "U3",
    power = system_power,
    spi = spi_bus,
    capacity = "128Mbit",
)

Layout("layout")

License

Zener is licensed under the MIT License. See LICENSE for details.

Acknowledgments


Made in Brooklyn, NY, USA.