Specification
Overview
Zener is a domain-specific language built on top of Starlark for describing PCB schematics. It provides primitives for defining components, symbols, nets, interfaces, and modules in a type-safe, composable manner. This specification describes the language extensions and primitives added on top of Starlark. For the base Starlark language features, please refer to the Starlark specification and the starlark-rust types extension.Table of Contents
Evaluation Model
Files as Modules
Each.zen
file is a Starlark module. It can be used in two ways:
- Its exported symbols can be
load()
ed into other modules. For example,load("./MyFile.zen", "MyFunction", "MyType")
will load theMyFunction
andMyType
symbols from theMyFile.zen
module. - It can be loaded as a schematic module using the
Module()
helper. For example,MyFile = Module("./MyFile.zen")
will importMyFile.zen
as a schematic module, which you can instantiate like so:
Load Resolution
Theload()
and Module()
statements support multiple resolution strategies:
Default Package Aliases
Zener provides built-in package aliases for commonly used libraries:@kicad-footprints
→@gitlab/kicad/libraries/kicad-footprints:9.0.0
@kicad-symbols
→@gitlab/kicad/libraries/kicad-symbols:9.0.0
@stdlib
→@github/diodeinc/stdlib:HEAD
Custom Package Aliases
You can define custom package aliases or override the defaults in your workspace’spcb.toml
:
Core Types
Net
ANet
represents an electrical connection between component pins.
Net
Constructor:
Net(name="")
name
(optional): String identifier for the net
Symbol
ASymbol
represents a schematic symbol definition with its pins. Symbols can be created manually or loaded from KiCad symbol libraries.
Symbol
Constructor:
Symbol(library_spec=None, name=None, definition=None, library=None)
library_spec
: (positional) String in format “library_path:symbol_name” or just “library_path” for single-symbol librariesname
: Symbol name (required when loading from multi-symbol library with named parameters)definition
: List of (signal_name, [pad_numbers]) tupleslibrary
: Path to KiCad symbol library file
library_spec
argument with the named library
or name
parameters.
Component
Components represent physical electronic parts with pins and properties.Component
Constructor:
Component(**kwargs)
Key parameters:
name
: Instance name (required)footprint
: PCB footprint (required)symbol
: Symbol object defining pins (required)pins
: Pin connections to nets (required)prefix
: Reference designator prefix (default: “U”)mpn
: Manufacturer part numbertype
: Component typeproperties
: Additional properties dict
Interface
Interfaces define reusable connection patterns with field specifications, type validation, and promotion semantics.Basic Syntax
Field Types
Net Instances: Use the provided Net instance as the default templateInterface Instantiation
- Optional name: First positional argument sets the interface instance name
- Field overrides: Named parameters override defaults
- Type validation: Values must match field specifications
Examples
Promotion Semantics
Fields marked withusing()
enable automatic type promotion when passing interfaces across module boundaries:
Rules:
- Unique promotion targets: Only one
using()
field per type per interface - duplicate promotion targets to the same type are not allowed - Cross-module promotion: Automatic conversion when crossing module boundaries
- Same-module access: Explicit field access required within same module
- Type safety: Promotion only occurs when target type exactly matches the expected type
Post-Initialization Callbacks
self
and cannot be overridden during instantiation.
Type: interface
Constructor:
interface(**fields)
- Fields can be Net instances, interface instances,
field()
specifications, orusing()
specifications
Module
Modules represent hierarchical subcircuits that can be instantiated multiple times. Module objects support indexing to access child components and submodules directly.Module
Constructor:
Module(path)
Module Indexing: module[name]
supports:
- Single names:
module["ComponentName"]
returns Component or Module objects - Nested paths:
module["Sub.Component"]
equivalent tomodule["Sub"]["Component"]
- Deep nesting:
module["A.B.C"]
equivalent tomodule["A"]["B"]["C"]
- Returns Component objects for leaf components, Module objects for intermediate submodules
- Raises an error if any part of the path is not found
name in module
supports:
- Single names:
"ComponentName" in module
checks if component or submodule exists - Nested paths:
"Sub.Component" in module
equivalent to checking nested existence - Returns
True
if the path exists,False
otherwise - Works with the same path syntax as indexing
module.nets
: Dict mapping net names to lists of connected port tuplesmodule.components
: Dict mapping component paths to component objects
- Component paths in
module.components
follow the patternSubmoduleName.ComponentName
(e.g.,"BMI270.BMI270"
,"C1.C"
) - The first part is the submodule name, the second part is the component name within that submodule
- Indexing supports both single names and nested paths:
module["BMI270"]
returns the BMI270 submodulemodule["BMI270"]["BMI270"]
returns the component within the submodule (chained)module["BMI270.BMI270"]
also returns the component (nested path syntax)
- All three approaches are equivalent for accessing nested components
TestBench
TestBench values represent the results of module validation tests. They are created by theTestBench()
function and contain information about the tested module and check results.
TestBench
Created by:
TestBench()
function (see Built-in Functions)
Properties accessible via the TestBench value:
name
: The test bench identifier- Module evaluation status
- Check function results
PhysicalRange
APhysicalRange
represents a bounded range of physical values with an optional nominal value, used for specifying operating ranges, tolerances, and electrical characteristics. Ranges have minimum and maximum bounds with consistent physical units.
PhysicalRange
Created by: Unit-specific range constructors (e.g., VoltageRange
, CurrentRange
)
Properties:
min
: Minimum value (Decimal)max
: Maximum value (Decimal)nominal
: Optional nominal/typical value (Decimal or None)unit
: Physical unit dimensions
- With nominal:
11–26 V (12 V nom.)
- Without nominal:
11–26 V
PhysicalRangeType
APhysicalRangeType
is a factory type that creates PhysicalRange
instances for a specific physical unit. Each unit has its own range type (e.g., VoltageRange
for volts, CurrentRange
for amperes).
PhysicalRangeType
Created by: builtin.physical_range(unit)
(see Built-in Functions)
Standard Range Types (available in @stdlib/units.zen
):
VoltageRange
- Voltage ranges (V)CurrentRange
- Current ranges (A)ResistanceRange
- Resistance ranges (Ω)CapacitanceRange
- Capacitance ranges (F)InductanceRange
- Inductance ranges (H)FrequencyRange
- Frequency ranges (Hz)TemperatureRange
- Temperature ranges (K)TimeRange
- Time ranges (s)PowerRange
- Power ranges (W)
-
String parsing with separators:
- En-dash:
"1.1–3.6V"
- Word “to”:
"11V to 26V"
- Left side can omit unit:
"11–26V"
- En-dash:
-
Nominal value extraction:
- Parenthesized suffix:
"11–26 V (12 V nom.)"
- Can be combined with string ranges or keyword override
- Parenthesized suffix:
-
Tolerance expansion:
- Percentage tolerance:
"15V 10%"
expands to13.5–16.5 V
- Percentage tolerance:
-
PhysicalValue conversion:
- Single value:
VoltageRange(Voltage(15))
creates point range15–15 V
- Toleranced value:
VoltageRange(Voltage(15, 0.1))
expands tolerance to range
- Single value:
-
Keyword arguments:
- Explicit bounds:
min=11, max=26
- With nominal:
min=11, nominal=16, max=26
- Accepts numbers or strings:
min="11V"
,max="26V"
- Explicit bounds:
-
Mixed modes:
- String range with keyword nominal:
VoltageRange("11V to 26V", nominal="16V")
- String range with keyword nominal:
- Units must be consistent across min, max, and nominal values
- Range types are distinct:
VoltageRange
≠CurrentRange
- Type checking enforces unit correctness at compile time
- Component operating ranges:
input_voltage = VoltageRange("2.7V to 5.5V")
- Load specifications:
output_current = CurrentRange("0A to 3A")
- Environmental conditions:
operating_temp = TemperatureRange("-40C to 85C")
- Future: Electrical Rule Checking (ERC) - validate that supply ranges satisfy load requirements
Built-in Functions
moved(old_path, new_path)
Parametersold_path
: The old path that should be remappednew_path
: The new path to remap to
moved()
directive allows you to specify path remapping for refactoring support. When modules or components are moved or renamed, downstream artifacts (like layout files) may still reference the old paths. The moved()
directive provides a mapping from old paths to new paths.
moved()
directives are scoped to the module where they are defined. They affect how paths from that module are resolved in downstream artifacts.
If the path refers to a module, all children of that module are automatically remapped:
io(name, type, checks=None, default=None, optional=False)
Declares a net or interface input for a module.name
: String identifier for the input (positional)type
: Expected type -Net
or interface type (positional)checks
: Optional check function or list of check functions to run on the value at eval time (3rd positional or named)default
: Default value if not provided by parent (named)optional
: If True, returns None when not provided (unless default is specified) (named)
config(name, type, default=None, convert=None, optional=False)
Declares a configuration value input for a module.name
: String identifier for the inputtype
: Expected type (str, int, float, bool, enum, or record type)default
: Default value if not providedconvert
: Optional conversion functionoptional
: If True, returns None when not provided (unless default is specified)
File(path)
Resolves a file or directory path using the load resolver.Path(path, allow_not_exist=False)
Advanced path resolution supporting all LoadSpec formats with optional existence checking.path
: String path to resolve (supports any LoadSpec format)allow_not_exist
: Optional boolean (default: False). If True, allows non-existent paths. Can only be used with local path LoadSpecs, not package/GitHub/GitLab specs.
error(msg)
Raises a runtime error with the given message.check(condition, msg)
Checks a condition and raises an error if false.add_property(name, value)
Adds a property to the current module instance.builtin.physical_range(unit)
Built-in function that creates unit-specific range constructor types for defining bounded physical value ranges.unit
: String identifier for the physical unit (e.g.,"V"
,"A"
,"Ohm"
,"F"
,"H"
,"Hz"
,"K"
,"s"
,"W"
)
PhysicalRangeType
that can be called to create PhysicalRange
instances
Standard Usage:
This builtin is typically accessed through @stdlib/units.zen
, which provides pre-defined range constructors:
VoltageRange
- Voltage ranges (V)CurrentRange
- Current ranges (A)ResistanceRange
- Resistance ranges (Ω)CapacitanceRange
- Capacitance ranges (F)InductanceRange
- Inductance ranges (H)FrequencyRange
- Frequency ranges (Hz)TemperatureRange
- Temperature ranges (K)TimeRange
- Time ranges (s)PowerRange
- Power ranges (W)
- Units must be consistent:
VoltageRange("5V to 3A")
→ Error (mixing V and A) - Range types are distinct: Cannot pass a
VoltageRange
whereCurrentRange
is expected - Type checking enforces correctness at compile time
builtin.add_board_config(name, config, default=False)
Built-in function for registering board configurations with the layout system. Parameters:name
: String identifier for the board configurationconfig
:BoardConfig
object containing design rules and stackupdefault
: If True, this becomes the default board config for the project
Board()
function rather than directly.
builtin.add_electrical_check(name, check_fn, inputs=None)
Built-in function for registering electrical validation checks that run duringpcb build
.
Parameters:
name
: String identifier for the check (required)check_fn
: Function to execute for validation (required)inputs
: Optional dictionary of input parameters to pass to the check function
- Checks are registered during module evaluation via
builtin.add_electrical_check()
- Check functions and inputs are stored as frozen values
- During
pcb build
, after evaluation completes, all checks are collected from the module tree - Each check executes with a fresh evaluator context
- Check failures generate error diagnostics that are reported through the standard diagnostic system
- Build fails if any electrical checks fail
- Checks run only during
pcb build
, not duringpcb test
(useTestBench
for test-specific validation) - Check failures generate error-level diagnostics
- Checks can access the entire module structure, including components, nets, interfaces, and properties
- Multiple checks with the same name are allowed (they execute independently)
Board Configuration Types
The board configuration record types are provided by@stdlib/board_config.zen
and offer comprehensive control over PCB manufacturing specifications.
Available Records and Functions:
Board(name, layout_path, config=None, layers=None, layout_hints=None, default=False)
Stdlib function for declaring board configurations for PCB layout generation.name
: String identifier for the board configuration (required)layout_path
: Directory path where layout files will be generated (required)config
:BoardConfig
record containing design rules and stackup (optional, mutually exclusive withlayers
)layers
: Number of layers - 2, 4, 6, or 8 (optional, mutually exclusive withconfig
). When specified, uses predefined stackups from the stdlib:layers=2
: 2-layer board (1.6mm, 1oz copper, SIG/SIG)layers=4
: 4-layer board (1.6mm, 1oz outer/0.5oz inner, SIG/GND/PWR/SIG)layers=6
: 6-layer board (1.6mm, 1oz outer/0.5oz inner, SIG/GND/(SIG/PWR)/(SIG/PWR)/GND/SIG)layers=8
: 8-layer board (1.6mm, 1oz outer/0.5oz inner, SIG/GND/SIG/PWR/GND/SIG/GND/SIG)
layout_hints
: Optional list of layout optimization hintsdefault
: If True, this becomes the default board config for the project
config
or layers
must be provided, but not both. If neither is provided, an error will be raised.
Implementation: The stdlib Board()
function calls the language builtin builtin.add_board_config()
and sets layout properties.
BoardConfig Structure:
design_rules
: Optional PCB design rules and constraintsstackup
: Optional layer stackup and materials configurationnum_user_layers
: Number of User.N layers to create (default: 4)spec
: Optional path to a specification markdown file. UsePath()
to resolve the path:
TestBench(name, module, checks)
Creates a test bench for validating module connectivity and properties without requiring inputs.name
: String identifier for the test benchmodule
: Module instance to test (created withModule()
)checks
: List of check functions to execute
Module
argument containing circuit data:
module.nets
: Maps each net name to a list of connected port tuples (e.g.,{"VCC": [("U1", "VDD"), ("C1", "P1")]}
)module.components
: Maps component paths to component objects (e.g.,{"U1.IC": <Component>, "C1.C": <Component>}
)module["name"]
: Direct indexing access to child components and submodules by name
- Check functions should use
check(condition, message)
orerror(message)
to signal failures - Any unhandled error or exception in a check function is treated as a test failure
- Check functions do not need to return any specific value
- Use
print()
for informational output during testing
- Evaluates the module with relaxed input requirements (missing required inputs are allowed)
- Stores check functions for deferred execution (checks run later when using
pcb test
command) - Returns a TestBench value containing test cases and deferred checks
- When executed by
pcb test
: runs each check function in order, reports failures as diagnostics with precise source location pointing to the failingcheck()
call, and prints a success message if all checks pass
Circuit Graph Analysis & Path Validation
Overview
Zener provides circuit graph analysis for validating module connectivity and topology. The system converts circuit schematics into searchable graphs, enabling path finding between component pins and verification of component sequences. The graph analysis operates on the public interface paradigm: path finding works between component ports (specific IC pins) and external nets (module’s io() declarations), while automatically discovering internal routing paths.Core Concepts
Circuit Graph
Every module automatically generates a circuit graph that models component connectivity:Public Interface Boundaries
Path finding operates between two types of well-defined endpoints: Component Ports: Specific pins on specific componentsPath Finding API
graph.paths(start, end, max_depth=10)
Finds all simple paths between two points in the circuit:start
: Component port tuple("Component", "Pin")
or external net nameend
: Component port tuple("Component", "Pin")
or external net namemax_depth
: Maximum number of components to traverse (default: 10)
Path Objects
Each path contains discovered connectivity information:Path Validation Methods
Basic Validation
Sequential Pattern Matching
Thepath.matches()
method validates component sequences in order:
Design Principles
Datasheet Requirements Translation
Circuit validation can directly implement datasheet requirements by mapping component pin constraints to path validation: Power Supply DecouplingPublic Interface Paradigm
Start/End Points: Always use the module’s “public” interface:- Component ports: Known IC pins from the main component
- External nets: Public nets from io() declarations
- Deterministic scope: Clear rule boundaries
- Implementation freedom: Internal routing flexibility
- Hierarchical composability: Rules work at any module level
- Performance: Constrained search space
Validation Strategies
Path Existence: Verify required connections existsuppress_errors=True
for path identification
path.matches() API Reference
Syntax
*matchers
: Sequential matcher functions to apply in ordersuppress_errors
: If True, returns False on validation failure instead of raising errors
Sequential Processing Model
- Matchers consume components sequentially using cursor-based processing
- Each matcher receives
(path, cursor_index)
and returns components consumed - Validation fails if any matcher fails or if components remain after all matchers
Built-in Matcher Functions
Custom Matcher Functions
Module System
Module Definition
A module is defined by a.zen
file that declares its inputs and creates components: