← Blog

2 October 2026

Why PCB design needs a new language

I tried schematic tools and code-first tools. Both kept losing the thing I cared about most, and that is why Circutri is a language of its own.

Circutri founder

Every board I have designed started the same way: a regulator, a few capacitors, a connector, a microcontroller. And every time, I drew them again.

Not from nothing. I copied them. I opened an older project, selected the power section, pasted it into the new schematic and adjusted a value or two. It felt like reuse. It wasn't. The moment I pasted it, there were two regulators in the world that had nothing to do with each other. When I found a better output capacitor for one, the other never heard about it. When a colleague asked which of my boards had the fix, the honest answer was "let me open them and look".

That is the whole problem, really. Hardware has modules. It has interfaces. It has parts that carry real limits. But the tools I used treated a circuit as a drawing, and a drawing does not know what it is.

I come to this as a roboticist. A robot only works when everything around it holds together: software that is versioned and reviewed line by line, tests that run on every change, harnesses that tie the pieces together, verification and validation that prove each part does its job before it meets the rest. That discipline is how a pile of hardware becomes something that solves a real problem. The boards were the one part of the robot I could never treat that way.

Drawings that cannot be reviewed

I learned on KiCad and spent a good while in EasyEDA. Both are capable, and KiCad in particular is a remarkable piece of work. I still use its symbol and footprint libraries. But the longer I worked in them, the more I felt the same three frictions.

Reuse is a copy. Hierarchical sheets and design blocks exist, and they help. But a sheet is a file of shapes, not a definition with parameters. I could not say "the same regulator, but 5V in and 3.3V out" and have the tool understand what changed.

Review is a screenshot. My designs lived in Git, like my code. But a diff of a schematic file is coordinates and identifiers. Nobody can read it in a pull request. So reviews happened in meetings, over a shared screen, from memory.

The checks came late. Electrical rule checks caught unconnected pins. They did not tell me that three LEDs and a probe would pull a small regulator past what it can deliver. I found that kind of thing on the bench.

Then I tried code

So I did what many engineers have done in the last few years: I moved to code-first tools. There are good ones now, built on Python or TypeScript, where a circuit is a program and a module is a function you can call twice.

For a while it felt like the answer. Reuse was real at last. A diff was readable. I could keep my power section in one place and import it.

But I kept running into the same wall from the other side. A general-purpose language is general-purpose. It does not know what a volt is. It will happily add a voltage to a current, because to the language both are numbers. It will accept a property I misspelled and quietly ignore it, because objects in those languages are forgiving by design. And because the design was a program, it could do anything a program can do: read a file, fetch something, depend on the order things ran in. Two builds of the same commit were not guaranteed to agree.

And reuse turned out to be only half of what I had lost. In a schematic tool I could at least see the circuit; in code, I was reading it back out of function calls and keeping the picture in my head. The notes that matter in hardware, why this capacitor is here, which datasheet page set that limit, ended up in comments scattered across files, attached to nothing the tool understood. And getting from the code to something a manufacturer could actually assemble meant a chain of exports and scripts that broke a little differently every time.

The worst moments were the quiet ones. A build that finished cleanly and produced a board with something missing. Nothing failed. Nothing warned. The tool had done exactly what the code said, and the code had not said what I meant.

That is when it clicked for me. The problem was not the syntax. It was that the language underneath had no idea it was describing hardware.

What a hardware language has to know

If I wrote down what I actually wanted from a design language, the list was short, and none of it was about style:

  • Units are types. 3.3V is a voltage and 15mA is a current. Dividing one by the other gives a resistance, exactly, and adding them is an error before anything is drawn.
  • Parts are data, not strings. A part comes from a registry that states its pins, its voltage ranges, its current limits and the datasheet page each came from, and it is verified before any design can use it.
  • Connections say which way power goes. usb.vbus ~> regulator.power_in is not just a wire; it tells the compiler where the energy comes from, so it can follow it to every load.
  • No arbitrary code. A design is declarative. Loops and arithmetic run at compile time and produce one finite board, the same on every machine, every time.
  • Errors have names. Every problem has a stable code, so I can search it, link it and know it will mean the same thing next year.
  • It says what it did not check. A pass is only worth something if the tool is honest about its edges.

I looked hard at whether this could be a library inside an existing language. It can't, not fully. The moment the host language can run arbitrary code, determinism is a convention rather than a guarantee. The moment objects accept unknown fields, a typo is a silent bug. You can paper over it with linters and conventions, but the floor underneath stays soft.

So Circutri is a language of its own: .circuit, a programming language for hardware. It is small on purpose. It reads like an outline, with indentation for structure and braces only for dense data. Here is the power path of a real sensor board in it:

1module MainBoard:2    let usb        = UsbPower()3    let power_tree = PowerRegulation()4    let controller = STM32F411()5 6    usb.vbus             ~> power_tree.power_in7    power_tree.power_out ~> controller.vcc

PowerRegulation is defined once. Every board that needs a 3.3V rail calls it, and when I improve it, every board improves with it. That is the reuse I wanted from the start.

Not silently, though. If one change can reach every board, I want to hear about it first, not find out later. The parts a board uses already stay fixed until I agree to a change. Being told which boards a change will touch, before they are rebuilt, is next.

The moment it earns its keep

The check that convinced me was not a clever one. On that same board I raised the debug probe's load from 1mA to 500mA, the kind of change that slips in when someone is moving fast. A drawing would have accepted it. A general-purpose program would have built it. The compiler did this:

~/iot-sensor-node — zsh

It named the rail, the regulator, the derating it applied and every load on the rail, in order of draw. It exited with an error and wrote nothing, so no half-valid output could be mistaken for a board. That is output a person can act on in seconds, and, just as important, output an agent can act on too.

This is the part the code-first tools could never quite give me. There, the design was a script: it ran, and I learned what it did by running it. A mistake surfaced only if someone had thought to check for it, and often only after the board files already existed. Here the whole design is read and checked before anything is produced. Every rail, every part, every time, whether or not anyone thought to ask.

Why agents change the equation

I should be honest about the other reason this matters now. More and more of my code is drafted by an agent, and I review it. Hardware is heading the same way, and a GUI is the worst possible interface for that: an agent cannot reliably drag symbols around a canvas, and I cannot review what it dragged.

Text is the interface agents are good at. But text alone is not enough, because an agent will confidently write something plausible and wrong. What makes the pairing safe is the compiler in the middle, and the fact that it is not a single pass but a loop.

The agent drafts. The compiler checks every line against real part data and says exactly what does not hold, by name. That answer goes straight back to the agent, which fixes what it was told and tries again. Each round, a little more of the board is settled and a little less is guesswork. It is the whole job, really: “turning the unknown into the known”, to borrow a line from Wistoria: Wand and Sword. By the time the design reaches me, I am reviewing decisions, not hunting for mistakes. I change what I disagree with, and nothing moves on until I accept it.

That loop only works if the language is strict. A forgiving language turns an agent's mistake into a silent bug, and the loop has nothing to learn from. A strict one turns it into a named error the agent can act on, round after round, before I ever see it.

What it costs

A new language is a real ask. It is one more thing to learn, and a young ecosystem has fewer parts and fewer examples than tools that have been around for decades. I do not want to pretend otherwise.

What I can do is keep the cost small. The language is deliberately compact, and the documentation starts from a real board rather than a grammar. Parts can be drafted from KiCad's symbol and footprint libraries instead of being drawn from scratch. And one language server will bring the same checks into whatever editor you already use.

Where this goes

In a way, Circutri is the test setup I always wanted for my boards: every change checked, every result named, and an honest list of what has not been tested yet. Today it captures and checks a design: the language, the compiler and a verified parts registry. Notes are part of the language: a /// comment belongs to the definition it describes, not to a line in a file. The editor and a schematic view drawn from the code come next, and the board, with what a manufacturer needs to assemble it, after that. The text stays the source of truth through all of it, because that is the only way I know to keep a design reviewable as it grows.

I built this because I was tired of redrawing the same regulator and finding my mistakes on the bench. If that sounds familiar, I would like to hear how you work today, and what you would want a hardware language to refuse.

— Circutri founder