Source code for circuitgenome.synthesizer.compatibility.cmfb

"""
CMFB-slot compatibility filter and pruning for
:func:`~circuitgenome.synthesizer.synthesizer.enumerate_circuits`.

Every ``fully_differential`` topology has a ``cmfb`` slot, wired
``cmfb.out -> net_cmfb_out -> load.bias_cmfb``. But only 4 of the 12 ``load``
variants -- ``folded_cascode_load_{nmos,pmos}_input_differential_output``
(gating ``mn3``/``mn4`` or ``mp1``/``mp2``) and
``current_source_load_{pmos,nmos}`` (gating both branch devices, issue #112),
the four tagged ``output_cardinality: "differential"`` -- declare
``bias_cmfb`` as ``role: input`` and actually reference it from a device
terminal. The other 8 ``load`` variants declare ``bias_cmfb`` as
``role: optional`` and never reference it, so ``net_cmfb_out`` drives nothing
for those combinations.

Without a filter, both ``cmfb`` variants (``resistive_sense_cmfb``/
``dda_cmfb``) would be enumerated for every combination, but for the 8
non-``"differential"`` loads the choice between them makes zero difference to
the assembled circuit -- pure combinatorial duplication, plus 7-8 dead
``cmfb_*`` devices and an unnecessarily "needed" bias rail 4 (``cmfb.bias``).

:func:`is_cmfb_compatible` collapses this duplication: for a ``load`` whose
``output_cardinality`` isn't ``"differential"``, only the canonical
:data:`CANONICAL_CMFB_VARIANT` is allowed through. :func:`prune_cmfb` then
empties that variant's ports/devices for those combinations, so it
contributes no devices and ``cmfb.bias`` is no longer "needed" (see
:func:`~circuitgenome.synthesizer.bias_construction.required_rail_kinds`).

To extend: tag a new or edited ``load`` variant with
``output_cardinality: "differential"`` (and give it a real
``bias_cmfb: role: input`` consumer) to make it a genuine ``cmfb`` consumer --
no code changes needed here.
"""
from __future__ import annotations
import dataclasses

from ..models import ModuleVariant

CANONICAL_CMFB_VARIANT = "resistive_sense_cmfb"
_CMFB_CONSUMING_CARDINALITY = "differential"


[docs] def is_cmfb_compatible(variant_map: dict[str, ModuleVariant]) -> bool: """Return ``False`` if ``cmfb``'s variant choice is irrelevant for this combination. Topologies without a ``cmfb`` slot are unaffected. For a ``load`` whose ``output_cardinality`` is ``"differential"``, both ``cmfb`` variants drive a real ``bias_cmfb`` consumer and remain distinct. For every other ``load``, ``cmfb.out`` drives nothing, so only :data:`CANONICAL_CMFB_VARIANT` is allowed through -- the other variant would otherwise be enumerated as a duplicate no-op circuit. """ if "cmfb" not in variant_map: return True if variant_map["load"].output_cardinality == _CMFB_CONSUMING_CARDINALITY: return True return variant_map["cmfb"].name == CANONICAL_CMFB_VARIANT
[docs] def prune_cmfb(variant: ModuleVariant, load: ModuleVariant) -> ModuleVariant: """Return an empty placeholder if *load* doesn't consume ``cmfb.out``. If *load*'s ``output_cardinality`` is ``"differential"``, *variant* is returned unchanged. Otherwise, returns a copy of *variant* with no ports and no devices -- it contributes nothing to the assembled circuit, and ``cmfb.bias`` is no longer "needed" by :func:`~circuitgenome.synthesizer.bias_construction.required_rail_kinds`. """ if load.output_cardinality == _CMFB_CONSUMING_CARDINALITY: return variant return dataclasses.replace(variant, name="cmfb_absent", ports=[], devices=[])