Analytical Sizing Flow¶
Level-1 square-law equations and their step-by-step derivation.
This page documents the complete analytical flow used by
size_circuit() to size a two-stage
Miller-compensated single-ended op-amp. It covers the device model,
the five-step constraint derivation order, the CP-SAT integer
linearisation, and the post-sizing performance metric evaluation —
with a concrete numerical walkthrough using the values from
examples/two_stage_se_specs/spec_generic.yaml.
Scope and circuit topology¶
The sizer targets every op-amp topology template the synthesizer produces.
This page documents the Level-1 (Shichman-Hodges) analytical flow — square-law
drain current in saturation, constant channel-length modulation coefficient λ, no
short-channel or velocity-saturation effects. This flow is selected for the
card-less generic technology. Deep-submicron PTM nodes and foundry PDKs
(e.g. GF180MCU) use the gm/Id pipeline instead (LUT-driven, deterministic
geometry — see gm/Id model (PTM nodes) below); a PTM/SPICE-model node without a
gm/Id LUT raises UnsupportedTechError. For those technologies the reported
performance numbers are measured in ngspice rather than computed from the
Level-1 equations below (see Post-sizing performance metrics).
ibias ──► [Tail] ──► [Input pair + Load] ──► Rout1 ──┬──► [2nd stage] ──► out
│ ▲
Cc ─────────┘
Stage 1 dominant pole: ωp1 = 1/(Rout1·Cc)
Non-dominant pole (neglected): ωp2 ≈ gm2/CL
The five-step sizing flow in compute_requirements()
derives performance requirements in this order:
Step |
From spec |
Derives |
|---|---|---|
1 |
|
\(g_{m1}\) lower bound — only constraint independent of \(C_c\) |
2 |
|
\(C_c\) initial upper bound |
3 |
|
\(g_{m1}\) from \(C_c\); refines \(C_c\) if CMRR pushed \(g_{m1}\) up |
4 |
|
\(g_{m2}\) from the remaining gain budget after \(g_{m1}\) is fixed |
5 |
|
\(g_{m2}\) from worst-case (grid-rounded) \(g_{m1}\) — often the binding constraint |
Device model¶
Three Level-1 equations underpin the constraints below. All are implemented
in circuitgenome.sizer.shared.equations.
Transconductance¶
gm() — mu_cox in A/V², w_um
and l_um in µm, ids_a in A.
\(g_m\) grows as the square root of gate area at a fixed \(I_{DS}\): doubling \(W\) (at fixed \(L\)) gives \(\sqrt{2}\,\times\,g_m\), not \(2\times\). The \(g_m / I_{DS} = \sqrt{2\,\mu C_{ox} / (I_{DS}\,W/L)}\) ratio falls as current density increases — a key efficiency trade-off in low-power design.
Output conductance¶
gd() — λ (lam) in V-1.
λ is treated as a constant, independent of \(V_{DS}\). In real silicon λ falls with channel length and rises with \(|V_{DS}|\), so the \(R_{out}\) computed here is a first-order estimate. Use cascode stages or SPICE simulation when accurate \(R_{out}\) is critical.
Stage output resistance¶
rout() — parallel combination of the
upper (load) and lower (drive) transistors’ output conductances.
For a PMOS input pair with NMOS active load (generic tech, λp=0.05, λn=0.04) biased at \(I_{DS}\) = 5 µA each:
gm/Id model (PTM nodes)¶
The single fitted \((\mu C_{ox}, V_{th}, \lambda)\) triple above cannot
capture moderate/weak inversion, velocity saturation, or
\(\lambda \propto 1/L\) — so on deep-submicron PTM nodes the Level-1
predictions diverge sharply from SPICE. A technology that carries a
gm/Id lookup table (the gmid_lut field of
TechParams, e.g. ptm45) instead drives
sizing from a SPICE-characterized table indexed by \((g_m/I_{DS}, L)\).
In this path the small-signal primitives \(g_m\), \(g_{ds}\), \(V_{DS,sat}\) and \(V_{GS}\) are read from the table — most importantly \(g_{ds} = g_m / (g_m/g_{ds})\), with the intrinsic-gain ratio \(g_m/g_{ds}\) a genuine function of \(L\) rather than a constant \(\lambda\). Because \(I_{DS}\) is fixed by KCL, a target \(g_m\) fixes \(g_m/I_{DS}\) and the table yields \(I_{DS}/W \rightarrow W\) directly, so geometry is computed, not searched (no CP-SAT): a deterministic forward pass with grid snapping, matched-pair symmetry, and exact current-mirror ratios.
The model is selected per-tech by
build_device_model()
(Level1Model vs
GmIdModel); the table interface is
GmIdLut and the geometry pass is
assign_geometry_gmid(). The Level-1
flow described below is unchanged for the card-less generic tech.
Operating-point assignment¶
Reference: assign_ids()
\(I_{DS}\) for every transistor is determined by KCL and the external bias current before any W/L is chosen. This is the critical insight that keeps the sizing problem tractable: because \(I_{DS}\) is a known constant for each device, the nonlinear \(g_m \geq g_{m,req}\) constraint linearises in W and L (see CP-SAT integer linearisation).
Slot |
\(I_{DS}\) per transistor |
Rationale |
|---|---|---|
|
\(I_{bias} / n\) (n = number of same-type devices) |
Tail current splits equally across the two input transistors |
|
\(I_{bias} / n\) |
Mirror-image current from the input pair |
|
\(I_{bias}\) |
Carries the full tail current |
|
\(I_{bias}\) |
Conservative estimate (one bias leg) |
|
\(I_{bias} \times \text{ratio}\) |
Set by |
Five-step constraint derivation¶
Reference: compute_requirements()
Each step below follows the same structure: equation → derivation →
intuition → why this position in the ordering → numerical example using
ibias = 10 µA, cl = 20 pF (examples/two_stage_se_specs/spec_generic.yaml).
Step 1 — CMRR sets the gm1 floor¶
Inverting for the minimum \(g_{m1}\):
where \(\text{CMRR}_{\text{lin}} = 10^{\,\text{CMRR}_{\text{dB}}/20}\).
Intuition: The tail current source has finite output resistance \(1/g_{d,\text{tail}}\). A common-mode input shift modulates the tail node, injecting a differential-mode error current \(g_{d,\text{tail}}\,v_{cm}\) into the input pair. The CMRR measures how well \(g_{m1}\) suppresses this error. Higher \(I_{bias}\) or higher λ degrades \(g_{d,\text{tail}}\) and demands a larger \(g_{m1}\) floor.
Why first: CMRR is the only performance spec that is independent of \(C_c\). Setting it first lets Step 3 use the correct \(g_{m1}\) floor when refining \(C_c\) from the GBW constraint. If CMRR were evaluated after \(C_c\), the GBW step might fix \(g_{m1}\) too low to satisfy CMRR, forcing a retroactive change to \(C_c\) that could violate SR.
Implementing function: cmrr_db()
Numerical example (CMRR = 50 dB, not specified in the example spec but shown here for illustration): λtail= 0.05 V-1, \(I_{bias}\) = 10 µA:
Note
examples/two_stage_se_specs/spec_generic.yaml does not specify cmrr_min_db
because CMRR = 50 dB + GBW = 2.5 MHz + SR = 3.5 V/µs are mutually
exclusive at \(I_{bias}\) = 10 µA (see Spec compatibility).
The bound above is shown purely to illustrate the formula.
Step 2 — SR sets the Cc ceiling¶
\(C_c\) is then clamped to [cap.min, cap.max] from the technology config.
Intuition: During large-signal slewing, the input stage saturates and the tail current is the only drive available to charge or discharge \(C_c\). A larger \(C_c\) slows SR linearly.
Why second: SR provides the upper bound on \(C_c\). All subsequent steps can only grow \(C_c\) (never shrink it), so setting the SR ceiling first guarantees SR is never violated by later adjustments.
Implementing function: slew_rate_vps()
Numerical example — \(I_{bias}\) = 10 µA, SRmin = 3.5 V/µs:
Step 3 — GBW fixes gm1, refines Cc¶
If Step 1 (CMRR) already set a higher \(g_{m1}\) floor, \(C_c\) must grow to maintain GBW:
re-clamped to cap.max.
Intuition: In a two-stage Miller amplifier the dominant pole sits at \(1/(R_{out1}\,C_c)\) and the unity-gain frequency (where the gain magnitude first crosses 0 dB) is \(g_{m1}/(2\pi C_c)\). GBW is essentially proportional to \(g_{m1}\) at a fixed \(C_c\).
Why after SR: The GBW equation can be satisfied by any (\(g_{m1}\), \(C_c\)) pair on the line \(g_{m1} = 2\pi \cdot GBW \cdot C_c\). Without the SR constraint the sizer would pick the smallest \(C_c\) (cheapest in power), but that would violate SR. Step 2 pins \(C_c\) from above; Step 3 reads that value to derive \(g_{m1}\).
Implementing function: unity_gain_bw()
Numerical example — GBWmin = 2.5 MHz, \(C_c\) = 2.857 pF from Step 2:
Step 4 — Gain fixes gm2¶
The two-stage open-loop DC gain is:
With \(g_{m1}\) now known, solve for the minimum \(g_{m2}\):
where \(R_{out1} = 1/(g_{d,ip} + g_{d,ld})\) and
\(R_{out2} = 1/(g_{d,n2} + g_{d,p2})\) are computed from
\(g_d = \lambda\,|I_{DS}|\) at the bias point
(see rout()).
Intuition: The total gain is the product of two stage gains. Stage 1’s gain is \(g_{m1}\,R_{out1}\) — fixed once \(g_{m1}\) is known and the operating point is set. Stage 2’s gain budget fills the remainder.
Why gm2 and not gm1: \(g_{m1}\) is already locked by GBW + SR. If the gain spec were met by inflating \(g_{m1}\) instead, the GBW equation would require a proportionally larger \(C_c\) (since \(C_c = g_{m1}/(2\pi\,GBW)\)), which violates the SR ceiling from Step 2. Assigning the gain responsibility to \(g_{m2}\) decouples gain from the SR-limited \(C_c\).
Implementing function: open_loop_gain_db()
Numerical example — gainmin = 80 dB, \(g_{m1}\) = 44.88 µA/V:
With the generic tech and second_stage_current_ratio = 2.5 → \(I_{DS,2}\) = 25 µA:
Step 5 — PM (worst-case gm1) fixes gm2¶
The dominant-pole phase margin approximation (non-dominant mirror pole neglected):
Solving for the minimum \(g_{m2}\):
The solver minimises gate area (width), so it will pick the smallest integer \(W\) that satisfies the \(g_{m1}\) constraint — exactly \(W_{\text{ceil}}\):
where \(W_{\text{ceil}}\) and \(L_{\min,\text{int}}\) are in integer grid-step units.
Intuition: The CP-SAT solver picks the minimum \(W\) satisfying the \(g_{m1} \geq g_{m1,\text{req}}\) constraint, which is \(W_{\text{ceil}}\). Because of the square-root relationship between \(g_m\) and \(W\), ceiling-rounding \(W\) raises the actual \(g_{m1}\) above the required value. A larger \(g_{m1}\) shifts the non-dominant pole frequency upward relative to \(g_{m2}\), degrading PM. Sizing \(g_{m2}\) against \(g_{m1,\text{worst}}\) — the actual post-rounding value — ensures PM is satisfied on the integer grid.
Implementing function: phase_margin_two_stage_deg()
Numerical example — PMmin = 60°, \(C_L\) = 20 pF:
Input pair is PMOS (µpCox = 90 µA/V²), \(I_{DS,ip}\) = 5 µA, \(L_{\min,\text{int}}\) = 1 step (1 µm):
Final \(g_{m2}\) = max(226, 630) = 630 µA/V ← PM is the binding constraint.
Note
The non-dominant mirror pole at \(\approx g_{m,\text{load}}/C_{gs,\text{load}}\) is ignored by this formula. The actual PM is lower than the formula predicts when \(C_c\) is small relative to \(C_{gs,\text{load}}\). Verify with SPICE AC simulation before tape-out.
CP-SAT integer linearisation¶
Reference: build_model()
W and L for each transistor are integer decision variables in units of the technology grid step. The key observation is that once \(I_{DS}\) is fixed (Step 3 of the pipeline, before the solver runs), every constraint becomes linear in the integer W and L variables.
gm lower-bound constraint¶
Starting from \(g_m \geq g_{m,\text{req}}\):
Squaring both sides (both positive):
In code (constraints.py):
lhs = round(2.0 * params.mu_cox * abs(ids_a) * _SCALE) # coefficient of W
rhs = round(gm_req ** 2 * _SCALE) # coefficient of L
model.add(lhs * W[ref] >= rhs * L[ref])
VDS,sat upper-bound constraint¶
The overdrive constraint \(V_{DS,sat} \leq V_{DS,sat,\max}\) likewise linearises:
Integer coefficient scaling¶
Raw floating-point coefficients (e.g. \(\mu C_{ox} \approx 90\;\mu\text{A/V}^2\),
\(I_{DS} \approx 5\;\mu\text{A}\)) would give CP-SAT coefficients of order
\(10^{-9}\), which must be integers. All coefficients are multiplied by
_SCALE = 1012 before rounding:
Objective and symmetry¶
Objective: minimise \(\sum W_i\) across all transistors — a proxy for total gate area and quiescent power.
Symmetry: matched pairs within the
input_pair,load, andtail_currentslots are constrained to equal \(W\) and equal \(L\) (model.add(W[i] == W[j])).Branching heuristic:
bias_gentransistors are prioritised first, then all others, usingSELECT_MIN_VALUEto bias towards small W/L solutions early in the search tree.
Post-sizing performance metrics¶
After the solver returns W/L values, _evaluate_metrics()
computes all performance metrics and their margins against spec. The
implementing function for each is in circuitgenome.sizer.shared.equations.
These are analytical (model-based) estimates; the gm/Id pipeline computes the
same table from LUT-accurate small-signal parameters. For PTM / foundry-PDK techs
the CLI does not display these numbers — it reports ngspice-measured metrics
instead (see Feasibility verdict and SPICE metrics (PTM / foundry PDKs)).
Metric |
Equation |
Function |
Margin sign |
|---|---|---|---|
Open-loop gain |
\(A_0 = \prod_j g_{m,j}\,R_{out,j}\), converted to dB |
actual − spec |
|
GBW |
\(GBW = g_{m1}\,/\,(2\pi C_c)\) |
actual − spec |
|
Phase margin |
\(PM = 90° - \arctan(g_{m1}\,C_L\,/\,(g_{m2}\,C_c))\) |
actual − spec |
|
Slew rate |
\(SR = I_{bias}\,/\,C_c\) |
actual − spec |
|
CMRR |
\(CMRR = g_{m1}\,/\,(2\,g_{d,\text{tail}})\), dB |
actual − spec |
|
PSRR+ (approx) |
\(PSRR^+ \approx g_{m2}\,/\,g_{d,\text{bias}}\), dB |
actual − spec |
|
Quiescent power |
\(P = (V_{DD} - V_{SS})\sum|I_{supply}|\) |
spec − actual |
|
Output swing max |
\(V_{out,\max} = V_{DD} - V_{DS,sat}(\text{PMOS}_2)\) |
(inline in |
actual − spec |
Output swing min |
\(V_{out,\min} = V_{SS} + V_{DS,sat}(\text{NMOS}_2)\) |
(inline in |
spec − actual |
A positive margin value means the spec is met with headroom; a negative margin means the spec is violated (only possible if the spec was not enforced by a CP-SAT constraint, e.g. PSRR, power, swing).
Feasibility verdict and SPICE metrics (PTM / foundry PDKs)¶
For a technology with a real device model (a PTM BSIM4 card, or a foundry PDK such as GF180MCU), the analytical estimate above would mismatch the device, so the CLI grounds its report in ngspice instead:
Feasibility verdict. A SPICE DC operating-point check (
check_bias_soundness()) classifies the design as:INFEASIBLE — the bias point cannot be established (the feedback operating point rails, or a device is starved / pushed into triode); performance is not reported.
MARGINAL — biases correctly but misses one or more specs.
FEASIBLE — biases correctly and meets every measured spec.
Measured metrics. When feasible, the CLI measures performance in ngspice (
simulate_metrics()): open-loop gain, GBW, phase margin, slew rate (min of the rising and falling edges), quiescent power, CMRR, PSRR+, and output swing. A metric ngspice cannot extract is shown asn/a(no analytical fallback), and ngspice is required — the command errors if it is not installed.Corner sweep (foundry PDKs). A PDK tech (
spice_lib, e.g. GF180MCU) is sized at its nominal corner, then the sized design is re-measured across the configured process corners ({typical, ss, ff, sf, fs}) and printed as a corner-verification table for worst-case visibility. The PTM nodes carry a single model card and report the nominal corner only.
Assumptions and where they break down¶
Warning
Level-1 (square-law) model. The \(g_m = \sqrt{2\,\mu C_{ox}\,(W/L)\,I_{DS}}\) formula is accurate for transistors well into saturation with \(L \geq\) approximately 0.18–0.25 µm (as in the generic tech config). For deep-submicron nodes (28 nm, 7 nm), velocity saturation and other short-channel effects dominate; the Level-1 model will significantly over-estimate \(g_m\) at a given \(W/L\) and \(I_{DS}\).
Warning
Dominant-pole phase margin. The formula \(PM \approx 90° - \arctan(g_{m1}\,C_L / (g_{m2}\,C_c))\) ignores the non-dominant mirror pole at \(\approx g_{m,\text{load}}/C_{gs,\text{load}}\). When \(C_c\) is small (aggressive SR spec) the mirror pole can push the actual PM several degrees below the formula’s prediction. Always verify PM with a SPICE AC sweep before committing to a topology.
Note
Constant λ. \(g_d = \lambda\,|I_{DS}|\) treats λ as VDS-independent. In practice λ ∝ 1/L and increases with \(|V_{DS}|\), so \(R_{out}\) computed here is an approximation. For designs where DC gain is critical, consider cascode stages and use simulation to characterise \(R_{out}\) accurately.
Note
PSRR approximation. psrr_db_approx()
returns a first-order upper bound on PSRR+. Full PSRR — including
the path through the compensation capacitor — requires an AC simulation.
Note
No device mismatch. The solver assumes perfectly matched differential pairs. Offset voltage and CMRR degradation from mismatch must be estimated separately (e.g. Pelgrom’s model).
Spec compatibility¶
CMRR, GBW, and SR all depend on the same three variables (\(I_{bias}\), \(C_c\), \(g_{m1}\)) and are mutually exclusive when the bias current is small.
The conflict chain:
If the GBW-required \(C_c\) exceeds the SR-limited \(C_c\):
the specification set is physically infeasible at the given \(I_{bias}\).
The CP-SAT solver returns INFEASIBLE in this case.
Resolution strategies:
Remove one of the three conflicting specs (CMRR, GBW, or SR).
Increase \(I_{bias}\) — this relaxes the SR ceiling on \(C_c\) without affecting the CMRR-derived \(g_{m1}\) floor.
Relax the CMRR target — this lowers \(g_{m1,\min}\), reducing the \(C_c\) required to maintain GBW.
Three-Stage NMC / RNMC¶
The sizer also supports all four three-stage topologies
(three_stage_opamp_nmc_single_ended, three_stage_opamp_rnmc_single_ended,
three_stage_opamp_nmc_fully_differential,
three_stage_opamp_rnmc_fully_differential). The same conservative
equations are applied to both NMC and RNMC.
Circuit topology¶
ibias ──► [Tail] ──► [Input pair + Load] ──► Rout1 ──► [2nd stage] ──► Rout2 ──► [3rd stage] ──► out
▲ ▲
│◄─────────── Cc1 ─────────┼──────────────────────────────┘
│ │◄──── Cc2 ──────────────────┘
NMC: \(C_{c1}\) closes the outer loop (stage-3 output → stage-1 output); \(C_{c2}\) closes the inner loop (stage-3 output → stage-2 output).
RNMC: \(C_{c1}\) closes the inner loop (stage-3 output → stage-2 output); \(C_{c2}\) closes the reversed outer loop (stage-2 output → stage-1 output). The sizer uses the same conservative equations for both schemes.
Design variables¶
Variable |
Source |
|---|---|
\(C_{c1}\) (outer) |
\(\min(I_{bias}/SR,\; g_{m1}/(2\pi \cdot GBW))\) |
\(C_{c2}\) (inner) |
\(C_{c1}/4\) (Eschauzier–Huijsing heuristic) |
\(g_{m1}\) |
CMRR + GBW (same as two-stage) |
\(g_{m2}\) |
Inner-pole PM condition (see below) |
\(g_{m3}\) |
Gain + outer-pole PM condition (see below) |
Phase margin derivation¶
With two non-dominant poles, the phase margin is:
where \(\omega_t = g_{m1}/C_{c1}\) is the unity-gain frequency
(implemented in phase_margin_three_stage_deg()).
For a target \(\text{PM}_{\min}\), the sizer splits the phase budget equally between the two poles — each is allowed to contribute at most \((90^{\circ} - \text{PM}_{\min})/2\) of lag:
This leads to two independent lower bounds:
For \(\text{PM}_{\min} = 60^{\circ}\): \(\theta = 15^{\circ}\), \(\tan\theta \approx 0.268\), so each non-dominant pole must be placed at \(\approx 3.73 \times \omega_t\).
Note
These are sufficient conditions — both poles are individually bounded, so the actual PM will be ≥ \(\text{PM}_{\min}\) even if one pole is at its minimum.
Gain requirement¶
Once \(g_{m2}\) is determined from the inner-pole condition, the three-stage gain formula is used to derive \(g_{m3}\):
The sizer takes \(g_{m3,\text{req}} = \max(g_{m3,\text{gain}},\; g_{m3,\text{PM}})\).
Numerical example¶
Specification: \(I_{bias}=10\,\mu\text{A}\), \(C_L=20\,\text{pF}\), \(SR=3.5\,\text{V/µs}\), \(GBW=2.5\,\text{MHz}\), \(\text{PM}\geq 60^{\circ}\), \(A_0 \geq 100\,\text{dB}\), \(I_{D2}/I_{bias}=2.5\), \(I_{D3}/I_{bias}=5\).
Cc1 from SR: \(C_{c1} = 10\,\mu\text{A} / 3.5\,\text{V/µs} = 2.857\,\text{pF}\)
Cc2: \(C_{c2} = 2.857/4 = 0.714\,\text{pF}\)
gm1 from GBW: \(g_{m1} = 2\pi \times 2.5\,\text{MHz} \times 2.857\,\text{pF} = 44.88\,\mu\text{A/V}\)
θ for PM=60°: \(\theta = 15^{\circ}\), \(\tan\theta = 0.2679\)
gm2 (inner pole): \(g_{m2} \geq 44.88 \times 0.714/(2.857 \times 0.2679) = 41.9\,\mu\text{A/V}\)
gm3 (output pole, PM): \(g_{m3} \geq 44.88 \times 20/(2.857 \times 0.2679) = 1175\,\mu\text{A/V}\)
gm3 (gain, 100 dB → 10⁵): Using typical \(R_{out}\sim100\,\text{k}\Omega\) per stage: \(g_{m3,\text{gain}} = 10^5/(44.88 \times 10^5 \times 41.9 \times 10^5 \times 10^5) \ll g_{m3,\text{PM}}\) → PM dominates.
Binding constraint: \(g_{m3,\text{req}} = 1175\,\mu\text{A/V}\) (output pole PM)
Note
The output-stage \(g_{m3}\) requirement for three-stage is much larger than the two-stage \(g_{m2}\) at the same spec because the phase budget is split — each non-dominant pole must be ~3.7× farther than the single pole in the two-stage case.
Fully-differential three-stage¶
For FD topologies, the sizer applies the same equations to each output path
independently (using second_stage_p/third_stage_p as the representative
path). Power is computed as:
Cross-slot symmetry constraints (second_stage_p ↔ second_stage_n
and third_stage_p ↔ third_stage_n) force identical W/L on both
output paths, ensuring balanced differential swing.
Operating-point mapping for three-stage¶
Slot |
\(I_{DS}\) assignment |
|---|---|
|
\(I_{bias}/2\) per transistor |
|
\(I_{bias}/2\) per transistor |
|
\(I_{bias}\) |
|
\(I_{D2} = \text{ratio}_2 \times I_{bias}\) |
|
\(I_{D3} = \text{ratio}_3 \times I_{bias}\) |
|
capacitors — no \(I_{DS}\) |
|
\(I_{bias}\) (conservative) |