Language Built-in intrinsics

Built-in intrinsics

A handful of built-ins are available everywhere without an import. Two of them — V() and I() — are how logic reads the circuit, and both come with a timing rule worth internalising early.

The intrinsics

V(wire)Voltage at an MNA node, from the previous timestep
I(element)Current through a named branch-bearing element, from the previous timestep
idt(x)Integrates the value over time. Analog modules only
ddt(x)Differentiates the value with respect to time. Analog modules only

Both read the previous solve. V() and I() cannot see the result of the current timestep — that value doesn't exist yet when logic runs.

Reading currents — I()

Unlike V(), which takes a wire, I() takes an element instance name — so the element has to have been given one. See Named elements.

Element references are module-local, exactly like nets: a bare name resolves within the same module instance. In a top-level directive, where there's no enclosing instance, write the full flattened path — .save(I(main.probe.vsense)).

Which elements have a current

Only elements that own a row in the MNA matrix — an extra unknown for their current — can be read. For everything else the current is derived from node voltages rather than solved for, so there's no variable to point at.

Readable with I() Not readable
voltage_source R
L C
VCVS VCCS
CCVS CCCS, current_source

This is the same set that may appear as a CCCS/CCVS sense reference, and the same set .save() accepts as a current column — the three features share one definition.

To measure a current through anything in the right-hand column, put a 0 V voltage source in series with it as an ammeter and read that instead:

A bad I() argument is a hard compile error, not a runtime warning — and the compiler distinguishes "no such element" from "that element has no branch current", because the fixes differ. The latter prints the ammeter declaration for you.

Sign convention

I() is positive when current flows from the element's n+ terminal, through the element, to its n- — the passive sign convention, as in SPICE. The consequence worth internalising: a source that is delivering power reads negative, because its current flows internally from n- to n+.

nr_prev()

nr_prev(expr) evaluates expr using the node voltages and branch currents from the previous Newton-Raphson iteration of the current timestep's solve. It's the iteration-level counterpart to state: where state remembers across timesteps, nr_prev() remembers across iterations within one timestep.

V(a), I(e)Most recently solved value — the iterate the solver is currently refining
nr_prev(V(a))Its value one NR iteration earlier, same timestep
state double xIts value carried over from the previous timestep

It introduces no storage of its own: it re-emits its argument with every nested V() and I() routed to their previous-iteration source. Everything else — parameters, arithmetic, time, dt — is emitted unchanged. Only V() and I() carry iteration history, so only they are redirected.

The point is to let you write iteration-aware numerical aids — junction limiting, damping, source ramping — in the language rather than hard-coding them into the solver. The solver is general and makes no assumption that a node is a semiconductor junction, so the per-device limiting that keeps a stiff exponential from overflowing belongs in the model:

Supported forms

nr_prev() accepts any expression — there's no special argument grammar. The redirection is purely lexical over the V()/I() leaves, so it composes with arbitrary surrounding math and nests idempotently.

Two limits worth knowing. It does not give the previous-iteration value of a computed local or state signal — nr_prev(x) on an ordinary variable emits x unchanged and is a no-op; recompute from nr_prev-wrapped reads instead. And it reaches into call arguments (sin(V(a))) but not into a user function's body.

On the first iteration of a timestep there's no earlier iterate, so nr_prev() returns that step's starting guess — meaning the first iteration applies no limiting, which is the intended cold-start behaviour.

Special keywords

time Current simulation time in seconds. Read-only, available everywhere.
dt Timestep in seconds. Read-only, available everywhere.
gnd Ground node (0 V reference). Available everywhere as a wire.
state Variable modifier — retains value across timesteps.
input / output Logic port direction in module declarations.