Source code for circuitgenome.sizer.shared.equations

"""
Level-1 MOSFET equations (Shichman-Hodges) and op-amp performance formulas.

All functions operate on SI units unless the name suffix states otherwise.
Consistent units: currents in A, voltages in V, transconductances in A/V (S),
dimensions (W, L) in µm (dimensionless ratio W/L is used where needed),
frequencies in Hz, power in W.
"""
from __future__ import annotations

import math


# ---------------------------------------------------------------------------
# Level-1 MOSFET small-signal parameters
# ---------------------------------------------------------------------------

[docs] def gm(mu_cox: float, w_um: float, l_um: float, ids_a: float) -> float: r"""Transconductance gm in A/V. gm = √(2·µCox·(W/L)·\|IDS\|) :param mu_cox: Process transconductance µCox in A/V². :param w_um: Gate width in µm. :param l_um: Gate length in µm. :param ids_a: Drain-source current in A (sign ignored). """ return math.sqrt(2.0 * mu_cox * (w_um / l_um) * abs(ids_a))
# Maximum transconductance efficiency gm/IDS in 1/V (weak-inversion ceiling). # gm is physically bounded by gm ≤ IDS/(n·φt); with n ≈ 1.5 and φt ≈ 0.0259 V at # 300 K this is ≈ 25.8 /V (matches measured gm/Id ≈ 25 for the PTM devices). The # square-law gm formula ignores this ceiling and would otherwise let the sizer # promise a gm the device can only reach by sliding into weak inversion. _GM_OVER_ID_MAX = 25.0
[docs] def gm_ceiling(ids_a: float) -> float: """Physical upper bound on gm in A/V (weak-inversion limit).""" return _GM_OVER_ID_MAX * abs(ids_a)
[docs] def gd(lam: float, ids_a: float) -> float: r"""Output conductance gd in A/V. gd = λ·\|IDS\| :param lam: Channel-length modulation coefficient λ in 1/V (positive). :param ids_a: Drain-source current in A. """ return lam * abs(ids_a)
[docs] def rout(gd_top: float, gd_bot: float) -> float: """Stage output resistance in Ω. Rout = 1 / (gd_top + gd_bot) :param gd_top: Output conductance of the upper (load) transistor in A/V. :param gd_bot: Output conductance of the lower (drive) transistor in A/V. """ total = gd_top + gd_bot return 1.0 / total if total > 0.0 else float("inf")
[docs] def vgs_from_ids( mu_cox: float, w_um: float, l_um: float, ids_a: float, vth: float ) -> float: """Gate-source voltage in V (saturation, λ=0 approximation). Solves IDS = (µCox/2)·(W/L)·(VGS−Vth)² for VGS. Returns a signed value: positive for NMOS, negative for PMOS. :param vth: Threshold voltage in V (negative for PMOS). """ overdrive = math.sqrt(2.0 * abs(ids_a) * l_um / (mu_cox * w_um)) return math.copysign(abs(vth) + overdrive, vth)
[docs] def vds_sat(mu_cox: float, w_um: float, l_um: float, ids_a: float) -> float: r"""Minimum \|VDS\| for saturation in V. VDS_sat = VGS − Vth = √(2·\|IDS\|·L / (µCox·W)) :returns: Always positive. """ return math.sqrt(2.0 * abs(ids_a) * l_um / (mu_cox * w_um))
# --------------------------------------------------------------------------- # Op-amp performance metrics # ---------------------------------------------------------------------------
[docs] def open_loop_gain_db(stage_gains: list[float]) -> float: """Total open-loop DC gain in dB. A0 = Π(gm_j · Rout_j), converted to dB. :param stage_gains: List of per-stage voltage gains (dimensionless). """ product = math.prod(stage_gains) return 20.0 * math.log10(abs(product)) if product != 0.0 else -math.inf
[docs] def unity_gain_bw(gm1_a_v: float, cc_f: float) -> float: """Unity-gain bandwidth (GBW) in Hz. GBW = gm1 / (2π·Cc) :param gm1_a_v: Input-pair transconductance in A/V. :param cc_f: Compensation capacitor in F. """ return gm1_a_v / (2.0 * math.pi * cc_f)
[docs] def phase_margin_two_stage_deg( gm1: float, gm2: float, cc_f: float, cl_f: float ) -> float: """Phase margin in degrees (dominant-pole approximation). PM ≈ 90° − arctan(gm1·CL / (gm2·Cc)) Valid when the dominant pole is at 1/(Rout1·Cc) and the non-dominant pole is at gm2/CL (internal mirror pole neglected). :param gm1: Input-pair gm in A/V. :param gm2: Second-stage signal transistor gm in A/V. :param cc_f: Compensation capacitor in F. :param cl_f: Output load capacitance in F. """ return 90.0 - math.degrees(math.atan(gm1 * cl_f / (gm2 * cc_f)))
[docs] def phase_margin_three_stage_deg( gm1: float, gm2: float, gm3: float, cc1_f: float, cc2_f: float, cl_f: float, ) -> float: """Phase margin in degrees for three-stage NMC/RNMC (two non-dominant poles). PM ≈ 90° − arctan(ωt·Cc2/gm2) − arctan(ωt·CL/gm3) where ωt = gm1/Cc1. :param gm1: Input-pair transconductance in A/V. :param gm2: Second-stage signal transistor gm in A/V. :param gm3: Third-stage signal transistor gm in A/V. :param cc1_f: Outer (primary) compensation capacitor in F. :param cc2_f: Inner compensation capacitor in F (Cc2 = Cc1/4 by default). :param cl_f: Output load capacitance in F. """ wt = gm1 / cc1_f lag2 = math.degrees(math.atan(wt * cc2_f / gm2)) lag3 = math.degrees(math.atan(wt * cl_f / gm3)) return 90.0 - lag2 - lag3
[docs] def slew_rate_vps(ibias_a: float, cc_f: float) -> float: """Slew rate in V/s. SR = IBias / Cc (limited by tail-current charging/discharging Cc) :param ibias_a: Tail bias current in A. :param cc_f: Compensation capacitor in F. """ return ibias_a / cc_f
[docs] def quiescent_power(vdd: float, vss: float, supply_currents_a: list[float]) -> float: """Total quiescent power in W. P = (VDD − VSS) · Σ|IDS_supply| :param supply_currents_a: Currents drawn from the positive supply (before summing, absolute values are taken). """ return (vdd - vss) * sum(abs(i) for i in supply_currents_a)
[docs] def cmrr_db(gm1: float, gd_tail: float) -> float: """Common-mode rejection ratio in dB (first-order approximation). CMRR ≈ gm1 / (2·gd_tail) :param gm1: Input-pair transconductance in A/V. :param gd_tail: Output conductance of the tail current source in A/V. """ if gd_tail == 0.0: return math.inf return 20.0 * math.log10(gm1 / (2.0 * gd_tail))
[docs] def psrr_db_approx(gm2: float, gd_bias: float) -> float: """Positive-supply PSRR rough approximation in dB. PSRR+ ≈ gm2 / gd_bias_mirror This is a first-order estimate valid for simple two-stage opamps; accurate PSRR requires simulation. :param gm2: Second-stage signal transistor gm in A/V. :param gd_bias: Output conductance of the second-stage bias transistor in A/V. """ if gd_bias == 0.0: return math.inf return 20.0 * math.log10(gm2 / gd_bias)