Skip to main content

A Python framework for modelling real electronic circuits with physical fidelity.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wirebench-0.1.0.tar.gz (197.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wirebench-0.1.0-py3-none-any.whl (359.4 kB view details)

Uploaded Python 3

File details

Details for the file wirebench-0.1.0.tar.gz.

File metadata

  • Download URL: wirebench-0.1.0.tar.gz
  • Upload date:
  • Size: 197.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wirebench-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9faf30439f1748cd3ca52b895dcb146b3569a3d13ae2bb78531c2460c9358b82
MD5 99d7523ce08d72cdb7a3344dda98971f
BLAKE2b-256 0c1243c3cfb39b1cd4d1f5c80062f74a731e863b8c46ea840a0c584b48d60e0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for wirebench-0.1.0.tar.gz:

Publisher: release.yml on raeq/wirebench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file wirebench-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: wirebench-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 359.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for wirebench-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8bc4c9d2d43a1651722e172762237c00fe2884d0458ca45287527d9fc5418542
MD5 136eca63ecb103f9e460a3564227551e
BLAKE2b-256 cdeb1d4e4fffc2d7d7a7e78b662ac1442524c93ff9477bdfd89637fe9a96cd1a

See more details on using hashes here.

Provenance

The following attestation bundles were made for wirebench-0.1.0-py3-none-any.whl:

Publisher: release.yml on raeq/wirebench

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page