Source code for circuitgenome.sizer.gmid.analyze

"""Phase 1 — Analyze: build the structural view of the recognised circuit.

Everything the later phases need to know about the circuit's *structure* is
derived here, once: the per-slot device lists, the deduplicated ref→(device,
slot) map, the typed block view (load kind, stage count, fully-differential
flag), the cascode devices, and the topology-mismatch warnings.  All of it is
read-only from here on.
"""
from __future__ import annotations

from dataclasses import dataclass

from circuitgenome.recognizer.models import FunctionalBlockRecognitionResult
from circuitgenome.synthesizer.models import Device, TopologyTemplate

from ..shared.preprocess import (
    check_topology_match,
    deduplicate_devices,
    extract_slot_resistors,
    extract_slot_transistors,
)
from .blocks import OpAmpBlocks, build_blocks, cascode_device_refs


[docs] @dataclass class CircuitView: """Structural view of the circuit (Phase 1 output). :param slot_transistors: FBR slot name → MOSFET devices in that slot. :param slot_resistors: FBR slot name → resistor devices in that slot. :param all_transistors: deduplicated ref → (Device, owning slot); a device appearing in several slots is attributed to the highest-priority one. :param blocks: the typed :class:`~.blocks.OpAmpBlocks` decomposition. :param cascode_refs: refs of stacked (cascode) current-source devices. :param warnings: topology-mismatch advisories from the structural check. """ slot_transistors: dict[str, list[Device]] slot_resistors: dict[str, list[Device]] all_transistors: dict[str, tuple[Device, str]] blocks: OpAmpBlocks cascode_refs: set[str] warnings: list[str]
def _adopt_orphan_mosfets( slot_transistors: dict[str, list[Device]], fbr_result: FunctionalBlockRecognitionResult, topology: TopologyTemplate, ) -> None: """Attribute slot-suffixed MOSFETs the FBR left unassigned to their slot. Pattern matching can leave a device out of every slot when its structure is ambiguous (two identical parallel diodes on one net) or incomplete (a bias leg whose expected partner lives in another slot, e.g. a cascode tail's reference diode). Such a device would silently go **unsized** and run at the simulator's default W/L. Synthesized netlists name every device ``{ref}_{slot_name}``, so orphans are attributed -- and therefore sized -- by their ref suffix; devices whose ref matches no slot (external netlists) are left alone. """ assigned = {d.ref for devs in slot_transistors.values() for d in devs} slot_names = sorted((s.name for s in topology.slots), key=len, reverse=True) candidates = [d for s in fbr_result.unassigned_structures for d in s.devices] candidates += list(fbr_result.unrecognized_devices) for dev in candidates: if dev.type not in ("nmos", "pmos") or dev.ref in assigned: continue slot = next((s for s in slot_names if dev.ref.endswith("_" + s)), None) if slot is not None: assigned.add(dev.ref) slot_transistors.setdefault(slot, []).append(dev)
[docs] def analyze_circuit( fbr_result: FunctionalBlockRecognitionResult, topology: TopologyTemplate ) -> CircuitView: """Build the :class:`CircuitView` from the FBR assignments.""" slot_transistors = extract_slot_transistors(fbr_result) _adopt_orphan_mosfets(slot_transistors, fbr_result, topology) slot_resistors = extract_slot_resistors(fbr_result) return CircuitView( slot_transistors=slot_transistors, slot_resistors=slot_resistors, all_transistors=deduplicate_devices(slot_transistors), blocks=build_blocks(slot_transistors, slot_resistors), cascode_refs=cascode_device_refs(slot_transistors), warnings=check_topology_match(slot_transistors, topology.name), )