Skip to main content

wirebench

Describe real electronic circuits in Python. The framework won't let a design that wouldn't physically work compile — so when your tests are green, the breadboard build matches.

KiCad's ERC catches defective wiring after you've drawn the schematic. wirebench prevents you from constructing the wrong design in the first place — no burnt CMOS outputs from two drivers fighting on one net, no half-evenings tracing a regulator that never had a ground wired, no second order at Mouser because the cable housings don't fit the header you placed. Wire two chip outputs together and wire() raises. Leave a regulator's input floating and the Circuit subclass refuses to instantiate. Try to mate a male pin header to a JST plug and mate() says no. The defects ERC would flag after you've drawn the schematic are flagged before the Python design even imports cleanly.

If you've programmed an Arduino but felt like wiring the surrounding circuit is a black art — every project a hunt for the magic combination of pull-ups, current-limiting resistors, decoupling caps, and "did I just wire the LED backwards?" — wirebench is where every line of code maps to a physical operation. You write Python that says "a 330 Ω resistor in series with a red LED between VCC and GND." The wires in the code are the wires you'll need on the breadboard, and the design either constructs cleanly or it doesn't — there's no middle state where pytest passes but the bench build smokes.

Hello, world

from wirebench import Resistor, LED, Rail, Circuit, wire, export

class HelloLED(Circuit):
    def __init__(self) -> None:
        self.vcc = Rail(True)                       # 5 V supply rail
        self.gnd = Rail(False)                      # 0 V ground
        self.r1  = Resistor(330, refdes_number=1)   # R1: 330 Ω current limiter
        self.d1  = LED('red', refdes_number=1)      # D1: red LED

        wire(self.vcc.out, self.r1.t1)              # VCC → R1 pin 1
        wire(self.r1.t2,   self.d1.anode)           #         R1 pin 2 → LED anode
        wire(self.d1.cathode, self.gnd.out)         #                    LED cathode → GND

        super().__init__()

design = HelloLED()
export(design, 'bom',     'hello.bom.csv')
export(design, 'kicad',   'hello.net')
export(design, 'spice',   'hello.cir')
export(design, 'mermaid', 'hello.mmd')

This is a buildable circuit. The wire-list in the Python file maps one-for-one to the jumpers on your breadboard. Drop the BOM into Mouser or Digikey, order two parts, and you have everything you need.

Components are stored as self.<name> attributes so the framework can auto-collect them — super().__init__() walks self.__dict__ and picks up every Resistor, LED, Chip, and Rail you've placed. The two-stage pattern (declare parts → wire them → finalise with super().__init__()) reads like a bench-style buildout: take parts off the shelf, wire them together, close the assembly.

What comes out:

Format Extension What it's for
BOM CSV .bom.csv Paste into a parts cart at Mouser / Digikey / Tayda
KiCad netlist .net Import into KiCad's Pcbnew to lay out a PCB
SPICE deck .cir Simulate in ngspice or LTspice before building
Mermaid .mmd Embed in a README to document what you built
Graphviz DOT .dot Render to SVG / PNG with dot -Tsvg
Yosys JSON .yosys.json Render to a browser-friendly schematic via netlistsvg
Assembly Guide .md Read at the bench — recipe-style breadboard build instructions

Every demo in demos/ ships with all seven exports pre-generated in its docs/ subfolder — open any *.svg to see the rendered schematic, or any *.md to read the bench-assembly guide.

What it prevents

Three categories of real-world harm wirebench rules out before the design ever reaches a breadboard — each caught in milliseconds, each with the kind of error message that names the offending part and tells you what to fix.

Burnt outputs

u1 = SN74HC04(refdes_number=1)
wire(u1.y_1, u1.y_2)
# ShortCircuitError: wire() has multiple drivers ('y_1', 'y_2') — short circuit

Two CMOS outputs fighting on the same net will burn out one or both. You don't need to know which — wirebench knows, and wire() raises before the call returns. The chip never gets the chance to suffer the experiment.

A wasted parts order

mate(Header2xNMale(...), JSTPHCableHousing(...))
# IncompatibleMateError: Header2xNMale mates with Header2xNFemale, not JSTPHCableHousing

Pitch, pin count, and gender all checked at mate() time. You don't put through a JST cable order, wait five days, and discover the housings don't fit the header on your board — the wrong combination never gets past the Python.

Hours of fault-tracing

class CrossedRails(Circuit):
    def __init__(self):
        self.vcc1 = Rail(True)
        self.vcc2 = Rail(True)
        self.r1   = Resistor(330, refdes_number=1)
        wire(self.vcc1.out, self.r1.t1)
        wire(self.vcc2.out, self.r1.t1)   # legal in isolation
        super().__init__()
# ShortCircuitError: Short circuit on logical net — multiple drivers: 'Rail.out', 'Rail.out'

Each wire() call is fine in isolation — the contention only emerges when the framework walks the combined logical net at super().__init__(). This is the same algorithm KiCad's ERC runs, except KiCad waits for you to click Run ERC and wirebench waits no longer than __init__ returning. Without that walk, you'd find the conflict the slow way: build the design, watch the supplies fight, scope every node looking for the one that doesn't sit where it should.


The same logic prevents other classes of harm hobbyists don't always know to look for: forbidden runtime states (S=1, R=1 on an SR latch) that would lock a latch into undefined behaviour, ground-domain crossings without an isolator that would defeat the point of having isolation, chips with declared output pins that nothing internal drives — every quiet failure mode where the circuit looks right and doesn't work. Every error message names the offending part by refdes and pin number, so you know what to fix before you reach for a soldering iron.

When pytest is green, the topology is sound.

A real design

demos/water_alarm/ is the simplest end-to-end example — four chips, two LEDs, a pair of probes mounted in a tank. The source is water_alarm.py; open docs/WaterAlarm.svg for the rendered schematic, or docs/WaterAlarm.bom.csv for the parts list. The same folder shape applies to every demo. See docs/learning-path.md for the suggested order to work through them.

What it doesn't do

  • Solve Ohm's law. Logic-level only. For continuous-voltage simulation, export to SPICE and let ngspice handle it.
  • Catch parameter mistakes. The framework prevents defective topology — shorts, floating nets, mismatched connectors, forbidden states, cross-domain wiring — but it won't save you from a 330 Ω current limiter sized for a high-current LED, a 25 V capacitor on a 30 V rail, or a regulator dissipating 4 W with no heatsink. Use SPICE, arithmetic, or the per-component GOTCHAS strings for those.
  • Simulate firmware. Model firmware as a private cell inside a chip subclass (see demos/digital_thermometer/); the cell is your code.
  • Lay out a PCB. KiCad netlist export gets you to the start of layout; KiCad does the rest.

See docs/design-principles.md for why the framework is shaped the way it is.

Install

Requires Python 3.10+:

uv venv && uv pip install -e ".[dev]"
# or
python -m venv .venv && source .venv/bin/activate && pip install -e ".[dev]"

Run tests

pytest

Full coverage of the framework, components, every export format with byte-deterministic golden files, end-to-end round-trips, and property-based stress on the load-bearing logic.

Going further

  • demos/ — every demo is a complete study artifact (source + all six exports + rendered schematic).
  • docs/learning-path.md — suggested order for working through the demos.
  • docs/design-principles.md — why the framework prevents what it prevents.
  • docs/component-library-data.md — catalogue of all 122 modelled components with datasheet links, pin maps, and footprints.
  • docs/ — implementation specs for every major work package.
  • CLAUDE.md — design philosophy in full, for contributors.

Licensing

Released under the PolyForm Noncommercial License 1.0.0 — free for non-commercial use, including personal study, hobby projects, academic research, and use by educational, charitable, or public institutions.

Commercial use requires a separate paid license. Contact the project maintainer for terms.

Metadata

Release files for wirebench 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wirebench 0.1.0
File Size Uploaded Provenance
wirebench-0.1.0.tar.gz 197.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for wirebench 0.1.0
File Interpreter ABI Platform
wirebench-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 556.4 kB

Release files / wirebench-0.1.0.tar.gz

Download URL wirebench-0.1.0.tar.gz
Size 197.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9faf30439f1748cd3ca52b895dcb146b3569a3d13ae2bb78531c2460c9358b82
BLAKE2b-256 checksum
How to use checksums
0c1243c3cfb39b1cd4d1f5c80062f74a731e863b8c46ea840a0c584b48d60e0a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

Release files / wirebench-0.1.0-py3-none-any.whl

Download URL wirebench-0.1.0-py3-none-any.whl
Size 359.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8bc4c9d2d43a1651722e172762237c00fe2884d0458ca45287527d9fc5418542
BLAKE2b-256 checksum
How to use checksums
cdeb1d4e4fffc2d7d7a7e78b662ac1442524c93ff9477bdfd89637fe9a96cd1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

Security for wirebench 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for wirebench-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 13, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page