PCB tooling by Diode Computers, Inc.
pcb
is a command-line utility for building PCBs. It uses the
Zener language to describe
PCB schematics and provides automations on top of KiCad to build PCBs fast.
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.
Follow the instructions here
to install the latest pcb
.
# 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.
- KiCad 9.x (for generating and editing layouts)
Create a file called blinky.zen
:
# Load standard library
load("@stdlib:v0.2.23/properties.zen", "Layout")
load("@stdlib:v0.2.23/board_config.zen", "Board", "BASE_4L")
Resistor = Module("@stdlib:v0.2.23/generics/Resistor.zen")
Led = Module("@stdlib:v0.2.23/generics/Led.zen")
# 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
)
Board(
name = "blinky",
config = BASE_4L,
layout_path = "layout/blinky"
)
# Compile the design and check for errors
pcb build blinky.zen
# Output:
# ✓ blinky.zen (2 components)
# Generate PCB layout files
pcb layout blinky.zen
# Output:
# ✓ blinky.zen (layout/blinky.kicad_pcb)
# Open the generated layout
pcb open blinky.zen
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 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 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 enable hierarchical design and reusability. A module is a .zen
file that defines configuration parameters and IO interfaces:
# power_supply.zen
# 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", Power)
output = io("output", Power)
# Module implementation
Regulator(
name = "REG1",
vin = input.vcc,
vout = output.vcc,
gnd = input.gnd,
# ... component configuration
)
# main.zen
PowerSupply = Module("power_supply.zen")
PowerSupply(
name = "PSU1",
input_voltage = 9.0,
output_voltage = 5.0,
input = system_power_in,
output = regulated_power,
)
The config()
function defines configuration parameters at the module level:
# sensor_module.zen
# 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)
The io()
function defines input/output interfaces at the module level:
# uart_bridge.zen
# 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,
)
Build and validate PCB designs from .zen
files.
pcb build [PATHS...]
Arguments:
[PATHS...] One or more .zen files or directories containing .zen files
When omitted, all .zen files in the current directory are built
Directories are scanned non-recursively
Examples:
pcb build # Build all .zen files in current directory
pcb build board.zen # Build specific file
pcb build designs/ # Build all .zen files in designs/ directory (non-recursive)
pcb build a.zen b.zen # 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
Generate PCB layout files from .zen
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 .zen files to process for layout generation
When omitted, all .zen files in the current directory are processed
Examples:
pcb layout # Generate layouts for all .zen files
pcb layout board.zen # 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 .zen 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
)
Open existing PCB layout files in KiCad.
pcb open [PATHS...]
Arguments:
[PATHS...] One or more .zen 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.zen # Open layout associated with board.zen
pcb open *.zen # Open layouts for all .zen files
The open command:
- Builds .zen files to find their associated layout paths
- Falls back to searching for .kicad_pcb files if no .zen files specified
- Prompts for selection when multiple layouts are found
- Opens the selected layout in your system's default PCB editor (typically KiCad)
Format .zen
files using the ruff formatter.
pcb fmt [OPTIONS] [PATHS...]
Options:
--check Check if files are formatted correctly without modifying them
--diff Show diffs instead of writing files
-h, --help Show help information
Arguments:
[PATHS...] One or more .zen/.zen files or directories containing such files
When omitted, all .zen/.zen files in the current directory are formatted
Examples:
pcb fmt # Format all .zen/.zen files in current directory
pcb fmt design.zen # Format specific file
pcb fmt src/ # Format all files in src/ directory
pcb fmt --check # Check formatting without making changes
pcb fmt --diff main.zen # Show what would change
The fmt command:
- Uses the ruff formatter (no external dependencies)
- Formats files according to Python PEP 8 style conventions
- Supports checking mode (
--check
) for CI/CD pipelines - Shows diffs (
--diff
) without modifying files - Returns non-zero exit code if files need formatting in check mode
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
A typical Zener project structure:
my-pcb-project/
├── main.zen # Main board definition
├── components/ # Reusable components
│ ├── mcu.zen
│ ├── power.zen
│ └── connectors.zen
├── modules/ # Reusable modules
│ ├── usb_interface.zen
│ └── power_supply.zen
├── libs/ # External libraries
│ └── stdlib.zen
├── eda/ # KiCad symbols and footprints
│ ├── symbols/
│ └── footprints/
└── layout/ # Generated layouts
└── main.kicad_pcb
Zener is built as a modular Rust workspace with specialized crates:
pcb-zen
- Main Starlark runtime with PCB-specific extensions, LSP server, and DAP supportpcb-zen-core
- Core language features including components, modules, nets, interfaces, and the type systempcb-zen-wasm
- WebAssembly bindings for running Starlark PCB designs in the browser
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
pcb-starlark-lsp
- Language Server Protocol implementation for Starlark with PCB extensionspcb
- Main CLI tool providing build, layout, open, and lsp commands
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
load("@stdlib/properties.zen", "Layout")
Resistor = Module("@stdlib/generics/Resistor.zen")
LED = Module("@stdlib/generics/LED.zen")
Capacitor = Module("@stdlib/generics/Capacitor.zen")
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/")
# voltage_regulator.zen
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.zen
load("@stdlib/interfaces.zen", "Power")
VoltageRegulator = Module("voltage_regulator.zen")
# Define power rails
input_power = Power("VIN")
output_power = Power("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"),
)
load("@stdlib/properties.zen", "Layout")
load("@stdlib/interfaces.zen", "Power", "SPI", "I2C")
# Load modules
MCU = Module("stm32f4.zen")
Sensor = Module("bmi270.zen")
Flash = Module("w25q128.zen")
# Power distribution
system_power = Power("3V3")
# Communication buses
spi_bus = SPI("SPI1")
i2c_bus = I2C("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")
Zener is licensed under the MIT License. See LICENSE for details.
- ruff: The
pcb fmt
command usesruff fmt
from the astral-sh/ruff project, which is licensed under the MIT. See LICENSE for the full license text.
- Built on starlark-rust by Meta.
- Inspired by atopile, tscircuit, and others.
Made in Brooklyn, NY, USA.