# Hierarchical Devices

`hierdevice.h`/`hierdevice.cpp` implement subcircuit instantiation: `HierarchicalDevice`, `HierarchicalModel`, and `HierarchicalInstance` are the [`Device`/`Model`/`Instance`](devbase.md) triple behind every `subckt`/`ends` definition and its `x`-style instances - the one device family in the codebase whose "instances" can themselves contain further instances of any other device, including more hierarchical ones.

## `HierarchicalDevice`: one shared marker device

There is effectively a single `HierarchicalDevice` in the whole simulator (the constructor's own comment: "We will have only one instance of this class so the identifier is fixed") - it exists to satisfy `isHierarchical()` and to be the `Device*` every `HierarchicalModel` points back to, not to hold per-subcircuit state itself. `createModel()` is explicitly disabled (`DBGCHECK` and a `nullptr` return): the generic `Device::createModel(circuit, parentInstance, evaluator, PTModel, s)` signature every other device implements doesn't carry enough context to build a subcircuit model - a `HierarchicalModel` additionally needs its parent *definition* (for nested `subckt`s) and a definition-hierarchy path prefix, so it is always constructed directly instead.

## `HierarchicalModel`: a bound subcircuit definition

Wraps one parsed `PTSubcircuitDefinition` ([Circuit Representation and Construction API](parseroutput.md)). `buildTerminalMap()`/`buildParameterMap()` run once at construction, building name-to-index maps (rejecting duplicate terminal/parameter names) - the dynamic, parse-time equivalent of what [`Introspection`](introspection.md) builds at compile time for a fixed C++ struct, needed here because a subcircuit's terminal and parameter list is only known once the netlist is parsed.

`setParameter()` on a `HierarchicalModel` always fails with `Status::Unsupported` ("Model parameters cannot be set on a subcircuit definition") - a subcircuit's parameter *defaults* live in the shared, parsed definition; per-instance overrides are stored on each `HierarchicalInstance` instead (see below), never on the shared model.

### Two kinds of subcircuit parameters

`HierarchicalModel::parameterCount()` deliberately counts only the definition's `PTParameterValue` entries (`parameters().valueCount()`), not its `PTParameterExpression` entries - so only *constant-default* parameters from the subcircuit's `parameters` statement become the formal, instance-overridable parameter list (`x1 mysubckt p1=5 ...`). An `expression`-valued entry in the same `parameters` statement is not exposed through the `Parameterized` interface at all: it has no index, isn't settable by an instantiating line, and is instead re-evaluated fresh in each instance's own context every time that context is (re)built (see `enterContext()` below) - functioning as a local, context-dependent computed name (which may itself reference the instance's own resolved value-parameters) rather than an overridable argument.

### Nested definitions

`parentDefinition_`/`childrenPathPrefix_` track the *definition* hierarchy (a `subckt` declared textually inside another `subckt`), which is a separate concept from the *instance* hierarchy. `HierarchicalModel::translate()` mangles a name with `::` (as opposed to `Instance::translate()`'s `:`) along this definition path - it's what lets an instance anywhere inside an outer subcircuit definition instantiate a `subckt` nested directly inside that same definition, by unqualified name, regardless of which particular outer instance it's part of (see master-name resolution below).

## `HierarchicalInstance`: one subcircuit instantiation

Holds a raw `Value* parameters` array (`new`/`delete[]`, not an `IStruct<T>`, since a subcircuit's parameter set is dynamic rather than a compile-time struct), sized to the model's `parameterCount()` and initialized from the model's defaults at construction, then overridden per-instance via `setParameter()` - which sets `Instance::Flags::ParamsChanged` on an actual change, the same dirty-flag convention [`devbase.md`](devbase.md) documents for every device family.

Its dangling-terminal policy matches [`OsdiInstance`](osdiinstance.md) (connect to an internal node), not [`BuiltinInstance`](devbuiltin.md)'s "require every terminal connected."

`translateNode()` overrides the base behavior: if the name being translated is one of *this subcircuit's own terminals*, it resolves to whatever node is actually bound to that terminal in the enclosing scope, instead of being translated into an internal name the usual way - "terminals shadow global nodes." An unconnected terminal (not yet bound) falls through to ordinary `Instance::translateNode()` behavior.

`childInstances_`/`childModels_` are the actual owned lists of everything built inside this instance - what `childInstances()`/`childModels()` expose to [`Instance::HierarchicalIterator`](devbase.md) for whole-hierarchy traversal.

## Instantiating: `HierarchicalModel::createInstance()`

The choreography for turning a parsed `PTInstance` into a live subtree, in order:

1. Bind connected terminals to nodes, set the instance's own parameters from the parsed `PTInstance`.
2. `idata.addAncestor(instance)` records this instance's model in the shared `InstantiationData` ancestor set - the recursion guard a subcircuit definition instantiating itself (directly or through a chain of other subcircuits) trips over later, in `buildBlock()`.
3. `enterContext()` pushes a fresh [`ContextStack`](context.md) scope and (when rebuilding) loads this instance's resolved value-parameters, then evaluates and loads the definition's expression-only "local" parameters (see above) into the same scope, so later expressions can see both.
4. `recomputeBlockConditions()` evaluates every `@if`/`@elseif`/`@else` chain in the subcircuit body in that now-established context, populating `activeBlocks`.
5. `buildHierarchy()` actually creates the child models and instances found in the root block and every active conditional block.
6. The context is reverted and the ancestor marker removed unconditionally - on success or failure alike - and any failure at any step aborts the whole instantiation.

An empty instance name (the toplevel netlist's own implicit subcircuit "instance") is defaulted to `__toplevel__`.

## Conditional blocks at runtime: `recomputeBlockConditionsWorker()`

Walks one `PTBlockSequenceEntry` ([PTBlock and PTBlockSequence](ptblock.md)) at a time: an empty condition expression (how the parser represents `@else`, see [Parser](dflparser.md)) is treated as unconditionally true; otherwise the expression is evaluated with an `RpnEvaluationNetlistContext` tagged `MCData::CtxType::Condition` (the context type [the netlist context](rpnevalctx.md) documents as never actually drawing Monte Carlo randomness) and interpreted as a boolean via [`Value::checkCondition()`](value.md). The first `@if`/`@elseif`/`@else` arm whose condition is true is added to `newBlocks` and, if it itself contains nested `@if` chains, recursed into the same way; evaluation of the sibling arms in that sequence stops there - exactly the short-circuit `@if`/`@elseif`/`@else` semantics the netlist language specifies. `recomputeBlockConditions()` runs this over the subcircuit's whole root block.

## Detecting and propagating change

`subhierarchyChanged()` re-runs `recomputeBlockConditions()` into a fresh list and compares it against the currently active `activeBlocks` by pointer (both point into the same parsed, immutable `PTSubcircuitDefinition`, so pointer equality is exactly "same parse-tree block") - if the *set* of active conditional blocks differs from before, the caller (the circuit's parameter-propagation driver) knows this instance's subtree needs to be torn down and rebuilt, not just have its parameters updated in place.

`propagateParameters()` pushes a value-parameter change down exactly one level: for every child model and child instance, it re-evaluates and re-applies only their *expression*-typed parameters (never their constant ones, which can't depend on anything that changed) in the already-established context, marking a child that actually changed `NeedsSetup`/`ParamsChanged` so the same propagation continues further down on a later pass - the incremental, changed-only cascade [`devbase.md`](devbase.md)'s flag conventions exist to support.

## Resolving a child's master: `buildBlock()`

Runs in two passes - every `model` statement in a block is created before any `instance` in the same block, so instance order relative to model order in the source doesn't matter. For an instance's master name, three tiers are tried in order: a model or subcircuit definition scoped to *this instance's* own hierarchy path (`Instance::translate`, `:`-separated); failing that, a subcircuit definition nested inside *this subcircuit's own definition* (`HierarchicalModel::translate`, `::`-separated - reachable from any instance anywhere inside that definition, not just this specific instantiation); failing that, a bare global name. A device name alone is deliberately not tried - an instance needs an explicit `model` statement to bind to, even a trivial one (`model r r`). Before creating a child instance, `idata.isAncestor(mod)` checks the recursion guard set up in `createInstance()` above, reporting `Status::Recursion` if this subcircuit definition is already its own ancestor in the instantiation chain.

## Tearing down: `deleteHierarchy()`

Recursively deletes each child instance's own subhierarchy first, unbinds its terminals, and removes it from the circuit; then removes every child model; then unbinds this instance's own internal nodes (the ones created for its own unconnected terminals) and clears the child lists - the operation `subhierarchyChanged()` reporting a change is expected to trigger, before `buildHierarchy()` runs again to rebuild the subtree against the new conditional-block set.
