sycan.headroom¶
Headroom analysis — symbolic input range that keeps every MOSFET saturated.
Given a circuit and one input axis — either a single independent
source whose value is swept, or a group of sources whose values are
all tied to one common scalar variable — solve_headroom()
returns the symbolic interval of that variable for which every
MOSFET in the circuit is in saturation.
For each MOSFET the analysis builds two saturation predicates straight from the device equations,
c1 = V_GS_eff - V_TH(V_SB) > 0(above threshold, in inversion)c2 = V_DS_eff - (V_GS_eff - V_TH) >= 0(V_DS past the overdrive knee)
with the long-channel body-effect threshold for 4T cells. The DC
operating point is solved with the saturation-form drain currents —
I_D = (1/2) β (V_GS_eff − V_TH)² (1 + λ V_DS_eff) — so cas.solve
sees a polynomial system. Substituting the solved node voltages
turns each predicate into an expression in the input variable (and
any leftover symbolic parameters); the interval edges then come from
cas.solve of each predicate against the input, never from a
numeric sweep.
For circuits whose KCLs cas.solve can’t close in one shot — the
canonical case is a 5T-OTA with a diode-connected current mirror —
pass a pre-computed op_point= mapping. Compute it however you
like (sequential elimination, hand algebra, your own solver) and the
analysis will pick up from there with the predicates / boundaries.
Typical use:
from sycan import parse, solve_headroom
c = parse("""...netlist with MOSFETs and a single Vin...""")
result = solve_headroom(c, "Vin")
print(result) # symbolic / numeric interval
print(result.predicates["MN"]) # what MN demands of x
print(result.boundaries) # per-device edge values
For a differential pair, the input axis is one symbol \(V_{id}\) that drives two physical sources:
V_id = cas.Symbol("V_id", real=True)
result = solve_headroom(
c,
sources={"Vinp": Rational(9,10) + V_id/2,
"Vinm": Rational(9,10) - V_id/2},
var=V_id,
)
Functions
|
Symbolic headroom: input range that keeps every MOSFET in saturation. |
Classes
|
Symbolic outcome of a |
- class sycan.headroom.HeadroomResult(var, node_voltages, predicates, boundaries, interval, binding)[source]¶
Bases:
objectSymbolic outcome of a
solve_headroom()call.- Parameters:
var (Symbol)
node_voltages (dict[Symbol, Expr])
predicates (dict[str, list[Expr]])
boundaries (list[tuple[str, str, Expr]])
interval (tuple[Expr, Expr] | None)
binding (dict[str, str | None])
- var¶
The input variable the analysis sweeps.
- Type:
sympy.core.symbol.Symbol
- node_voltages¶
Symbolic operating-point map
{V(node): expr_in_var}, solved with each MOSFET pinned to its saturation drain current.- Type:
dict[sympy.core.symbol.Symbol, sympy.core.expr.Expr]
- predicates¶
{device_name: [c1, c2]}— for each MOSFET, the two saturation predicates (must be> 0forc1and>= 0forc2). Substitute concrete values to check margin.- Type:
dict[str, list[sympy.core.expr.Expr]]
- boundaries¶
[(device, kind, var_value), ...]— every place a predicate crosses zero, in symbolic form, with the device name and which predicate ("threshold"forc1,"overdrive"forc2).- Type:
list[tuple[str, str, sympy.core.expr.Expr]]
- interval¶
(low, high)— symbolic edges of the widest contiguous all-saturation interval, orNoneif no interval is consistent (e.g. a fixed bias kills one device regardless of the input).- Type:
tuple[sympy.core.expr.Expr, sympy.core.expr.Expr] | None
- binding¶
{"low": device, "high": device}— the device that sets each interval edge.- Type:
dict[str, str | None]
- var: Symbol¶
- node_voltages: dict[Symbol, Expr]¶
- predicates: dict[str, list[Expr]]¶
- boundaries: list[tuple[str, str, Expr]]¶
- interval: tuple[Expr, Expr] | None¶
- binding: dict[str, str | None]¶
- sycan.headroom.solve_headroom(circuit, sources, var=None, *, op_point=None, simplify=True)[source]¶
Symbolic headroom: input range that keeps every MOSFET in saturation.
- Parameters:
circuit (Circuit) – A
Circuitcontaining one or more MOSFETs. Sub-threshold-only devices and BJTs are ignored.sources (str | Mapping[str, Expr]) – Either the name of one independent voltage / current source — in which case the source’s value is replaced by
var(or by a freshly minted symbol named after the source) — or a{name: sympy_expression}mapping. Every expression in the mapping must depend on the same singlevar(other free symbols are taken as circuit parameters).var (Symbol | None) – The swept input variable. Optional for the single-source form; for the dict form, it is auto-detected as the unique symbol common to all expressions, or pass it explicitly to override.
op_point (Mapping[Symbol, Expr] | None) – Optional pre-computed operating-point map
{V(node): expr}. When provided, the analysis skipscas.solveof the saturation-form DC system and uses these values directly to substitute into the predicates. Useful for circuits whose KCLscas.solvecan’t close in one shot (5T-OTA with a current mirror): derive the operating point yourself with sequential elimination and feed it in. Only the node voltages your predicates actually reference need to be present.simplify (bool) – Run
cas.simplifyon the operating-point voltages and the saturation predicates before returning them.
- Returns:
See the dataclass for fields. The most useful one is
interval— a(low, high)pair of sympy expressions in the swept variable’s coefficients.- Return type: