with one click
ato
// Authoritative ato authoring and review skill: language reference, stdlib, design patterns, and end-to-end board design workflow.
// Authoritative ato authoring and review skill: language reference, stdlib, design patterns, and end-to-end board design workflow.
Core runtime behavior for the atopile sidebar agent: identity, persistence model, execution rules, and tool recipes.
Generate short live progress summaries for the atopile agent from recent tool events, preambles, checklist changes, and build state. Use for ephemeral UI activity text only, never for transcript replies or autonomous reasoning.
Spec-driven planning for complex design tasks: when to plan, how to write specs as .ato files, and how to verify against requirements.
Frontend standards for atopile extension webviews: architecture, contracts, design system, and testing workflow.
Reference for the `.ato` declarative DSL: type system, connection semantics, constraint model, and standard library. Use when authoring or reviewing `.ato` code.
How the Faebryk component library is structured, how `_F.py` is generated, and the conventions/invariants for adding new library modules. Use when adding or modifying library components, traits, or module definitions.
| name | ato |
| description | Authoritative ato authoring and review skill: language reference, stdlib, design patterns, and end-to-end board design workflow. |
This is the canonical sequence for designing a board in atopile. Move quickly, keep the structure clean, and avoid spreading planning state across multiple rounds unless the design genuinely requires it.
Capture user intent as ato code immediately. Start with a clean high-level architecture and only stop to ask batched design questions when there are real unresolved decisions.
Focus on:
Tools: Use
design_questionsto batch multiple unresolved decisions at once. Useweb_searchif you need to research unfamiliar domains or components before locking the architecture.
Gate: a spec .ato file exists with module hierarchy, interface connections, requirements in docstrings, and formal constraints.
The spec IS the design file at a high level of abstraction. As you implement, you fill in real components and wiring. The file grows; the structure stays.
Key principles:
ElectricPower, I2C, SPI, UART, ElectricLogic) instead of low-level electrical connections where possible.interface, check the stdlib first with stdlib_list / stdlib_get_item. If an existing stdlib interface or a simple composition/array of stdlib interfaces works, use that instead.Requirements: section on the module that owns them.assert for voltage, current, frequency bounds.~). Do NOT wire pins yet.Step-by-step:
module — power, MCU, sensors, comms, IO, etc.Requirements: section to the docstring of the module that owns each requirement.assert for voltage, current, frequency bounds.~).Example spec:
import ElectricPower
import I2C
import SPI
import ElectricLogic
module SensorBoard:
"""
# Environmental Sensor Board
Battery-powered sensor node with temperature, humidity, and
pressure sensing, BLE comms, and USB-C charging.
## Requirements
- R1: BLE connectivity — nRF52840 with BLE 5.0
- R2: Environmental sensing — BME280 for temp/humidity/pressure
- R3: USB-C charging — 5V USB-C input with charge IC
- R4: Board size — 25mm x 30mm max
## Key Decisions
- nRF52840 for BLE + low power
- BME280 for temp/humidity/pressure
"""
# ── Architecture ──────────────────────────────────────
power = new PowerSupply
mcu = new MCU
sensors = new EnvironmentalSensor
comms = new Radio
# Interface-level wiring (no pins yet)
power.rail_3v3 ~ mcu.power
power.rail_3v3 ~ sensors.power
mcu.i2c ~ sensors.i2c
mcu.spi ~ comms.spi
# ── Constraints ───────────────────────────────────────
assert power.usb_in.voltage within 4.5V to 5.5V
assert power.rail_3v3.voltage within 3.3V +/- 5%
module PowerSupply:
"""
USB-C input, charge controller, LDO regulation.
## Requirements
- R5: Battery charging — LiPo charge IC with thermal protection
"""
usb_in = new ElectricPower
battery = new ElectricPower
rail_3v3 = new ElectricPower
module MCU:
"""nRF52840 with crystal, decoupling, and debug header."""
power = new ElectricPower
i2c = new I2C
spi = new SPI
module EnvironmentalSensor:
"""BME280 environmental sensor."""
power = new ElectricPower
i2c = new I2C
module Radio:
"""BLE antenna matching and RF front end."""
spi = new SPI
Key rules for this step:
PowerSupply stays PowerSupply through implementation. Do NOT suffix with "Spec".design_questions to batch open questions and then continue implementation.Tools: Use
stdlib_list/stdlib_get_itemto check available interfaces and components before defining custom ones. Useexamples_search/examples_read_atoto find reference designs for similar systems.
Gate: architecture is coherent enough to implement. If there are multiple open design decisions, batch them with design_questions and continue once answers arrive.
Present the user with the current architecture and any real unresolved decisions:
Use design_questions to batch unresolved decisions instead of trickling follow-up questions across multiple turns. Then incorporate the answers directly into the spec and continue implementation.
Gate: the key open questions are resolved or reasonable defaults have been chosen.
Now fill in the spec with real components, wiring, and constraints. This step covers package search, part selection, and detailed wiring.
Search the atopile package registry before building from scratch.
Tools:
packages_search→packages_install→package_ato_readto inspect public interface. Also checkstdlib_listfor built-in modules.
When packages_search returns no match for a needed IC, connector, or module, create a local driver package instead of giving up or asking the user to find one.
Tools:
parts_search→web_search(to compare families, inspect the vendor datasheet/design guide, validate topology, and find reference circuits) →parts_install(create_package=true)→project_read_file(to inspect the generated wrapper package) →project_edit_file(to refine that wrapper in place) →workspace_list_targets(to discover nested package targets).
Step-by-step recipe:
parts_search to find the LCSC component (e.g., parts_search("LAN8742A")).web_search before locking the part if you need application notes, common reference circuits, family comparisons, or confirmation that the chosen topology is standard and robust.parts_install with the LCSC ID and create_package=true. This installs the raw part and generates the canonical reusable wrapper package under packages/.web_search with the part number, vendor, and terms like datasheet, hardware design, application circuit, decoupling, pinout, or the specific pins/features you need.packages/<PartName>/<PartName>.ato and the installed raw part it imports to see available interfaces and exact pin names.packages/<PartName>/<PartName>.ato as the canonical wrapper module for that part.ElectricPower, I2C, SPI, UART, CAN, SWD, USB2_0, USB2_0_IF, ElectricLogic, or ElectricSignal.interface, check stdlib_list / stdlib_get_item for an existing stdlib interface and prefer stdlib arrays/composition over project-local aggregate interfaces.uart, spi, adc_inputs, gpio, usb, swd, power, not design-specific roles like sbus, phase_current, weapon_pwm, or battlebot_interfaces._package component pins to those interfaces.parts_install(project_path="packages/<PartName>").workspace_list_targets after package creation to inspect and build the package targets that were exposed automatically.packages/<PartName>/<PartName>.ato.package_agent_spawn(project_path="packages/<PartName>", goal=..., comments=...) so a package specialist can refine that wrapper while you continue top-level integration.Example: refining a generated local I2C mux wrapper
The generated package file under packages/<PartName>/<PartName>.ato is the wrapper you should refine. The raw part component it imports is not the place to edit behavior.
#pragma experiment("BRIDGE_CONNECT")
import ElectricPower
import ElectricLogic
import I2C
import Capacitor
import Resistor
from "parts/Texas_Instruments_TCA9548APWR/Texas_Instruments_TCA9548APWR.ato" import Texas_Instruments_TCA9548APWR_package
module TI_TCA9548A:
# Public interfaces
power = new ElectricPower
assert power.voltage within 1.65V to 5.5V
i2c = new I2C
reset = new ElectricLogic
# Instantiate the auto-generated package component
package = new Texas_Instruments_TCA9548APWR_package
# Power connections
power.hv ~ package.VCC
power.lv ~ package.GND
# I2C — connect via .line and .reference
i2c.sda.line ~ package.SDA
i2c.scl.line ~ package.SCL
i2c.sda.reference ~ power
i2c.scl.reference ~ power
# Decoupling — use bridge connect (~>) for series path
decoup_100n = new Capacitor
decoup_100n.capacitance = 100nF +/- 20%
decoup_100n.package = "0402"
power.hv ~> decoup_100n ~> power.lv
decoup_2u2 = new Capacitor
decoup_2u2.capacitance = 2.2uF +/- 20%
decoup_2u2.package = "0402"
power.hv ~> decoup_2u2 ~> power.lv
# Reset with pullup
reset.line ~ package.nRESET
reset.reference ~ power
reset_pullup = new Resistor
reset_pullup.resistance = 10kohm +/- 1%
reset_pullup.package = "0402"
reset.line ~> reset_pullup ~> reset.reference.hv
Key rules:
parts_install first — never reference a part that hasn't been installed.parts_install(create_package=true) for ICs and other reusable wrapped parts.parts_install(project_path="packages/<name>") for any new supporting parts the package itself imports.package_create_local only when you need an empty local package scaffold without installing a physical part..ato files to see the exact signal names (e.g., package.VCC, package.SDA). Do NOT guess pin names.web_search to inspect the vendor datasheet and hardware design notes to get correct pin mapping, constraints, and recommended decoupling.packages/ is the canonical wrapper for that part. Refine it in place.component — never edit it.package_agent_spawn instead of doing all package work serially in the main agent.package = new <ComponentName>.main.ato should import wrapper packages directly from packages/<name>/<name>.ato, not through an extra aggregator wrapper file..line and .reference (e.g., i2c.sda.line ~ package.SDA; i2c.sda.reference ~ power).~> for decoupling caps in series (e.g., power.hv ~> cap ~> power.lv)..capacitance for Capacitor values, .resistance for Resistor values (NOT .value).#pragma experiment("BRIDGE_CONNECT") if using ~>.Choose components using generics + constraints wherever possible.
Tools:
parts_search/parts_installfor specific ICs/connectors.web_searchfor vendor datasheets, hardware design guides, application notes, and alternative parts.
Resistor, Capacitor, Inductor, Diode, LED, Fuse) with value + package constraints for auto-picking. Prefer generics over locked parts.parts_search only when a specific part is needed (IC, connector, specialized component).web_search before locking a part when you need to compare candidate families, confirm the recommended implementation pattern, or find a solid reference circuit/application note.parts_install for parts that need explicit LCSC IDs, and prefer create_package=true when the part should become a reusable local wrapper.web_search after selecting a concrete part to inspect the vendor datasheet, exact pins, limits, and supporting circuitry.interface, check whether the wrapper boundary can be represented as:
SPI, UART, SWD, USB2_0_IF, etc.)new ElectricLogic[3], new ElectricPower[3], new ElectricSignal[3])main.ato or project modules above the package layer.Wire connectivity, add constraints and equations, complete the design.
~ (or ~> for bridge/series paths).assert ... within ...) for all key electrical properties.Gate: design is complete — all modules wired, all constraints declared, all interfaces connected. Every component is either a constrained generic or an explicitly selected part.
Run builds and fix issues iteratively until everything passes. Build submodules first (if applicable) — it is much easier to get small chunks working before running the full build.
Tools:
workspace_list_targets→build_run→build_logs_search(filter bylog_levels/stage) →design_diagnosticsfor silent failures. Usereport_variablesto inspect constraint state andreport_bomto verify part selection.
workspace_list_targets first after creating/installing local packages so you know which package targets already exist automatically.ato.yaml entries just to build generated local package wrappers if workspace_list_targets already exposes those targets.build_logs_search for errors/warnings.design_diagnostics for silent failures.Tools: Use
report_bomfor parts list andreport_variablesfor constraint summary when preparing the summary.
When the build finishes, give the user a summary:
Gate: user has received a clear summary and knows the state of the design.
Name modules the way you'd label blocks on a system block diagram — by their role in the system, not their implementation topology. Avoid generic suffixes like Subsystem, Unit, Block, or Section.
Good names:
PowerSupply — input protection, regulation, and distributionPowerInput — connector, reverse polarity protection, and bulk decouplingBatteryCharger — charge IC, sense resistors, and status outputBMS — cell balancing, protection, and fuel gaugeGateDriver — bootstrap, dead-time, and level shifting for a FET bridgeMotorDrive — integrated driver with current limit and fault outputCurrentSense — shunt and sense amplifierCANTransceiver — transceiver, termination, and ESD (don't use CAN — it shadows the stdlib interface)USBPort — connector, ESD, and pull-ups (don't use USB — too generic, may shadow stdlib types)EthernetPHY — PHY, magnetics, and RJ45Radio — RF front end, antenna match, and balunIMU — accelerometer/gyro with decouplingADCInput — anti-alias filter, reference, and input scalingLevelShift — voltage translation between power domainsInputFilter — common-mode choke and filter capsClock — crystal or oscillator with load capsDebug — SWD/JTAG connector and pull-upsIndicators — status LEDs with current-limiting resistorsProtection — ESD, TVS, or overvoltage clamping on an interfaceIC wrapper packages use the part name directly: STM32G474, DRV8317, TCAN3414.
Inside a module, names get more specific — a PowerSupply module might contain a BuckConverter and an LDO. The name should match the level of abstraction: system-level blocks use system-level names.
When in doubt, ask: "what would this block be labelled on a system block diagram?"
For non-trivial designs, split early and keep one primary module per file.
Complexity triggers that force splitting:
Example file layout:
main.ato # integration module only
ato.yaml # project-level builds
packages/
stm32g474/
stm32g474.ato # reusable wrapper package
power/
buck_5v.ato # project-specific power stage
buck_3v3.ato # project-specific regulator stage
control/
motor_control.ato # project-specific control module
io/
connectors.ato # project-specific adapters/connectors
Keep main.ato at the project root. Put reusable IC wrappers under packages/. Put project-specific implementation modules in sibling folders only when they help keep the design clean.
Imagine the board comes back from the factory, gets plugged in, and it's your job to bring it up — automatically, at scale. Design with that scenario in mind from the start.
Before wiring, walk through the commissioning sequence in your head:
Include the connectors, headers, and test points needed for this flow in your design. A debug header that gets cut to save $0.10 will cost hours during bringup.
Every power rail that matters should be observable — ideally by the MCU itself, not just a multimeter:
# Example: ADC-measurable voltage rail
adc_sense = new ResistorVoltageDivider
power_12v.hv ~> adc_sense ~> power_12v.lv
adc_sense.output ~ mcu.adc[0]
assert adc_sense.ratio within 0.2 to 0.3 # scale 12V into MCU ADC range
When designing, ask yourself:
ato is a declarative language for defining electronics. Every statement constructs graph nodes and edges or declares constraints — there is no runtime execution order. This section covers the language by concept with practical examples.
Three block kinds exist: module, component, and interface.
module PowerSupply:
"""Regulate 5V input down to 3.3V."""
pass
component MyChip:
pin VCC
pin GND
interface MyBus:
signal data
signal clock
module for reusable hardware building blocks with components and equations.interface for reusable connectable bus/signal/power abstractions.component for physical parts with pin mappings (usually auto-generated — you rarely write these by hand).Inheritance specializes a block:
module USB2_0TypeCVerticalConnector from USBTypeCConnector_driver:
connector -> VerticalUSBTypeCConnector_model
Rules:
Create instances with new. Bare type assignment is invalid.
# Good
r = new Resistor
caps = new Capacitor[4]
# Bad — will error
r = Resistor
Arrays create indexed sequences:
leds = new LED[8]
leds[0].lcsc_id = "C2286"
leds[3].lcsc_id = "C2297"
Templated construction (requires MODULE_TEMPLATING pragma):
#pragma experiment("MODULE_TEMPLATING")
addressor = new Addressor<address_bits=3>
Access fields with dotted paths and array indexing:
power # local field
power.hv # sub-field
i2c.sda.line # nested access
gpio[0].line # indexed access
microcontroller.uart[0].tx # deep path
Declare typed parameters with unit annotation:
v_in: V
v_out: V
max_current: A
ratio: dimensionless
Declared parameters can then be used in equations and constraints.
~Use for net-level or interface-level equivalence. This is the primary connection operator.
power_3v3 ~ sensor.power # interface-level
i2c_bus ~ sensor.i2c # bus-level
gpio.line ~ package.PA0 # signal-level
When you connect interfaces, all matching sub-fields connect recursively.
~> (requires BRIDGE_CONNECT pragma)Use for series/inline topology through bridgeable elements:
#pragma experiment("BRIDGE_CONNECT")
power_in ~> fuse ~> ldo ~> power_out # power path
power.hv ~> cap ~> power.lv # decoupling
data_in ~> led_strip ~> data_out # daisy chain
Bridge connect traverses can_bridge trait paths (in/out). Only use when the module is physically in series.
Rules:
a ~> b <~ c is invalid.~> and <~ exist but keep chains in one direction.Use assert to declare constraints over parameter domains.
within — for bounds and ranges (preferred for values)assert power.voltage within 3.3V +/- 5%
assert power.voltage within 1.8V to 5.5V
assert i2c.frequency within 100kHz to 400kHz
is — for expression identity between parametersassert addressor.address is i2c.address
assert v_out is v_in * r_bottom.resistance / r_total
assert r_total is r_top.resistance + r_bottom.resistance
assert power.voltage >= 3.0V
assert max_current <= 500mA
assert inductor.max_current >= peak_current
Rules:
within for literal/range bounds. Use is for relating expressions/parameters.assert x is 3.3V (deprecated) — use assert x within 3.3V +/- 0% instead.assert 1V < x < 5V is NOT supported. Split into two asserts.>, >=, <, <=, within, is.3.3V
10kohm
100nF
2.4MHz
1A
1.8V to 5.5V
100kHz to 400kHz
0.5mA to 3mA
10kohm +/- 1kohm
3.3V +/- 0.2V
10kohm +/- 5%
3.3V +/- 5%
ratio: dimensionless
ratio = 0.5
assert ratio within 0.1 to 0.9
Supported operators: +, -, *, /, **, parentheses ().
assert resistor.resistance is (power.voltage - led.forward_voltage) / current
assert peak_current is (i_out / (efficiency * (1 - duty))) + ((v_in * duty) / (2 * f_sw * l))
Rules:
3.3V to 5A is invalid).| and & operators parse but are rejected semantically — never use them.V, A, ohm/kohm/Mohm, F/uF/nF/pF, Hz/kHz/MHz, W, H/uH, mm. SI prefixes (m, u, n, k, M) are supported.import ElectricPower
import I2C
import Resistor
import Capacitor
Bare import X must resolve to a stdlib-allowlisted type or trait. If not allowlisted, you get a DslImportError.
from "atopile/ti-tlv75901/ti-tlv75901.ato" import TI_TLV75901
from "packages/stm32g474/stm32g474.ato" import STM32G474
from "parts/Texas_Instruments_TCA9548APWR/Texas_Instruments_TCA9548APWR.ato" import Texas_Instruments_TCA9548APWR_package
from "./power/buck_5v.ato" import Buck5V
Rules:
import A, B is deprecated).main.ato should import reusable local wrappers directly from packages/<name>/<name>.ato../relative/path.ato for truly local sibling files, not for canonical wrapper packages under packages/.Traits attach metadata and behavior to blocks. Use them only for traits that are actually supported by the language/runtime.
#pragma experiment("TRAITS")
import can_bridge_by_name
module MyModule:
"""
Requirements:
- R1: Supply voltage — 3.3V regulated
"""
# Bridge trait for series-path modules
trait can_bridge_by_name<input_name="power_in", output_name="power_out">
# Targeted trait on a specific field
trait some_field has_part_removed
Common traits:
| Trait | Purpose |
|---|---|
can_bridge_by_name | Enable ~> bridge connect through a module's named in/out fields |
has_part_removed | Suppress part picking for non-BOM placeholders |
has_single_electric_reference | Declare shared reference rail for an interface |
r.resistance = 10kohm +/- 5%
cap.capacitance = 100nF +/- 20%
ldo.v_out = 3.3V +/- 3%
These field names map to trait behavior:
r.package = "0402" # footprint/package constraint
led.lcsc_id = "C2286" # lock to specific LCSC part
ic.manufacturer = "Texas Instruments"
ic.mpn = "TLV75901PDDR"
i2c.required = True # mark interface as required
net.override_net_name = "VCC" # force net name
net.suggest_net_name = "SDA" # suggest net name
param.default = 0x20 # default value (overridable)
Package/module authors set defaults; integrators override with explicit constraints:
# In the package module:
i2c.address.default = 0x20
# In the integration module:
assert sensor.i2c.address within 0x21 to 0x21
Requires FOR_LOOP pragma. Used for repetitive constraint/wiring patterns, not logic branching.
#pragma experiment("FOR_LOOP")
# Iterate over a sequence
for cap in decoupling_caps:
cap.capacitance = 100nF +/- 20%
cap.package = "0402"
# Iterate with slice
for cap in decoupling_caps[1:]:
cap.package = "0603"
# Iterate over explicit list of references
for rail in [power_core, power_io, power_analog]:
assert rail.voltage within 3.3V +/- 10%
Restricted — NOT allowed in loop body:
import statementsnew assignments (create arrays outside the loop, constrain inside)pin / signal declarationstrait statementsfor loops# Good: create array outside, constrain inside
resistors = new Resistor[8]
for r in resistors:
r.resistance = 1kohm +/- 20%
r.package = "0402"
# Bad: creating instances inside loop
for r in resistors:
x = new Resistor # NOT ALLOWED
Feature gates for experimental constructs. Place at top of file, only include what you use.
#pragma experiment("BRIDGE_CONNECT") # enables ~> and <~
#pragma experiment("FOR_LOOP") # enables for loops
#pragma experiment("TRAITS") # enables trait statements
#pragma experiment("MODULE_TEMPLATING") # enables new Type<k=v>
Unknown experiment names are errors.
-> performs deferred specialization of an existing field to a more specific type:
module USB2_0TypeCVerticalConnector from USBTypeCConnector_driver:
connector -> VerticalUSBTypeCConnector_model
Use sparingly — only for clear specialization points like swapping a connector variant. Do not use as a general-purpose assignment.
Used inside component and interface blocks:
component MyChip:
pin VCC
pin GND
pin 1
pin "A0"
interface MyBus:
signal data
signal clock
In practice, you rarely write pin declarations by hand — they come from auto-generated part files.
ato is declarative. Do NOT generate or suggest:
if / elif / else / while / matchtry / except / finallyIf a user asks for conditional behavior, express it as constraints and module alternatives.
ElectricalUntyped electrical connection point. Use when you only need net continuity with no reference semantics.
ElectricPowerTwo-rail power interface.
| Field | Type | Description |
|---|---|---|
hv | Electrical | High-side rail |
lv | Electrical | Low-side rail |
voltage | parameter | Rail voltage |
max_current | parameter | Current capacity |
max_power | parameter | Power budget |
Legacy aliases vcc and gnd exist — prefer hv/lv.
power = new ElectricPower
assert power.voltage within 3.3V +/- 5%
ElectricSignalSingle signal line with explicit reference power rail. Use for analog or general signals.
| Field | Type | Description |
|---|---|---|
line | Electrical | Signal line |
reference | ElectricPower | Reference rail |
ElectricLogicLogic signal (line + reference). Use for GPIOs, digital control, interrupts, reset, enable.
| Field | Type | Description |
|---|---|---|
line | Electrical | Signal line |
reference | ElectricPower | Reference rail |
ElectricLogic vs ElectricSignal: Use ElectricLogic for digital. Use ElectricSignal for analog/general.
Important: Always connect the .reference when crossing module boundaries:
gpio.reference ~ power
i2c.sda.reference ~ power
Missing reference wiring is a common source of bugs.
I2CTwo-wire bus with address and frequency.
| Field | Type | Description |
|---|---|---|
scl | ElectricLogic | Clock |
sda | ElectricLogic | Data |
address | parameter | Device address |
frequency | parameter | Bus frequency |
i2c = new I2C
i2c.scl.reference ~ power
i2c.sda.reference ~ power
assert i2c.frequency within 100kHz to 400kHz
SPI3-wire data+clock bus (CS managed separately).
| Field | Type | Description |
|---|---|---|
sclk | ElectricLogic | Clock |
miso | ElectricLogic | Master In Slave Out |
mosi | ElectricLogic | Master Out Slave In |
frequency | parameter | Bus frequency |
MultiSPIMulti-lane SPI/QSPI style interface.
| Field | Type | Description |
|---|---|---|
clock | ElectricLogic | Clock |
chip_select | ElectricLogic | Chip select |
data[n] | ElectricLogic | Data lanes |
UART_Base and UARTUART_Base — minimal TX/RX. UART — full with flow control (RTS/CTS). UART includes base_uart sub-field.
I2SAudio serial bus.
| Field | Type | Description |
|---|---|---|
sd | ElectricLogic | Serial data |
ws | ElectricLogic | Word select |
sck | ElectricLogic | Serial clock |
sample_rate | parameter | Sample rate |
bit_depth | parameter | Bit depth |
DifferentialPairPaired differential signals with impedance parameter.
| Field | Type | Description |
|---|---|---|
p | ElectricLogic | Positive |
n | ElectricLogic | Negative |
impedance | parameter | Target impedance |
USB2_0_IF and USB2_0USB2_0_IF contains differential data pair (d) and bus power (buspower). USB2_0 wraps usb_if and is easier for top-level module interfaces.
SWD, JTAG, Ethernet, XtalIF — available for import, commonly used in package modules.
ResistorAuto-pickable passive. Key fields:
r = new Resistor
r.resistance = 10kohm +/- 5%
r.package = "0402"
Two unnamed pins accessed via bridge connect or r.unnamed[0] / r.unnamed[1].
Capacitorcap = new Capacitor
cap.capacitance = 100nF +/- 20%
cap.package = "0402"
Inductorind = new Inductor
ind.inductance = 10uH +/- 20%
ind.max_current = 1A
DiodeKey fields: anode, cathode, forward_voltage, max_current.
LEDExtends Diode behavior. Key fields: diode.forward_voltage, diode.max_current.
led = new LED
led.lcsc_id = "C2286" # lock to specific LED
FuseSeries protection element with bridge capability.
ResistorVoltageDividerPre-built voltage divider module with r_top, r_bottom, and ratio equations.
MountingHoleStandalone mounting hole for mechanical attachment.
TestPointTest point for debugging and measurement.
NetTieNet tie for connecting separate nets on the PCB.
Traits require #pragma experiment("TRAITS") and must be imported.
Document design requirements in the owning module's docstring.
module PowerSupply:
"""
Requirements:
- R1: Supply voltage — 3.3V regulated from USB
"""
can_bridge_by_nameEnable bridge connect (~>) through a module by naming its input and output fields.
import can_bridge_by_name
trait can_bridge_by_name<input_name="power_in", output_name="power_out">
has_part_removedSuppress part picking. Used for non-BOM placeholders or integration stubs.
trait some_field has_part_removed
has_single_electric_referenceDeclare that an interface shares a single reference rail. Used internally by stdlib interfaces.
These field assignments map to traits without needing explicit trait syntax:
x.package = "0402" — package/footprint requirementx.lcsc_id = "C12345" — lock to LCSC partx.manufacturer = "TI" — manufacturer constraintx.mpn = "TLV75901PDDR" — manufacturer part numberx.required = True — mark as externally requiredx.override_net_name = "VCC" — force net namex.suggest_net_name = "SDA" — suggest net namex.default = 0x20 — set default (overridable by downstream constraints)Expose external behavior as interfaces; keep internals private.
module SensorBoard:
power = new ElectricPower
i2c = new I2C
# Internal blocks
sensor = new SomeSensor
pullups = new Resistor[2]
power ~ sensor.power
i2c ~ sensor.i2c
Avoid exposing internal package pins as public API.
Model power as named ElectricPower rails and bridge modules between them.
#pragma experiment("BRIDGE_CONNECT")
module PowerTree:
vin = new ElectricPower
vout = new ElectricPower
fuse = new Fuse
ldo = new TI_TLV75901
vin ~> fuse ~> ldo ~> vout
Create one bus spine and branch to devices:
module App:
i2c_bus = new I2C
mcu = new MCU
expander = new NXP_PCF8574
sensor = new Sensirion_SCD41
i2c_bus ~ mcu.i2c
i2c_bus ~ expander.i2c
i2c_bus ~ sensor.i2c
Centralized bus constraints, easy multi-device address management.
Each powered IC/module should own its decoupling:
#pragma experiment("BRIDGE_CONNECT")
decoup = new Capacitor
decoup.capacitance = 100nF +/- 20%
decoup.package = "0402"
power.hv ~> decoup ~> power.lv
#pragma experiment("BRIDGE_CONNECT")
#pragma experiment("TRAITS")
import LED
import Resistor
import ElectricPower
import can_bridge_by_name
module LEDIndicator:
power = new ElectricPower
resistor = new Resistor
led = new LED
current = 0.5mA to 3mA
assert (power.voltage - led.diode.forward_voltage) / current is resistor.resistance
assert current <= led.diode.max_current
power.hv ~> led ~> resistor ~> power.lv
signal low ~ power.lv
signal high ~ power.hv
trait can_bridge_by_name<input_name="high", output_name="low">
#pragma experiment("BRIDGE_CONNECT")
import Resistor
import ElectricPower
import ElectricSignal
module VoltageDivider:
power = new ElectricPower
output = new ElectricSignal
r_bottom = new Resistor
r_top = new Resistor
v_in: V
v_out: V
max_current: A
r_total: ohm
ratio: dimensionless
power.hv ~> r_top ~> output.line ~> r_bottom ~> power.lv
assert v_out is output.reference.voltage
assert v_in is power.voltage
assert r_total is r_top.resistance + r_bottom.resistance
assert v_out is v_in * r_bottom.resistance / r_total
assert max_current is v_in / r_total
assert ratio is r_bottom.resistance / r_total
Multiple algebraically equivalent expressions improve solver propagation.
#pragma experiment("MODULE_TEMPLATING")
#pragma experiment("BRIDGE_CONNECT")
#pragma experiment("FOR_LOOP")
import Addressor
import I2C
import ElectricPower
import Capacitor
import Resistor
module TI_TCA9548A:
power = new ElectricPower
i2c = new I2C
i2cs = new I2C[8]
assert power.voltage within 1.65V to 5.5V
addressor = new Addressor<address_bits=3>
addressor.base = 0x70
assert addressor.address is i2c.address
decoupling_caps = new Capacitor[2]
decoupling_caps[0].capacitance = 100nF +/- 20%
decoupling_caps[1].capacitance = 2.2uF +/- 20%
for cap in decoupling_caps:
power.hv ~> cap ~> power.lv
#pragma experiment("TRAITS")
#pragma experiment("BRIDGE_CONNECT")
import ElectricPower
import I2C
import DifferentialPair
import Resistor
import can_bridge_by_name
module TI_INA228:
power = new ElectricPower
i2c = new I2C
power_in = new ElectricPower
power_out = new ElectricPower
shunt_input = new DifferentialPair
shunt = new Resistor
shunt_input.p.line ~> shunt ~> shunt_input.n.line
power_in.hv ~> shunt ~> power_out.hv
power_in.lv ~ power.lv
power_out.lv ~ power.lv
trait can_bridge_by_name<input_name="power_in", output_name="power_out">
#pragma experiment("FOR_LOOP")
#pragma experiment("BRIDGE_CONNECT")
import ElectricPower
from "atopile/usb-connectors/usb-connectors.ato" import USB2_0TypeCHorizontalConnector
from "atopile/ti-tlv75901/ti-tlv75901.ato" import TI_TLV75901
from "atopile/espressif-esp32-c3/espressif-esp32-c3-mini.ato" import ESP32_C3_MINI_1
module ESP32_MINIMAL:
micro = new ESP32_C3_MINI_1
usb_c = new USB2_0TypeCHorizontalConnector
ldo_3V3 = new TI_TLV75901
power_3v3 = new ElectricPower
usb_c.usb.usb_if.buspower ~> ldo_3V3 ~> power_3v3
power_3v3 ~ micro.power
ldo_3V3.v_in = 5V +/- 5%
ldo_3V3.v_out = 3.3V +/- 3%
usb_c.usb.usb_if ~ micro.usb_if
#pragma experiment("FOR_LOOP")
#pragma experiment("BRIDGE_CONNECT")
import Resistor
module Sub:
r_chain = new Resistor[3]
for r in r_chain:
r.resistance = 1kohm +/- 20%
r.package = "R0402"
r_chain[0] ~> r_chain[1] ~> r_chain[2]
module Top:
sub_chains = new Sub[3]
sub_chains[0].r_chain[2] ~> sub_chains[1].r_chain[0]
sub_chains[1].r_chain[2] ~> sub_chains[2].r_chain[0]
sub_chains[2].r_chain[2] ~> sub_chains[0].r_chain[0]
For daisy-chain protocols (addressable LEDs, pass-through interfaces):
#pragma experiment("TRAITS")
#pragma experiment("BRIDGE_CONNECT")
import ElectricLogic
import can_bridge_by_name
module DataChain:
data_in = new ElectricLogic
data_out = new ElectricLogic
trait can_bridge_by_name<input_name="data_in", output_name="data_out">
Enables clean a ~> chain ~> b syntax.
Add constraints from generic to specific:
# Layer 1: datasheet-safe envelope
assert power.voltage within 2.7V to 5.5V
# Layer 2: system target
assert power.voltage within 3.3V +/- 5%
# Layer 3: manufacturing/package
res.package = "0402"
#pragma experiment("BRIDGE_CONNECT")
#pragma experiment("FOR_LOOP")
import ElectricPower
import LED
import Resistor
module App:
power = new ElectricPower
# Auto-picked by constraints
current_limiting_resistors = new Resistor[2]
for resistor in current_limiting_resistors:
resistor.resistance = 10kohm +/- 20%
resistor.package = "R0402"
# Locked to specific parts
leds = new LED[2]
leds[0].lcsc_id = "C2286"
leds[1].manufacturer = "Hubei KENTO Elec"
leds[1].mpn = "KT-0603R"
power.hv ~> current_limiting_resistors[0] ~> leds[0] ~> power.lv
power.hv ~> current_limiting_resistors[1] ~> leds[1] ~> power.lv
Symptom: Error about experiment not enabled.
Fix: Add the required pragma at top of file.
| Construct | Required Pragma |
|---|---|
~> / <~ | BRIDGE_CONNECT |
for loop | FOR_LOOP |
trait statement | TRAITS |
new Type<k=v> | MODULE_TEMPLATING |
Symptom: DslImportError for import Foo.
Fix: If Foo is not stdlib-allowlisted, use a path import:
from "path/to/foo.ato" import Foo
newSymptom: Assignment error.
Fix: r = Resistor → r = new Resistor
Symptom: Semantic error on assert with multiple comparisons.
Fix: Split into separate asserts:
# Bad
assert 1V < x < 5V
# Good
assert x > 1V
assert x < 5V
Symptom: UnitNotFoundError.
Fix: Use known symbols: V, A, ohm, F, Hz, W, H, mm. Check SI prefix spelling.
Symptom: Unit mismatch error in range/tolerance.
Fix: Ensure both sides have compatible dimensions:
# Bad
assert x within 3.3V to 5A
# Good
assert x within 3.3V to 5V
Symptom: Direction error in connect statement.
Fix: Use one direction per chain. a ~> b <~ c is invalid — rewrite as separate statements.
Symptom: Invalid statement in for-loop body.
Fix: Move import, new, pin, signal, trait, or nested for outside the loop. Create arrays before the loop, constrain inside.
Symptom: Solver reports contradictory constraints.
Fix:
.default vs explicit assignment interactions.Symptom: Unstable or poor part selection.
Fix:
package = "0402").lcsc_id or mpn.Symptom: Unexpected interface behavior or unresolved voltage domains.
Fix: Wire .reference to the correct power rail:
i2c.scl.reference ~ power
i2c.sda.reference ~ power
gpio.reference ~ power
When a module has multiple errors, fix in this order:
new)~ vs ~>)