# Initial Circuit Elaboration Protocol

Source: `circuit.h`, `circuit.cpp` (`Circuit::elaborate()`).

This is the protocol run once, from a clean slate, to turn a parsed netlist
(parser tables) into a fully elaborated `Circuit`: models and instances
built, nodes created and ordered, unknowns assigned, and the Jacobian
sparsity pattern enumerated. It is distinct from `Circuit::elaborateChanges()`,
which re-elaborates an already-elaborated circuit after parameter/option/
sweep changes (see `elaborateChanges()` in `circuit.h`).

Any failure aborts the protocol immediately, returns `false`, and sets/extends
the `Status`. Because step 1 (`clear()`) already wiped all derived state, an
aborted elaboration leaves the circuit empty and consistent rather than
half-built.

## Protocol

1. Bump the elaboration counter and start wall-clock accounting; call
   `clear()` to wipe all previously elaborated state (instances, models,
   nodes, sparsity map, toplevel bookkeeping), keeping circuit variables and
   options untouched; clear the `Elaborated` flag.
2. Recursively build every subcircuit definition found in the parsed netlist
   with `processSubcircuitDefinition()`, starting from the default toplevel
   `PTSubcircuitDefinition`. This creates one `HierarchicalModel` per
   definition (default toplevel, definitions named in `toplevelDefinitions`,
   and everything nested inside them), assigning each a path-prefixed name.
3. Create the ground nodes (`addGround()`, one call per ground node
   registered in the parser tables) and the global nodes (`addGlobal()`).
4. Register the default toplevel model, plus one `HierarchicalModel` per
   name in `toplevelDefinitions` (looked up with `findModel()` and checked
   to be hierarchical), as toplevel models (`toplevelModels_`).
5. For each toplevel model, build its toplevel instance with
   `buildTopInstance()`: fabricate a placeholder `PTInstance` and call
   `HierarchicalModel::createInstance()`, which instantiates and recursively
   elaborates the subhierarchy in its own `Context`. Once the first
   (default) toplevel instance is built, its `Context` is pushed onto the
   context stack and added to the search path via `Instance::enterContext()`
   so later toplevel instances can see the default toplevel circuit's names;
   it is not rebuilt again.
6. Unwind the parameter-evaluation context stack back to the marker recorded
   before step 5, via `Instance::revertContext()` — done both on success and
   on any failure inside step 5, so a partially built toplevel instance never
   leaves stale context entries behind.
7. Rebuild per-device/per-model entity lists with `buildEntityLists()`:
   clears and refills every `Device`'s model list and every `Model`'s
   instance list from `modelMap`/`instanceMap`.
8. Order nodes with `nodeOrdering()`: collect all nodes from `nodeMap`, sort
   them (ground nodes first, then lexicographically by name), and fail if
   there are no nodes at all or no ground node.
9. Run a full device setup pass, `setup(commons, forceFull=true, devReq, s)`,
   which calls `Device::setup()` on every device — assigning model/instance
   parameter defaults and determining node-collapsing requirements — and
   aggregates whether unknowns or sparsity changed. This is where mutual
   inductance resolves its coupled inductors' flow nodes (see
   [Binding to controlling flow nodes](#binding-to-controlling-flow-nodes)
   below).
10. Assign unknowns to nodes with `mapUnknowns()`: give every node a
    provisional unknown index (ground = 0), let each device collapse nodes
    via `Device::collapseNodes()`, renumber the surviving unknowns
    contiguously, build the unknown-to-representative-node table
    (`unknownToReprNode`), and reset the per-unknown residual contribution
    counters.
11. Build the sparsity pattern and states vector with
    `buildSparsityAndStates()`: clear the sparsity map, call
    `Device::populateStructures()` on every device (each adds its Jacobian
    entries via `Circuit::createJacobianEntry()` and allocates its
    states-vector slots), then add a resistive diagonal Jacobian entry for
    every non-ground unknown. This is where current-controlled sources
    resolve their controlling voltage source's flow node (see
    [Binding to controlling flow nodes](#binding-to-controlling-flow-nodes)
    below).
12. Enumerate the Jacobian with `enumerateSystem()`
    (`sparsityMap_.enumerate()`), assigning a stable index to every sparsity
    map entry. Binding these entries to a concrete matrix is left to the
    analysis and is not done here.
13. Run post-elaboration sanity checks: fail if `nodeCount()<=0`,
    `unknownCount()<=0`, or the sparsity map has no entries.
14. Mark the circuit consistent by clearing `VariablesChanged`,
    `HierarchyAffectingOptionsChanged`, `MappingAffectingOptionsChanged`, and
    `HierarchyParametersChanged`, then set the `Elaborated` flag.
15. Accumulate wall-clock elaboration accounting and return `true`.

## Binding to controlling flow nodes

Current-controlled sources (CCCS/CCVS), mutual inductance, and behavioral
sources (`i(instname)`/`I(inst1,inst2)` probes) don't control their own
current — they read the branch current (flow) unknown of another instance
(a voltage source, an inductor, or any instance exposing a flow node). That
instance is named by a parameter (`ctlinst` for CCCS/CCVS, `ind1`/`ind2` for
mutual inductance) or by an identifier in a behavioral expression, so
resolving it means looking the peer instance up by name and reading its
internal flow node. All three share the same lookup helper,
`Instance::findControl()` (`lib/devbase.cpp:155-168`, declared
`include/devbase.h:434`):

```cpp
Node* Instance::findControl(Circuit& circuit, Id instanceName, Id internalNodeName, Status& s) {
    auto peerInstance = findPeerInstance(circuit, instanceName, s);   // name -> circuit.findInstance()
    ...
    Id nodeName = peerInstance->translateNode(circuit, internalNodeName); // qualify "flow(br)" under peer's namespace
    auto* node = circuit.findNode(nodeName);                          // name-based node lookup
    ...
    return node;
}
```

`findControl()` first resolves the peer instance by name with
`Instance::findPeerInstance()` (`circuit.findInstance()`), then translates
the controlling internal node name — the default is `"flow(br)"`, the
standard internal branch/flow node every voltage source and inductor
exposes — into that peer's fully-qualified node name and resolves it with
`circuit.findNode()`. This is a name-based lookup, not an index or pointer
copy stashed from elsewhere; it depends only on the peer instance already
existing in `instanceMap`/`nodeMap`, so it works regardless of device
instantiation order.

- **CCCS/CCVS** (`lib/devctlsrc.cpp`) call `findControl()` from
  `populateStructuresCore()` — CCCS at line 496, CCVS at line 655 — i.e.
  during step 11 (`buildSparsityAndStates()`/`Device::populateStructures()`).
  The resolved `Node*` is stored as the controlling unknown (`uCtl`) and used
  immediately to create the Jacobian entries coupling the source's own
  node(s) to the controlling flow node. `bindCore()` (called later, from
  analysis `bind()`, not part of `elaborate()`) only turns the
  already-resolved `(node, uCtl)` pairs into concrete Jacobian-entry
  pointers — it does not repeat the name lookup.
- **Behavioral sources** (`name (n1 n2) v=expr`/`i=expr`) are compiled to a
  synthetic Verilog-A module and run as an ordinary `OsdiInstance`, so they
  don't get a device-specific override at all — they ride the generic OSDI
  path. Each `i(instname)` reference found while translating the expression
  to VA (`Rpn::verilogA()`, `lib/rpnexprva.cpp:238-259`) becomes a
  synthetic, deliberately dangling flow pseudo-terminal recorded in
  `RPNBehavioralVA::flow`; `lib/parseroutput.cpp:669-671` notes explicitly
  that these are left unconnected at instance creation and "connected during
  `OsdiInstance::populateStructuresCore()`". That connection happens at
  `lib/osdiinstance.cpp:505-527`, in the same step-11 phase as CCCS/CCVS: it
  loops over the recorded flow references, resolves each with
  `findControl(circuit, fId, "flow(br)", s)`, and binds the result with
  `bindNode()`. Because this path lives in the shared `OsdiInstance`, it
  applies to any OSDI/Verilog-A model using branch-current probes, not just
  literal behavioral B-sources.
- **Mutual inductance** (`lib/devctlsrc.cpp`, `BuiltinMutualInstance`) calls
  `findControl()` from `setupCore()` (lines 858 and 862, for `ind1`/`ind2`
  respectively) — i.e. during step 9 (`Circuit::setup()`/`Device::setup()`),
  earlier than CCCS/CCVS. It resolves both inductors' peer instances via
  `findPeerInstance()` (lines 846, 850) as well, since `preAnalysisCore()`
  later needs the peer `Instance*` to read `l` and `$mfactor` off each
  coupled inductor. Both resolved flow nodes are checked for the
  `Node::Flags::FlowNode` flag (lines 866-875) and stored; `populateStructuresCore()`
  (step 11, lines 983-999) then uses them directly to create the reactive
  Jacobian entries between the two flow nodes and allocate 4 states.
  `bindCore()` again only converts the already-resolved nodes into
  `UnknownIndex`/Jacobian pointers once unknown numbering is final.

## Functions involved

- **`Circuit::elaborate()`** — orchestrator; entry point for the whole
  protocol.
- **`Circuit::clear()`** — wipes all derived circuit state (instances,
  models, nodes, sparsity map, toplevel bookkeeping); leaves circuit
  variables and options untouched.
- **`Circuit::processSubcircuitDefinition()`** — recursively turns the
  parsed `PTSubcircuitDefinition` tree into `HierarchicalModel` objects,
  computing toplevel-vs-nested naming and path prefixes.
- **`Circuit::addGround()`** — creates one ground node from the parser
  tables' ground node list.
- **`Circuit::addGlobal()`** — creates one global node from the parser
  tables' global node list.
- **`Circuit::findModel()`** — looks up a model by name; used to resolve
  extra toplevel definitions named in `toplevelDefinitions`.
- **`Circuit::buildTopInstance()`** — builds one toplevel
  `HierarchicalInstance` from a `HierarchicalModel`, via
  `HierarchicalModel::createInstance()`.
- **`HierarchicalModel::createInstance()`** — instantiates a hierarchical
  (subcircuit) instance and recursively elaborates its subhierarchy
  (device-specific override of `Model::createInstance()`).
- **`Instance::enterContext()`** — pushes an instance's evaluation `Context`
  onto the context stack (and optionally the search path); used here to
  expose the default toplevel circuit's names to later toplevel instances.
- **`Instance::revertContext()`** (static) — pops the context stack back to
  a given marker; used to undo `enterContext()`/instance-building context
  pushes on both the success and failure paths.
- **`Circuit::buildEntityLists()`** — clears and refills every `Device`'s
  model list (`Device::clearModelList()`/`addModel()`) and every `Model`'s
  instance list (`Model::addInstance()`) from `modelMap`/`instanceMap`.
- **`Circuit::nodeOrdering()`** — builds `nodeOrder` from `nodeMap` and sorts
  it (ground nodes first, then by name); fails if there is no node or no
  ground node.
- **`Circuit::setup()`** — iterates all devices calling `Device::setup()` on
  each, aggregating `ok`/`unknownsChanged`/`sparsityChanged`.
- **`Device::setup()`** (virtual, per-device) — sets model/instance
  parameter defaults and determines node-collapsing requirements. Mutual
  inductance resolves its coupled inductors' flow nodes here
  (`BuiltinMutualInstance::setupCore()`).
- **`Instance::findPeerInstance()`** — resolves another instance by name
  (`circuit.findInstance()`, after peer-name translation); used to find the
  controlling voltage source (CCCS/CCVS) or coupled inductors (mutual
  inductance).
- **`Instance::findControl()`** — resolves the flow (branch-current) node of
  a named peer instance: calls `findPeerInstance()`, translates the internal
  node name (default `"flow(br)"`) into the peer's namespace, and looks it
  up with `circuit.findNode()`. Used by CCCS/CCVS
  (`populateStructuresCore()`, step 11) and mutual inductance
  (`setupCore()`, step 9) to bind to their controlling current's unknown.
- **`Circuit::mapUnknowns()`** — assigns provisional unknown indices to
  nodes, invokes `Device::collapseNodes()` on every device, renumbers
  unknowns contiguously, builds `unknownToReprNode`, and resets residual
  contribution counters.
- **`Device::collapseNodes()`** (virtual, per-device) — merges nodes that
  are electrically equivalent for a particular device instance.
- **`Circuit::buildSparsityAndStates()`** — clears `sparsityMap_`, calls
  `Device::populateStructures()` on every device, and adds resistive
  diagonal Jacobian entries for all non-ground unknowns.
- **`Device::populateStructures()`** (virtual, per-device) — adds the
  device's Jacobian entries (via `Circuit::createJacobianEntry()`) and
  allocates its states-vector slots. CCCS/CCVS resolve their controlling
  voltage source's flow node here (`BuiltinCccsInstance`/
  `BuiltinCcvsInstance::populateStructuresCore()`); mutual inductance uses
  the flow nodes it already resolved in `setupCore()` to add its reactive
  Jacobian entries (`BuiltinMutualInstance::populateStructuresCore()`).
  Behavioral sources resolve their `i(instname)` flow references here too,
  via `OsdiInstance::populateStructuresCore()` (`lib/osdiinstance.cpp:505-527`),
  since they run as ordinary OSDI instances rather than a dedicated device.
- **`Circuit::createJacobianEntry()`** — registers one (row, column) entry
  in the sparsity map; called directly for diagonal entries and indirectly
  through `Device::populateStructures()`.
- **`Circuit::enumerateSystem()`** — calls `sparsityMap_.enumerate()` to
  assign a stable index to every sparsity map entry.
- **`Circuit::nodeCount()` / `Circuit::unknownCount()`** — accessors used by
  the post-elaboration sanity checks.
- **`CommonData::fromOptions()`** — populates a `CommonData` object from the
  circuit's current simulator options; needed by `setup()`/`Device::setup()`.
- **`Accounting::wclk()` / `Accounting::wclkDelta()`** — wall-clock timing
  helpers used to accumulate elaboration accounting
  (`acctNew.elab`, `acctNew.telab`).
