CMFB Compatibility

CMFB-slot compatibility filter and pruning for 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).

is_cmfb_compatible() collapses this duplication: for a load whose output_cardinality isn’t "differential", only the canonical CANONICAL_CMFB_VARIANT is allowed through. 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 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.

circuitgenome.synthesizer.compatibility.cmfb.is_cmfb_compatible(variant_map)[source]

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 CANONICAL_CMFB_VARIANT is allowed through – the other variant would otherwise be enumerated as a duplicate no-op circuit.

Parameters:

variant_map (dict[str, ModuleVariant])

Return type:

bool

circuitgenome.synthesizer.compatibility.cmfb.prune_cmfb(variant, load)[source]

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 required_rail_kinds().

Parameters:
Return type:

ModuleVariant

circuitgenome.synthesizer.compatibility.cmfb.orient_cmfb(variant, topology, variant_map)[source]

Return the polarity-correct CMFB orientation for topology (issue #165).

The CMFB senses the external outputs (outp/outn) and drives the first-stage load gates, so the CM loop traverses every stage after the first. All four CMFB-consuming loads respond the same way (cmfb.out up → first-stage output CM down), so the loop sign depends on the net inversion parity of the chosen stage chain (_cm_loop_inversions() over variant_map — NOT the stage count: the NMC three-stage chain is noninverting_stage + common source, the same odd parity as a two-stage despite its three stages):

  • odd parity (net-inverting chain — two-stage, NMC three-stage): the loop is positive with the stock amp orientation — swap the sense/vref gates (<name>_inverting) so a rising output CM lowers cmfb.out;

  • even parity (RNMC three-stage’s CS+CS chain): the stock orientation is already negative — returned unchanged.

An unclassifiable chain falls back to the stage-count rule (two-stage → inverting). cmfb_absent placeholders pass through untouched.

Parameters:
Return type:

ModuleVariant