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
Built-in Functions
io(name, type, default=None, optional=False)
Declares a net or interface input for a module.name
: String identifier for the inputtype
: Expected type (Net
or interface type)default
: Default value if not provided by parentoptional
: If True, returns None when not provided (unless default is specified)
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.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.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)
- Executes each check function in order
- Reports failures as diagnostics with precise source location pointing to the failing
check()
call - Prints a success message if all checks pass
- Returns a TestBench value containing results
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: