* 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
14 KiB
Zener
A modern PCB design toolchain powered by Starlark
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 supportpcb-star-core- Core language features including components, modules, nets, interfaces, and the type systempcb-star-wasm- WebAssembly bindings for running Starlark PCB designs in the browser
Schematic & Layout
pcb-sch- Schematic representation, netlist structures, and KiCad export formatspcb-layout- PCB layout generation from schematics and KiCad file creationpcb-kicad- KiCad file format parsing and generation utilities
Language Server & Editor Support
pcb-starlark-lsp- Language Server Protocol implementation for Starlark with PCB extensionspcb- 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 sourcespcb-sexpr- S-expression parser for KiCad file formatspcb-ui- Terminal UI components including spinners, progress bars, and styled outputpcb-command-runner- Utility for running external commands with proper output capture
Key Design Principles
- Modular Architecture - Each crate has a focused responsibility, enabling reuse and testing
- Type Safety - Strong typing throughout, from Starlark evaluation to netlist generation
- WASM Compatibility - Core functionality can run in browsers via WebAssembly
- Extensibility - Clean interfaces between components allow for future expansion
- 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
- Built on starlark-rust by Meta.
- Inspired by atopile, tscircuit, and others.
Made in Brooklyn, NY, USA.