Skip to content

Instantly share code, notes, and snippets.

@Foadsf
Created June 9, 2026 09:12
Show Gist options
  • Select an option

  • Save Foadsf/02924c73be2efedf7174c4a0b39b1803 to your computer and use it in GitHub Desktop.

Select an option

Save Foadsf/02924c73be2efedf7174c4a0b39b1803 to your computer and use it in GitHub Desktop.
Exploring "Schematics-as-Code": Setting up `atopile` (v0.15+) on Linux Mint, surviving the configuration traps, and building a bare-metal Minimum Working Example.

Hardware as Code: The Quest for the "OpenSCAD of Electronics"

For workflows built around text-based engineering documentation, CLI environments, and agentic code generation, standard graphical EDA tools (like KiCad or Altium) can feel disjointed. I went looking for the electronics equivalent of OpenSCAD—a way to define, version-control, and compile hardware parametrically through code.

After evaluating frameworks like SKiDL (Python procedural generation) and tscircuit (React/TS), I settled on atopile. It uses a custom, purely declarative language (.ato) that enforces design rules natively and compiles logical netlists directly into physical KiCad .kicad_pcb layouts.

However, atopile is evolving rapidly. Version 0.15.x introduced massive breaking changes from 0.14.x. Here are the lessons learned, the traps to avoid, and the ultimate bare-metal Minimum Working Example (MWE) for a modern Linux Mint environment.


1. Installation: The Modern uv Stack

Do not use pip or pipx for this. The modern, fastest, and cleanest route on Debian-based systems (like Linux Mint or Ubuntu) is to use uv by Astral to ensure strict environment isolation, preventing conflicts with the desktop's system Python.

# 1. System prerequisites
sudo apt update
sudo apt install -y curl git python3 python3-venv kicad

# 2. Bootstrap uv
curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh
source ~/.bashrc

# 3. Install the atopile toolchain
uv tool install atopile

# 4. Verify
ato --version

2. Configuration Traps & The "KiCad Plugin" Ghost

If you run ato build on a fresh system, you will likely see this warning: Couldn't install plugin: Could not find KiCad plugin path. If KiCad is installed at a custom location, you may need to add the search path to your config.yaml file.

⚠️ DANGER: Do NOT follow the warning's advice to create a config.yaml file.

In v0.15+, adding kicad_plugin_path to a global config file triggers a fatal Pydantic ValidationError, completely crashing the compiler. Furthermore, the ato configure command has been removed from the CLI.

The Canonical Fix: The compiler utilizes an automatic pre-build hook that scans for standard KiCad plugin directories. If you just installed KiCad via apt, the user-level directory doesn't exist yet. Just create the directory manually:

mkdir -p ~/.local/share/kicad/7.0/scripting/plugins

On your next ato build, the hook will find this empty folder, silently drop the bidirectional Python sync plugin inside, and the warning will permanently vanish.


3. Lessons Learned: The DSL & Syntax Shifts

The .ato language is strict. Here is what trips up first-time users in v0.15+:

  • ato-version is dead: In your ato.yaml configuration file, ato-version is deprecated. You must use requires-atopile: "^0.15.0".
  • Modules vs. Components: A module is a logical container for grouping (like a sub-circuit). A component is a physical, footprint-bearing entity that possesses copper pins. If you try to assign a footprint to a module, the constraint solver will fail.
  • The Standard Library Black Box: V0.15.x moved the standard library (generics) into the compiler core. While import Resistor works natively now, the abstracted pin mapping ([0], [1], p1, etc.) is incredibly opaque and experimental flow syntaxes (a ~ b ~ c) will often throw ANTLR grammar errors.
  • Reference Designators are Mandatory: If you define a custom component but forget designator_prefix = "R", the compiler will build the logic but refuse to generate physical footprints (ATTENTION: No pickers and no footprint...), because KiCad requires standard designators (R1, R2).

4. The Bare-Metal Minimum Working Example (MWE)

To truly understand the compiler without fighting the built-in generics black-box, the best approach is to define a physical component from scratch. This guarantees the constraint solver knows exactly what to do.

Step 1: Scaffold the Project

mkdir ato-mwe && cd ato-mwe
touch ato.yaml main.ato

Step 2: ato.yaml

requires-atopile: "^0.15.0"
builds:
  default:
    entry: main.ato:VoltageDivider

Step 3: main.ato

# 1. Define the physical component from scratch
component CustomResistor:
    # Tell KiCad this is a physical resistor (Assigns R1, R2)
    designator_prefix = "R"
    footprint = "Resistor_SMD:R_0402_1005Metric"
    
    # Map the logical signals to physical KiCad pads
    signal p1
    signal p2
    p1 ~ pin 1
    p2 ~ pin 2

# 2. Build the circuit topology
module VoltageDivider:
    # Define the system boundaries
    signal v_in
    signal v_out
    signal gnd

    # Instantiate the custom components
    r_top = new CustomResistor
    r_bottom = new CustomResistor

    # 3. Wire the circuit (Strictly one connection per line)
    v_in ~ r_top.p1
    r_top.p2 ~ v_out
    v_out ~ r_bottom.p1
    r_bottom.p2 ~ gnd

Step 4: Compile

ato build

Result: A pristine Build successful! 🚀 with zero footprint warnings. The compiler assigns R1 and R2, grabs the 0402 footprints, nets them together via the ~ flow lines, and outputs a ready-to-route .kicad_pcb file in the build/default/ directory.

Next Steps

With the toolchain completely isolated, stabilized, and running natively in the terminal, the environment is now perfectly primed for autonomous AI agent workflows. An agent (like Claude Code) can now be pointed at this directory to "vibe code" entire analog front-ends simply by writing text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment