.claude/skills/ato-language/SKILL.md
Reference for the `.ato` declarative DSL: type system, connection semantics, constraint model, and standard library. Use when authoring or reviewing `.ato` code.
npx skillsauth add atopile/atopile ato-languageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
ato is a declarative, constraint-based DSL for describing electronic circuits. There is no control flow, no mutation, and no execution order — you declare what a circuit is, and the compiler + solver resolve it into a valid design.
A minimal complete .ato file:
#pragma experiment("BRIDGE_CONNECT")
import Resistor
import ElectricPower
import Capacitor
module PowerFilter:
"""A simple decoupled power input with a pull-down resistor."""
power = new ElectricPower
decoupling_capacitor = new Capacitor
pulldown_resistor = new Resistor
power.hv ~> decoupling_capacitor ~> power.lv
power.hv ~> pulldown_resistor ~> power.lv
decoupling_capacitor.capacitance = 100nF +/- 20%
pulldown_resistor.resistance = 100kohm +/- 5%
assert power.voltage within 3.0V to 3.6V
Validate with ato build from the package directory.
Every entity (a resistor, a power rail, an I2C bus, a voltage parameter) is a node in a typed graph. Nodes relate to each other through edges: composition (parent–child), connection (same-net), and traits (behavioral metadata). The .ato language is a surface syntax for constructing this graph declaratively.
ato has exactly three ways to define a new type:
| Keyword | Semantics | Typical Use |
| ----------- | ---------------------------------------------------- | --------------------------- |
| module | A design unit that contains children and connections | Circuit blocks, subsystems |
| interface | A connectable boundary; can be wired with ~ | Buses, power rails, signals |
| component | A physical part with footprint/symbol | Vendor ICs, connectors |
All three compile to graph nodes. The distinction controls which traits the compiler attaches (is_module, is_interface) and what operations are legal (by convention, interfaces appear on both sides of ~).
Inheritance uses from:
module MyRegulator from Regulator:
pass
Types contain children. Inside a block body, new instantiates a child:
module Board:
power = new ElectricPower # interface child
sensor = new BME280 # module child
caps = new Capacitor[4] # array of 4 capacitors
Children are accessed via dot-notation: sensor.power.voltage, caps[0].capacitance.
The wire operator ~ declares that two interfaces are the same net/bus. It is bidirectional and requires matching types:
power_3v3 ~ sensor.power # ElectricPower ~ ElectricPower
i2c_bus ~ sensor.i2c # I2C ~ I2C
The bridge operator ~> (requires #pragma experiment("BRIDGE_CONNECT")) inserts a component in series. The component must carry the can_bridge trait which defines its in/out mapping:
power_5v ~> regulator ~> power_3v3
i2c.scl.line ~> pullup ~> power.hv
Values in ato carry units and tolerances. The solver uses these to select real parts.
Assignment binds a value to a parameter:
power.voltage = 3.3V +/- 5%
resistor.resistance = 10kohm +/- 10%
i2c.frequency = 400kHz
i2c.address = 0x48
Assertions declare constraints the solver must satisfy:
assert power.voltage within 3.0V to 3.6V
assert i2c.frequency <= 400kHz
assert sensor.i2c.address is 0x50
Three value forms exist:
3.3V10kohm +/- 5%3.0V to 3.6VTraits attach capabilities or metadata to nodes. They are not children — they use trait edges in the graph.
#pragma experiment("TRAITS")
import has_part_removed
import is_atomic_part
module Placeholder:
trait has_part_removed # mark as non-physical placeholder
trait is_atomic_part # user-defined part with footprint
Key built-in traits:
| Trait | Effect |
| ----------------------- | ---------------------------------------------------------------- |
| can_bridge | Enables use with ~> operator (defines in/out pin mapping) |
| has_part_removed | No physical part placed (symbolic node) |
| is_atomic_part | User-defined part with manufacturer, partnumber, footprint |
| has_datasheet | Attaches a datasheet reference |
| has_designator_prefix | Sets PCB designator (R, C, U, etc.) |
Bare imports resolve to standard library types (1 line per import):
import ElectricPower
import I2C
import Resistor
Path imports resolve to types defined in other .ato files (1 line per import):
from "atopile/vendor-part/vendor-part.ato" import Vendor_Part
Experimental syntax is gated behind pragmas (file top, before imports):
#pragma experiment("BRIDGE_CONNECT") # ~> operator
#pragma experiment("FOR_LOOP") # for loops
#pragma experiment("TRAITS") # trait keyword
#pragma experiment("MODULE_TEMPLATING") # new Foo<p=v>
#pragma experiment("INSTANCE_TRAITS") # traits on instances
Using gated syntax without the pragma is a compile error.
Every statement inside a block body is one of:
| Statement | Syntax | Purpose |
| --------- | ----------------------------------- | -------------------------------------- |
| assign | name = value or name = new Type | Bind a value or instantiate a child |
| connect | a ~ b | Wire two interfaces together |
| bridge | a ~> b ~> c | Insert bridgeable components in series |
| assert | assert expr <op> expr | Declare a constraint |
| retype | name -> NewType | Replace an inherited child's type |
| pin | pin VCC | Declare a physical pin |
| signal | signal reset | Declare an electrical signal |
| trait | trait TraitName | Attach a trait |
| import | import Type | Import a type |
| for | for x in arr: | Iterate over an array (pragma-gated) |
| string | """...""" | Documentation string |
| pass | pass | Empty placeholder |
Statements within a block are order-independent — the compiler resolves the full graph, not a sequence of operations.
~ or ~>)| Type | Children / Parameters | Purpose |
| ------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------ |
| Electrical | (single node) | Raw electrical connection point |
| ElectricPower | .hv, .lv (Electrical); .voltage, .max_current | Power rails |
| ElectricLogic | .line (Electrical), .reference (ElectricPower) | Digital signals with voltage context |
| ElectricSignal | .line (Electrical), .reference (ElectricPower) | Analog signals |
| I2C | .scl, .sda (ElectricLogic); .frequency, .address | I2C bus |
| SPI | .sclk, .mosi, .miso (ElectricLogic); .frequency | SPI bus |
| UART / UART_Base | .tx, .rx (ElectricLogic); flow control lines | Serial |
| I2S | audio data bus lines | Digital audio |
| DifferentialPair | .p, .n | Differential signals |
| USB2_0 / USB3 / USB2_0_IF | USB data + power | USB interfaces |
| CAN_TTL | CAN bus lines | CAN bus |
| SWD / JTAG | debug lines | Debug interfaces |
| Ethernet / HDMI / RS232 / PDM / XtalIF / MultiSPI | protocol-specific | Other protocols |
new)| Type | Children / Parameters | Designator |
| ----------------------------------- | -------------------------------------------------------------------------------------- | ---------- |
| Resistor | .unnamed[0..1]; .resistance, .max_power | R |
| Capacitor | .unnamed[0..1], .power; .capacitance, .max_voltage, .temperature_coefficient | C |
| CapacitorPolarized | polarized variant of Capacitor | C |
| Inductor | .unnamed[0..1]; .inductance | L |
| Fuse | .unnamed[0..1]; .trip_current, .fuse_type | F |
| Diode | .anode, .cathode; .forward_voltage, .current | D |
| LED | .diode; .brightness, .color | D |
| MOSFET | .source, .gate, .drain; .channel_type, .gate_source_threshold_voltage | Q |
| BJT | .emitter, .base, .collector; .doping_type | Q |
| Regulator / AdjustableRegulator | .power_in, .power_out | — |
| Crystal | .unnamed[0..1], .gnd; .frequency, .load_capacitance | XTAL |
| Crystal_Oscillator | oscillator module | — |
| ResistorVoltageDivider | voltage divider circuit | — |
| FilterElectricalRC | RC filter | — |
| Net | .part_of (Electrical) | — |
| TestPoint | .contact; .pad_size, .pad_type | TP |
| MountingHole / NetTie | mechanical | — |
| SPIFlash | SPI flash memory | — |
trait)has_part_removed, is_atomic_part, can_bridge, can_bridge_by_name, has_datasheet, has_designator_prefix, has_doc_string, has_net_name_affix, has_net_name_suggestion, has_package_requirements, has_single_electric_reference, is_auto_generated, requires_external_usage
SI-prefixed units: V, mV | A, mA | ohm, kohm, Mohm | F, uF, nF, pF | Hz, kHz, MHz, GHz | s, ms | W, mW
Number formats: decimal (3.3), scientific (1e-6), hex (0x48), binary (0b1010), underscore-separated (1_000_000)
Booleans: True, False
~ and ~> should connect matching interface types. ElectricPower ~ I2C is a type mismatch (enforcement is being strengthened).~>, for, trait, or <> without the matching pragma is a compile error.resistance = 10kohm (zero tolerance) matches no real parts. Always use +/- N%.signal.reference ~ power_rail.if, while, return, functions, classes, or exceptions.development
How the Faebryk parameter solver works (Sets/Literals, Parameters, Expressions), the core invariants enforced during mutation, and practical workflows for debugging and extending the solver. Use when implementing or modifying constraint solving, parameter bounds, or debugging expression simplification.
development
# SEXP Benchmark Strategy ## Goal Measure and improve S-expression pipeline performance with a focus on: - Throughput per stage - Peak memory per stage - End-to-end behavior on realistic KiCad PCB inputs ## Pipeline Stages Benchmark these layers separately: - `tokenizer` - `ast` - `parser` (typed decode) - `encode` (typed encode to raw SEXP) - `pretty` (formatting) ## Dataset Dimensions Use a matrix over: - `depth`: shallow vs deep nesting - `size`: small, medium, large Recommended size buck
development
How the Zig S-expression engine and typed KiCad models work, how they are exposed to Python (pyzig_sexp), and the invariants around parsing, formatting, and freeing. Use when working with KiCad file parsing, S-expression generation, or layout sync.
tools
How the Zig↔Python binding layer works (pyzig), including build-on-import, wrapper generation patterns, ownership rules, and where to add new exported APIs. Use when adding Zig-Python bindings, modifying native extensions, or debugging C-API interactions.