Wires & components
A circuit is a graph of wire nodes connected by primitives that stamp directly into the MNA matrix. They use the same bracket convention as modules, but need neither a name nor the module keyword.
Wires
Inside a module body, wire a, b, c; declares nodes local to that module. A module's port list — the bracketed part of its signature — is also a list of wires, supplied by whoever instantiates it. One node is always in scope without being declared: gnd, the 0 V reference.
Physical primitives
| Primitive | Params | Ports | Description |
| R | <resistance> | [n+, n-] | Resistor |
| L | <inductance> | [n+, n-] | Inductor |
| C | <capacitance> | [n+, n-] | Capacitor |
| voltage_source | <dc_value> | [n+, n-] | Ideal voltage source |
| current_source | <dc_value> | [n+, n-] | Ideal current source |
| VCCS | <gm> | [n+, n-, nc+, nc-] | Voltage-controlled current source, I = gm · V(nc+, nc−) |
| VCVS | <k> | [n+, n-, nc+, nc-] | Voltage-controlled voltage source, V = k · V(nc+, nc−) |
| CCCS | <beta> | [n+, n-, sense] | Current-controlled current source, I = β · I(sense) |
| CCVS | <r> | [n+, n-, sense] | Current-controlled voltage source, V = r · I(sense) |
Note the asymmetry in the last four rows. The voltage-controlled pair takes four wires — two output terminals and two control terminals — because a control voltage is a property of a node pair. The current-controlled pair takes three: two output terminals and the name of an element, because a current in the MNA formulation isn't a property of a node pair but a variable belonging to one specific branch.
Semiconductor models — diodes, BJTs — aren't primitives. They're analog modules in the standard library, instantiated like any other module.
Named elements
Any primitive may be given an instance name, written between the primitive type and the <> list:
A name is not decoration — it's the only way to address an element, and three features depend on it:
I(rsense)— reading the element's current from logic. See Built-in intrinsics..save(rsense)— logging that current to the CSV.- The trailing
senseentry ofCCCS/CCVS.
Names scope to the module instance, exactly as wires do. A module containing voltage_source vsense<0>()[a, b]; instantiated twice yields two distinct elements, main.a.vsense and main.b.vsense. Inside the module body you write the bare name; from a top-level directive you write the full flattened path.
An unnamed primitive still gets an internal name of the form main.R_3, but you should never write one — the index shifts whenever a component is added above it. The compiler deliberately omits synthesized names from its "did you mean" suggestions for that reason.
Restrictions. An element name may not collide with another element in the same module, with a wire, or with a logic signal — all three are compile errors. It also may not take the form <Type>_<number>, which is reserved for synthesized names.
Current-controlled sources (CCCS / CCVS)
These are controlled by the current through another element, named as the third [] entry:
The first two entries are ordinary wire terminals. The third is not a wire — it's the name of an element declared elsewhere in the design, and it must name one that owns a branch row. Supplying only two entries is a compile error rather than a silently dead stamp.
The reference may point forward: the sensed source is very often declared below the controlled source in the same body, and the compiler resolves all sense references in a post-pass once the design is flattened, so declaration order never matters.
Self-reference is rejected. A CCVS that senses its own branch would produce a structurally singular matrix, so the compiler refuses it outright.
| CCCS | The generated current β · I(sense) flows from n+, through the source, to n- — drawn out of the n+ node and injected into n-. |
| CCVS | Sets V(n+) − V(n−) = r · I(sense). r has units of ohms (transresistance). |
The 0 V ammeter idiom
Because only branch-bearing elements have a current to sense, the standard technique — the same one SPICE uses — is to insert a 0 V voltage source in series with the branch you care about. It's electrically invisible, but it creates the branch row that makes the current a solved unknown:
Pointing a CCCS/CCVS at a resistor, capacitor, VCCS or current_source instead is a compile error, and the diagnostic prints the ammeter declaration you need.
Worked example
This produces main.out = 3.0 V, main.hn = 0.2 V and I(main.vsense) = 2 mA, constant for the whole run.
Driven and fixed sources
Pass a controlling signal through the logic port () to drive a source from logic each step:
Or give a compile-time value in <> for a fixed one: