Tail-Current Compatibility¶
tail_current-slot compatibility filter and pruning for
enumerate_circuits().
Every topology has a tail_current slot, wired
input_pair.tail -> net_tail <- tail_current.out. But only the 4
differential_pair_* input_pair variants actually reference their
tail port from a device terminal (s/b: tail on the tail
transistor, or t2: tail on the degenerated variants’ tail resistor).
The fifth input_pair variant, inverter_based_input – two
back-to-back CMOS inverters – is self-biased by design and never
references tail, so for that variant net_tail is a floating,
single-terminal node and tail_current is synthesized dead.
Without a filter, all 6 tail_current variants would be enumerated for
every combination, but for inverter_based_input the choice between them
makes zero difference to the assembled circuit – pure combinatorial
duplication, plus dead tail_current_* devices and an unnecessarily
“needed” bias rail 7 (tail_current.bias).
is_tail_current_compatible() collapses this duplication: for an
input_pair that doesn’t reference tail, only the canonical
CANONICAL_TAIL_CURRENT_VARIANT is allowed through.
prune_tail_current() then empties that variant’s ports/devices for
those combinations, so it contributes no devices and tail_current.bias
is no longer “needed” (see
required_rail_kinds()).
Both the filter and the prune are required together, exactly as with
is_cmfb_compatible()/
prune_cmfb():
itertools.product enumerates all 6 tail_current variants for every
combination before pruning runs. If prune_tail_current() ran
unconditionally with no filter, all 6 choices for an
inverter_based_input combination would each be pruned to the same empty
tail_current_absent placeholder – and since pruning happens before the
circuit name is computed, that would yield 6
SynthesizedCircuits with
identical devices and identical names for what should be one circuit.
is_tail_current_compatible() prevents this by rejecting all but
CANONICAL_TAIL_CURRENT_VARIANT up front, so
prune_tail_current() only ever empties that one canonical choice – 1
circuit per (inverter_based_input, load, bias_gen, ...) combination, not
6.
To extend: tag a new or edited input_pair variant’s tail-side device
terminal(s) with tail to make it a genuine tail_current consumer –
no code changes needed here. A new self-biased, no-tail input_pair
variant is automatically treated like inverter_based_input.
- circuitgenome.synthesizer.compatibility.tail_current.is_tail_current_compatible(variant_map)[source]¶
Return
Falseiftail_current’s variant choice is irrelevant for this combination.If
input_pairreferences itstailport (all 4differential_pair_*variants), everytail_currentvariant supplies a real bias current and remains distinct. Forinverter_based_input– which is self-biased and never referencestail–tail_current.outdrives nothing, so onlyCANONICAL_TAIL_CURRENT_VARIANTis allowed through – the other 5 variants would otherwise be enumerated as duplicate no-op circuits.- Parameters:
variant_map (dict[str, ModuleVariant])
- Return type:
- circuitgenome.synthesizer.compatibility.tail_current.prune_tail_current(variant, input_pair)[source]¶
Return an empty placeholder if input_pair doesn’t consume
tail_current.out.If input_pair references its
tailport, variant is returned unchanged. Otherwise, returns a copy of variant with no ports and no devices – it contributes nothing to the assembled circuit,net_tailis no longer left floating, andtail_current.biasis no longer “needed” byrequired_rail_kinds().- Parameters:
variant (ModuleVariant)
input_pair (ModuleVariant)
- Return type: