The software analogy

A map from circuit concepts to software ones, for engineers coming from code.

A design is a program. The symbol library is its imports. The BOM is its lockfile. Datasheets are vendor documentation, and the parameter layer turns those docs into type definitions a linter can check. This page expands that mapping one concept at a time, with what each thing means in an actual circuit and where it lives in the schemas. The architecture pages carry the design rationale. This page is for orientation.

It maps STRUCTURE and deliberately says nothing about engineering judgement: it will tell you a Net is a shared channel, not why an engineer put a resistor on one. For that, learn the domain is the companion section.

The master table:

Hardware / the schema Software analogy
PartLibrary a package/module you import
PartType a class declaration: members (pins) and their types (directions)
Component an instance whose ref des is the variable name
ComponentSection partial views of one instance
Net a shared channel aliasing fields of many instances
Net solving name resolution + linking
Protection walk (Reach) graph reachability across middleware that splits a channel
Reused hierarchical sheet a module instantiated N times, with name mangling
MPN an exact pinned artifact (lodash@4.17.21)
BomLine the lockfile
Datasheet vendor prose documentation for a closed-source dependency
PartSpec the hand- or machine-written .d.ts type stub for that dependency
LimitKind UB boundary / SLA envelope / benchmark numbers
The validation join type-checking call sites against dependency stubs via the lockfile
doc-IR the parsed AST of the vendor docs
derive, recipes, patches, manifests the codegen tool, its config, pinned overrides, and its lockfile
Geometry sidecar source maps
Provenance blame / debug symbols

Modules and classes: PartLibrary and PartType

Software. import Device brings in a package. Device.R is a class it declares, with two members (pins 1 and 2), each typed passive. The class says nothing about any particular resistor in your program, and nothing about which physical artifact will eventually satisfy it.

Circuit. The schematic’s symbol library. A KiCad lib_symbols entry or an EDIF cell holds the drawn body, the pin list, each pin’s electrical type (input, output, power_in…), and the designator prefix (“R” for resistors). Every resistor you place comes from this one definition.

Schema. ir.PartLibrary holding ir.PartTypes. Pins are ir.Pin with an ir.PinDirection. Readers fill these from the design file itself (v6+ KiCad files embed their libraries, like vendoring your dependencies).

class and instance

Instances: Component

Software. r1 := Device.R(value: "10k"). The variable name is the reference designator. Constructor arguments and fields are the instance attributes (Value, MPN/Manufacturer). Twenty resistors are twenty instances of one class.

Circuit. A placed part: R1 near the connector, R2 in the feedback path. Identity is the ref des, not the position. The same R1 exists in the schematic, the layout, and the BOM.

Schema. ir.Component with RefDes, Attributes, and Sections referencing the PartType by name. The checks quantify over Components, the way an analyzer walks call sites, not declarations.

Partial views: ComponentSection

Software. One object whose interface is used at several distinct sites: think of destructuring a struct’s fields across two files, or a partial class. There is still exactly one identity.

Circuit. A dual op-amp: one physical TL072 drawn as two triangles, U1A on this half of the sheet and U1B on that one. One package on the board, one BOM line, two drawn units.

Schema. One ir.Component (“U1”) with two ComponentSections (unit indexes 0 and 1). A repeated unit index is a genuine bug and trips the duplicate-ref-des diagnostics. Distinct units never do.

multi-unit

Aliasing: Net

Software. Not a function call. A net is a shared channel, or many variables aliasing one memory cell. Everything attached to “+5V” IS the same electrical node. There is no caller and no callee, no direction on the edge itself.

Circuit. The +5V rail, where the regulator’s output pin, the MCU’s VDD pin and a decoupling cap are all tied together. Directionality lives on the pins. The regulator’s pin is power_out, the MCU’s is power_in. Those are the type annotations the connectivity rules dispatch on, and a missing direction means skip rather than guess.

Schema. ir.Net with Connections (component ref + pin ref). Pin directions come from the PartType.

net aliasing

Name resolution: net solving

Software. Compilation’s front half. The source (wires, labels, junctions, geometry) contains only implicit references, and the solver builds the symbol table of which tokens denote the same thing. Two labels “+5V” on different wires are two mentions of one symbol. The solver unifies them, exactly like a linker unifying external symbols by name.

Circuit. KiCad stores no netlist. Connectivity is the drawing. A wire endpoint on a pin’s connect point binds. A label names the node. Same-named power symbols merge across the sheet. Getting these binding rules right is a language-semantics problem, so they are pinned against the reference implementation (kicad-cli), the way a compiler pins against a conformance suite. The full binding rules are in Net solving and hierarchy.

The protection walk: Reach

Software. Some questions are not about one node but about a path. Is there an auth middleware anywhere between the public handler and the database call? Neither endpoint can answer that. You walk the call graph between them. Reach is that walk. A two-terminal series part (a resistor, inductor, ferrite bead, or fuse) is inline middleware. It splits one logical channel into two named nets, so a per-net rule is blind across it.

Reach(start, hops) is a bounded BFS over the pass-element adjacency, and the helpers read the result like a stack trace. PathTo is the path, ThroughOnPath is the middleware crossed in order, and Between(from, to, class, hops) is the one-line “does any X sit on the path” query.

Why a series capacitor is a non-edge and a rail is a stop

Two edges of the model carry the electrical meaning. A series capacitor is a DC block, an insulator between two plates, so it is a non-edge and the walk never crosses it (a decoupling cap to ground is a different role entirely).

A rail is a global singleton. Ground, the design-wide global fact, or any net with bus-scale fan-out (more than 16 pins) is a stop, because following a pull-up onto VCC would make the whole design reachable. That is the graph equivalent of chasing an import into a global and treating everything it touches as local.

Circuit. Protection and presence rules are reachability questions. A fuse sits somewhere between the connector and the regulator. An ESD clamp hangs off a net on the power-entry path. The series element that splits the net is exactly what a per-net check cannot see past, which is why the walk exists.

Schema. check.Model.Reach/Between over the netlist IR. The crossable classes are resistor, inductor, ferrite, and fuse. The stops are ground, global, and high fan-out.

the protection walk

Templates: the reused sheet

Software. Real templates (C++ or generics) specialize at compile time, and each instantiation is a new type. The hardware analog is not the parameterized part. A Device:R with Value: 10k is just a constructor argument, and no specialization happens. The true template is the reused hierarchical sheet. One amp.kicad_sch source instantiated twice produces two complete copies of everything inside, with per-instance qualified names (/amp1/IN, /amp2/IN). That is name mangling, letter for letter.

Circuit. A stereo preamp drawn once and instantiated per channel. A motor driver repeated four times. Each instance has its own components (the walk resolves per-instance reference designators) and its own local nets.

Schema. ir.Sheet references plus the multi-sheet hierarchy walk. Qualified net names follow KiCad’s own convention so they match board-file names.

hierarchy template

The lockfile: MPN and BomLine

Software. Your code says import leftpad. The lockfile says leftpad@1.3.0, sha512-.... The MPN is that exact pinned artifact. “BSS138” names one orderable product with one datasheet, not “some N-FET”. BomLine (or the MPN attribute on a component) is the lockfile entry binding your variable to it.

Circuit. The BOM says R1 will be built as Yageo RC0603FR-0710KL. Two designs can place identical schematics and ship different physical parts, and only the BOM knows. This is also the moment of real specialization (see Templates). Choosing the MPN is link-time binding of the abstract symbol to a concrete implementation.

Schema. ir.BomLine{ref_des, mpn, manufacturer}. The KiCad reader carries MPN and Manufacturer symbol properties into component attributes as the no-BOM fallback. The join is case-insensitive on MPN and nothing fuzzier. A near-miss MPN is a different part until a human says otherwise.

Vendor docs as type stubs: PartSpec

Software. The dependency is closed-source (you will never see the die), and the vendor publishes prose documentation. A PartSpec is the .d.ts stub someone wrote for it. It carries machine-readable claims about the artifact’s limits and behavior, written against one pinned doc revision (SourceDoc), with every claim linking back to the prose it came from (page, table, extraction method, confidence). Like DefinitelyTyped, stubs start hand-written (the fixtures) and graduate to generated (derive). A stub no one has verified is not trusted, because it can look authoritative without being so.

Circuit. “Absolute-maximum VDD is 4.6 V (page 3, Absolute Maximum Ratings, TA = 25 °C).” A parameter is never a bare scalar. It is a min/typ/max range valid under stated test conditions, at a stated limit kind.

Schema. param.PartSpec / Parameter / Condition / ParamProvenance (see the datasheet layer). The honesty predicates are part of the contract. UnderSpecified means the conditions are not trustworthy, so skip. MachineComparable means a text-only condition should go to a human rather than an automatic comparison.

Limits as contract tiers: LimitKind

Software.

  • Absolute-max is the undefined-behavior boundary. Past it, the vendor promises nothing. Like indexing past the end of an array, damage may be immediate or latent.
  • Recommended-operating is the supported envelope. The SLA. Inside it, the product behaves as documented.
  • Characteristic is published benchmark numbers. Measured behavior under a stated config, and like any benchmark, the number is meaningless without the config (the test conditions).

Circuit. Take the LM1117. Operate VIN up to 15 V (recommended), never exceed 20 V (absolute max), expect ~1.2 V dropout at 800 mA and 25 °C (characteristic).

limit kinds

The type checker: the validation join

Software. With stubs (PartSpecs), a lockfile (BOM/MPN), and call sites (Components), checking becomes linting. Resolve each call site through the lockfile to its stub and verify usage against the declared types. If a dependency has no stub, the check is skipped rather than silently passed. A missing stub means the usage is unchecked, not that it is correct.

Circuit. Consider supply-exceeds-abs-max. A power-input pin on a rail whose name says “+5V”, joined to a part whose stub says absolute-max supply is 4.6 V, is a finding that cites both ends, the schematic location and the datasheet page.

Schema. The check Model’s params tier (check.NewModelWithParams, Model.PartSpec), the supply-symbol alias map (vendor spellings live in the model layer, never in rule text), and the rule itself. An empty param.ParamSet yields no findings by construction, so a missing tier is silent rather than a false pass.

the join

Codegen: doc-IR and derive

Software. Generating stubs from vendor docs is a compiler pipeline:

  • doc-IR is the parsed AST of the documentation, tables, cells, figures, text, with positions (see the datasheet layer). N parsers produce it, and nothing downstream re-reads the PDF.
  • derive is the generator: deterministic, versioned, reproducible (see the datasheet layer).
  • recipes are the generator’s per-vendor config (“in TI sheets, this heading means absolute-max”), data in git, reviewed like code.
  • patches are pinned human overrides that survive regeneration, the fix you commit so the generator’s known mistake on one exact input can never come back.
  • the RunManifest is the generator’s lockfile plus its warnings. Inputs are pinned, and every gap (what it saw and did not extract) is enumerated, so a gap is recorded rather than passing as coverage.

Circuit. A real run. Docling parsed the BSS138 sheet and derive emitted 30 parameters with page citations. On the LM1117 sheet the parser mis-placed one value into the wrong column, and a two-patch pair (clear plus insert) corrects it permanently.

derivation pipeline

Source maps and blame: geometry and provenance

Software. The geometry sidecar is a source map. It maps the same program to where things are drawn, kept out of the semantic schema and joined by keys. The renderer consumes it and the analyzers never do. Provenance is blame and debug symbols. Every IR node, finding, and extracted parameter can answer which file and line (or page and table) it came from, and that answer makes a finding verifiable rather than asserted.

Circuit. Click a finding, land on the exact wire in the schematic. Click a datasheet-backed limit, land on the exact table in the PDF.

Schema. geom.SchematicGeometry joined by ref_des/net/provenance keys (see Geometry and rendering). ir.Provenance and param.ParamProvenance carry the source keys.

source map

Where the analogy breaks (on purpose)

  • No dynamic dispatch, no open world. Every connection is resolved at design time, and the whole program is one closed compilation unit. That is why exhaustive static checking works at all, and why “the design” can be diffed as a value.
  • Nets are symmetric. There is no caller. Electrically everyone on the net calls everyone. Direction is a property of pins, not edges, so pin directions do the type-annotation work and their absence means skip.
  • Runtime is physics. There is no sandbox. Running the program means powering a board, so the linting tier (checks against stubs) carries weight software linters do not. It is the cheap static end of a ramp whose expensive end is simulation.
  • Instances are atoms. Two “identical” resistors are still two physical objects with tolerances, and the stub describes a population, not your unit. That is what tolerance analysis exists to reason about, and why characteristics carry min/typ/max rather than one number.