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 theMyFunctionandMyTypesymbols from theMyFile.zenmodule. - It can be loaded as a schematic module using the
Module()helper. For example,MyFile = Module("./MyFile.zen")will importMyFile.zenas 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→ Pinned to the toolchain version (e.g.,@github/diodeinc/stdlib:v0.4.4)
@stdlib alias is special: its version is controlled by the pcb toolchain, not by user configuration. This ensures stdlib is always compatible with your toolchain version. You don’t need to declare stdlib in your [dependencies] - it’s implicitly available.
These can be used directly:
Custom Package Aliases
You can define custom package aliases or override the defaults in your workspace’spcb.toml:
[dependencies], but you cannot use an older version.
Core Types
Net
ANet represents an electrical connection between component pins. Nets can optionally specify electrical properties like impedance, voltage range, and schematic symbols.
Net
Constructor: Net(name="", symbol=None, voltage=None, impedance=None)
name(optional): String identifier for the netsymbol(optional): Symbol object for schematic representationvoltage(optional): VoltageRange specification for the netimpedance(optional): Impedance specification for single-ended nets (in Ohms)
Symbol
ASymbol represents a schematic symbol definition with its pins. Symbols can be created manually or loaded from KiCad symbol libraries.
SymbolConstructor:
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.ComponentConstructor:
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 and type validation. Interfaces can specify impedance requirements that automatically propagate to their constituent nets during layout. Differential Pair Example:differential_impedance on the P and N nets, allowing the layout system to assign appropriate netclasses for differential pair routing.
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
Post-Initialization Callbacks
self and cannot be overridden during instantiation.
Type: interfaceConstructor:
interface(**fields)
- Fields can be Net instances, interface instances, or
field()specifications
Module
Modules represent hierarchical subcircuits that can be instantiated multiple times. Module objects support indexing to access child components and submodules directly.ModuleConstructor:
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 modulechecks if component or submodule exists - Nested paths:
"Sub.Component" in moduleequivalent to checking nested existence - Returns
Trueif the path exists,Falseotherwise - 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.componentsfollow 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.
TestBenchCreated 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
.diff(other)- Returns the maximum possible absolute difference between two rangesother: Another PhysicalRange or string (e.g.,"0V","1.7V to 2.0V")- Returns PhysicalValue with the worst-case difference
- Units must match or an error is raised
- Tolerance is always zero (like PhysicalValue.diff())
- Useful for determining component voltage ratings (capacitors, level shifters, etc.)
-
Addition (
+) - Shift a range by a valuerange + valuereturns a new range with min/max/nominal shifted by the value- Units must match (or value must be dimensionless)
- The value’s tolerance is ignored (only the numeric value is used)
-
Subtraction (
-) - Shift a range down by a valuerange - valuereturns a new range with min/max/nominal shifted down by the value- Units must match (or value must be dimensionless)
-
Comparison (
<,>,<=,>=) - Compare ranges using conservative semanticsrange1 < range2is true iffrange1.max < range2.min(entire range1 is strictly below range2)range1 > range2is true iffrange1.min > range2.max(entire range1 is strictly above range2)- For overlapping ranges,
maxvalues are compared as a tiebreaker - Can compare with:
PhysicalRange,PhysicalValue, or string values - Units must be compatible or an error is raised
-
Equality (
==) - Check if two ranges are identical- Returns true if
min,max,nominal, andunitall match - Can compare with:
PhysicalRangeor string values
- Returns true if
-
Containment (
in) - Check if a value fits within the rangevalue in rangereturns true if value’s bounds fit entirely within range’s bounds- Works with
PhysicalValue,PhysicalRange, or string values
-
Unary negation (
-) - Negate the range- Returns a new range with negated min/max (swapped) and negated nominal
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 -Netor 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, suppress=False, kind=None)
Raises a runtime error with the given message. Withsuppress=True, the error is rendered but doesn’t stop evaluation or fail the build.
msg: Error message (required)suppress: If True, renders diagnostic but doesn’t stop evaluation or fail build (default: False)kind: Optional diagnostic kind for filtering/categorization (e.g., “electrical.voltage_mismatch”)
check(condition, msg)
Checks a condition and raises an error if false.warn(msg, suppress=False, kind=None)
Emits a warning diagnostic with the given message and continues execution. Unlikeerror(), this does not stop evaluation.
msg: Warning message (required)suppress: If True, won’t cause build failure with-Dwarningsflag (default: False)kind: Optional diagnostic kind for filtering/categorization (e.g., “electrical.overvoltage”)
kind parameter enables hierarchical categorization of diagnostics for filtering at the CLI level:
-S flag with hierarchical matching:
-S electricalsuppresses all electrical.* warnings-S electrical.voltagesuppresses electrical.voltage.* warnings-S warningssuppresses all warnings regardless of kind-S errorssuppresses all errors regardless of kind
add_property(name, value)
Adds a property to the current module instance.builtin.net(type_name, **fields)
Built-in function that creates custom typed net constructors with optional field parameters.type_name: String name for the net type (required, positional)**fields: Keyword arguments defining optional typed fields- Each field can be a
field()spec (with default), a type constructor (str/int/float/bool), an enum type, or a physical value type
- Each field can be a
field(type, default)- Typed field with a default value (from starlark’s builtinfield())str,int,float,bool- Direct type constructors (no default)EnumType- Enum type created withenum()Voltage,Current, etc. - Physical value types from stdlib
- Fields can be accessed as attributes on net instances:
net.field_name - If a field was not provided and has no default, accessing it will fail
- Fields are stored in the net’s properties and can be introspected
- Field values are validated at net instantiation time using starlark’s type system
- Type mismatches produce clear error messages:
- Validation works uniformly for all type specifications: builtin types, field() wrappers, enums, and custom types
builtin.physical_value(unit)
Built-in function that creates unit-specific physical value constructor types for electrical quantities with optional tolerances.unit: String identifier for the physical unit (e.g.,"V","A","Ohm","F","H","Hz","K","s","W")
PhysicalValueType that can be called to create PhysicalValue instances
Standard Usage:
This builtin is typically accessed through @stdlib/units.zen, which provides pre-defined constructors:
.with_tolerance(tolerance)- Returns a new physical value with updated tolerancetolerance: String like"5%"or decimal like0.05
.with_value(value)- Returns a new physical value with updated numeric valuevalue: Numeric value (int or float)
.with_unit(unit)- Returns a new physical value with updated unit (for unit conversion/casting)unit: String unit identifier orNonefor dimensionless
.abs()- Returns the absolute value of the physical value, preserving unit and tolerance- No parameters required
.diff(other)- Returns the absolute difference between two physical valuesother: Another PhysicalValue or string (e.g.,"5V") to compare against- Units must match or an error is raised
- Always returns a positive value
- Tolerance is dropped (consistent with subtraction behavior)
.within(other)- Checks if this value’s tolerance range fits completely within another’sother: Another PhysicalValue or string (e.g.,"3.3V")- Returns
Trueif self’s range is completely contained within other’s range - Units must match or an error is raised
.value- The numeric value as a float.tolerance- The tolerance as a decimal fraction (e.g., 0.05 for 5%).unit- The unit string representation
Voltage("3.3V") + "2V" works and returns 5.3V.
Tolerance Handling:
- Multiplication/Division: Tolerance preserved only for dimensionless scaling (e.g.,
2 * 3.3V±1%keeps 1%) - Addition/Subtraction: Tolerance is always dropped
.abs(): Tolerance is preserved.diff(): Tolerance is dropped (consistent with subtraction).within(): Considers tolerance ranges for both values
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
VoltageRangewhereCurrentRangeis 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:BoardConfigobject 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(useTestBenchfor 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)
builtin.add_component_modifier(modifier_fn)
Built-in function for registering component modifier functions that automatically run on every component created in the current module and all descendant modules. Parameters:modifier_fn: Function that accepts a component and modifies it (required)
- Modifiers are registered during module evaluation via
builtin.add_component_modifier() - When a child module is instantiated, it inherits all ancestor modifiers
- Each component creation triggers modifiers in bottom-up order:
- Module’s own modifiers execute first
- Parent’s modifiers execute next
- Grandparent’s and further ancestors follow
- Modifiers can read and write any component property (mpn, manufacturer, dnp, custom properties)
- Modifiers only apply to components created AFTER registration
- Parent modifiers run AFTER child modifiers (can override child choices)
- Modifiers are inherited through the entire module hierarchy
- Common uses: vendor policies, DNP rules, property validation, debug tagging
match_component(match, parts)
Stdlib helper function (from@stdlib/bom/helpers.zen) for creating component modifiers that assign MPNs based on property matching.
Parameters:
match: Dict of property names to values (all must match)parts: Tuple(mpn, manufacturer)or list of tuples (first is primary, rest are alternatives)
builtin.current_module_path()
Built-in function that returns the current module’s path as a list of strings. Returns: List of strings representing the module hierarchy- Root module:
[](empty list) - Child module:
["child_name"] - Nested module:
["parent_name", "child_name"]
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, modifiers=None, bom_profile=add_default_bom_profile)
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:BoardConfigrecord 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 projectmodifiers: Optional list of component modifier functions that run before BOM profile modifiers. Use this to override BOM matching or set custom part numbers for specific components. Functions are registered only at the root module level.bom_profile: Function that registers BOM matching modifiers (default:add_default_bom_profile). The default profile automatically assigns manufacturer part numbers to generic resistors and capacitors. Passlambda: Noneto disable BOM matching.
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 testcommand) - 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: