# Sizing for a 2 Stage OTA
from pygmid import Lookup as lk
import numpy as np
lv_nmos = lk('sg13_lv_nmos.mat')
lv_pmos = lk('sg13_lv_pmos.mat')
# list of parameters: VGS, VDS, VSB, L, W, NFING, ID, VT, GM, GMB, GDS, CGG, CGB, CGD, CGS, CDD, CSS, STH, SFL
# if not specified, minimum L, VDS=max(vgs)/2=0.9 and VSB=0 are used In [1]:
In [2]:
# define the given parameters as taken from the specification table or inital guesses
c_load = 1e-12
#c_load = 1e-12
gm_id_m12 = 11
gm_id_m34 = 7
gm_id_m5 = 10
gm_id_m678 = 6
l_12 = 2
l_34 = 2
l_5 = 2
l_678 = 2
f_bw = 10e6 # -3dB bandwidth of the voltage buffer
i_total_limit = 30e-6
i_bias_in = 20e-6
output_voltage = 1.3
vin_min = 0.7
vin_max = 0.9
vdd_min = 1.45
vdd_max = 1.55
gain = 60In [3]:
c_m_min = 0.22*c_load
# Industrial scaling multiplier (6.6x the lower bound) to swallow up local device parasitics
c_m = 0.5*c_load
#c_m = 150e-16
#c_m = c_m_minIn [4]:
print('\n--- Step 2: First Stage Core Sizing ---')
gm_m12_ideal = 3*(2 * np.pi * f_bw) * c_m
id_m12_ideal = gm_m12_ideal / gm_id_m12
i_tail_ideal = 2 * id_m12_ideal
# Snapping tail bias current parameter smoothly onto physical layout grid (0.5 µA blocks)
i_tail = max(round(i_tail_ideal / 1e-6 * 2) / 2 * 1e-6, 0.5e-6)
id_m12 = i_tail / 2 # Re-establish physical branch balance post-quantization
gm_m12 = gm_id_m12 * id_m12 # Re-evaluate true manufactured transconductance from grid current
print(f'Required Analytical gm12 = {gm_m12_ideal*1e3:.5f} mS')
print(f'Grid Re-aligned I_tail = {i_tail/1e-6:.2f} µA')
print(f'True Manufactured gm12 = {gm_m12*1e3:.5f} mS')
--- Step 2: First Stage Core Sizing ---
Required Analytical gm12 = 0.09425 mS
Grid Re-aligned I_tail = 17.00 µA
True Manufactured gm12 = 0.09350 mS
In [5]:
print('\n--- Step 3: Second Stage Stability-Driven Constraints ---')
# Image Sizing Rule:
gm_m5_req = 2.2*f_bw*2*np.pi*c_load
print(f'Required Minimum Stage 2 transconductance gm5 = {gm_m5_req*1e3:.5f} mS')
id_m5_required = gm_m5_req / gm_id_m5
print(f'Analytically Required Stage 2 Bias Current = {id_m5_required/1e-6:.3f} µA')
--- Step 3: Second Stage Stability-Driven Constraints ---
Required Minimum Stage 2 transconductance gm5 = 0.13823 mS
Analytically Required Stage 2 Bias Current = 13.823 µA
In [6]:
# we calculate the first stage dc gain
gm_gds_m12 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0)
gm_gds_m34 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0)
gds_m12 = gm_m12 / gm_gds_m12
gm_m34 = gm_id_m34 * i_tail/2
gds_m34 = gm_m34 / gm_gds_m34
a0 = gm_m12 / (gds_m12 + gds_m34)
print('First stage gain a0 =', round(20*np.log10(a0), 1), 'dB')First stage gain a0 = 31.6 dB
In [7]:
# we can now look up the VGS of the MOSFET
vgs_m12 = lv_nmos.look_upVGS(GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0.0)
vgs_m34 = lv_pmos.look_upVGS(GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0.0)
vgs_m76 = lv_nmos.look_upVGS(GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0.0)
print('vgs_12 =', round(float(vgs_m12), 3), 'V')
print('vgs_34 =', round(float(vgs_m34), 3), 'V')
print('vgs_76 =', round(float(vgs_m76), 3), 'V')vgs_12 = 0.37 V
vgs_34 = 0.617 V
vgs_76 = 0.547 V
In [8]:
# calculate all widths
id_w_m12 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m12, L=l_12, VDS=vgs_m12, VSB=0)
w_12 = id_m12 / id_w_m12
w_12_round = max(round(w_12*2)/2, 0.5)
print('M1/2 W =', round(w_12, 2), 'um, rounded W =', w_12_round, 'um')
id_m34 = id_m12
id_w_m34 = lv_pmos.lookup('ID_W', GM_ID=gm_id_m34, L=l_34, VDS=vgs_m34, VSB=0)
w_34 = id_m34 / id_w_m34
w_34_round = max(round(w_34*2)/2, 0.5)
print('M3/4 W =', round(w_34, 2), 'um, rounded W =', w_34_round, 'um')
id_w_m7 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m678, L=l_678, VDS=vgs_m76, VSB=0)
w_7 = i_tail / id_w_m7
w_7_round = max(round(w_7*2)/2, 0.5)
print('M7 W =', round(w_7, 2), 'um, rounded W =', w_7_round, 'um')
w_6 = w_7_round * i_bias_in / i_tail
w_6_round = max(round(w_6*2)/2, 0.5)
print('M6 W =', round(w_6_round, 2), 'um')
# ==============================================================================
# Sizing for Second Stage: M8 and M5
# ==============================================================================
print('\n--- Second Stage Sizing ---')
# Step A: Size M8 (Stage 2 NMOS Load Mirror) using id_m5_required
id_w_m8 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0)
w_8 = id_m5_required / id_w_m8
w_8_round = max(round(w_8 * 2) / 2, 0.5)
print('M8 W =', round(w_8, 2), 'um, rounded W =', w_8_round, 'um')
# Step B: Realign true mirrored current to layout grid constraints
mirror_ratio_m8_m6 = w_8_round / w_6_round
id_m5 = i_bias_in * mirror_ratio_m8_m6
print(f'True Mirrored Stage 2 Current (id_m5) = {id_m5/1e-6:.3f} µA')
# Step C: Re-calculate actual physical gm5 and size M5 (PMOS Driver)
gm_m5 = gm_id_m5 * id_m5
id_w_m5 = lv_pmos.lookup('ID_W', GM_ID=gm_id_m5, L=l_5, VDS=0.75, VSB=0)
w_5 = id_m5 / id_w_m5
w_5_round = max(round(w_5 * 2) / 2, 0.5)
print('M5 W =', round(w_5, 2), 'um, rounded W =', w_5_round, 'um')
# Step D: Complete the flow with your Zero-Nulling Resistor value
rz = 2 / gm_m5
print(f'Textbook Zero-Nulling Resistor Rz = {rz/1e3:.2f} kΩ')M1/2 W = 2.7 um, rounded W = 2.5 um
M3/4 W = 3.55 um, rounded W = 3.5 um
M7 W = 1.25 um, rounded W = 1.5 um
M6 W = 2.0 um
--- Second Stage Sizing ---
M8 W = 1.01 um, rounded W = 1.0 um
True Mirrored Stage 2 Current (id_m5) = 10.000 µA
M5 W = 9.27 um, rounded W = 9.5 um
Textbook Zero-Nulling Resistor Rz = 20.00 kΩ
In [9]:
# ==============================================================================
# 5. Total Open-Loop DC Gain Evaluation
# ==============================================================================
print('\n--- Total DC Gain Evaluation ---')
# Second stage gain evaluation
# Extract the intrinsic gain of the PMOS Common-Source driver (M5)
gm_gds_m5 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m5, L=l_5, VDS=0.75, VSB=0)
gds_m5 = gm_m5 / gm_gds_m5
# Extract the output conductance density of the NMOS mirror load (M8)
gds_id_m8 = lv_nmos.lookup('GDS_ID', GM_ID=gm_id_m678, L=l_678, VDS=0.75, VSB=0)
gds_m8 = gds_id_m8 * id_m5
# Compute Stage 2 Gain
a1 = gm_m5 / (gds_m5 + gds_m8)
# Compute Total Open-Loop Gain
a0_total = a0 * a1
print(f'Stage 1 Open-Loop Gain (A1) = {20*np.log10(a0):.1f} dB')
print(f'Stage 2 Open-Loop Gain (A2) = {20*np.log10(a1):.1f} dB')
print(f'Total Open-Loop Gain (A0) = {20*np.log10(a0_total):.1f} dB')
gain_error = a0_total / (1 + a0_total)
print('voltage gain error =', round((gain_error-1)*100, 1), '%')
--- Total DC Gain Evaluation ---
Stage 1 Open-Loop Gain (A1) = 31.6 dB
Stage 2 Open-Loop Gain (A2) = 34.3 dB
Total Open-Loop Gain (A0) = 65.9 dB
voltage gain error = -0.1 %
In [10]:
# ==============================================================================
# 6. Node Parasitic Extraction, Actual UGBW, and Final Summary
# ==============================================================================
print('--- Node Parasitic Extraction & Actual UGBW ---')
# Look up frequency-dependent parasitic caps
gm_cgs_m12 = lv_nmos.lookup('GM_CGS', GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0)
gm_cdd_m12 = lv_nmos.lookup('GM_CDD', GM_ID=gm_id_m12, L=l_12, VDS=0.75, VSB=0)
gm_cdd_m34 = lv_pmos.lookup('GM_CDD', GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0)
# NEW: Look up the gate-capacitance of the M5 driver
gm_cgg_m5 = lv_pmos.lookup('GM_CGG', GM_ID=gm_id_m5, L=l_5, VDS=0.75, VSB=0)
gm_w_m34 = lv_pmos.lookup('GM_W', GM_ID=gm_id_m34, L=l_34, VDS=0.75, VSB=0)
gm_m34 = gm_w_m34 * w_34_round
# NEW: Add the M5 gate load to the total internal node parasitic calculation
c_para_internal = abs(gm_m12 / gm_cgs_m12) + abs(gm_m12 / gm_cdd_m12) + abs(gm_m34 / gm_cdd_m34) + abs(gm_m5 / gm_cgg_m5)
# The effective Miller capacitance includes this parallel parasitic loading
c_m_eff = c_m + c_para_internal
# Calculate the actual physical UGBW roll-off from the true c_m_eff
f_bw_actual = gm_m12 / (2 * np.pi * c_m_eff)
t_slew = c_m_eff*output_voltage / i_tail
print('slewing time =', round(t_slew/1e-6, 3), 'µs')
tau = 1 / (2 * np.pi * f_bw_actual)
t_settle = 5 * tau
print('settling time =', round(t_settle/1e-6, 3), 'µs')
print(f'Internal Node Parasitic Load = {c_para_internal/1e-15:.2f} fF and of which m5 {abs(gm_m5 / gm_cgg_m5)/1e-15}')
print(f'Effective Miller Capacitance = {c_m_eff/1e-15:.2f} fF')
i_total_rail = i_bias_in + i_tail + id_m5
# Power calculation at the minimum and maximum VDD limits
power_dissipation_min = i_total_rail * vdd_min
power_dissipation_max = i_total_rail * vdd_max
print(f"Total Current from VDD Rail = {i_total_rail/1e-6:.2f} µA")
# ==============================================================================
# 7. Final Industrial Sizing Blueprint Summary Printout
# ==============================================================================
cap_density = 1.5e-15
w_cm_approx = np.sqrt(c_m / cap_density)
print('\n============================================================')
print(' Textbook-Aligned Industrial 2-Stage Miller OTA Blueprint')
print('============================================================')
print('\nManufactured Device Sizes:')
print(f' M1/2 W = {w_12_round} µm, L = {l_12} µm [NMOS Input Pair]')
print(f' M3/4 W = {w_34_round} µm, L = {l_34} µm [PMOS Active Load]')
print(f' M5 W = {w_5_round} µm, L = {l_5} µm [PMOS CS Driver]')
print(f' M6 W = {w_6_round} µm, L = {l_678} µm [NMOS Ref Mirror]')
print(f' M7 W = {w_7_round} µm, L = {l_678} µm [NMOS Tail Mirror]')
print(f' M8 W = {w_8_round} µm, L = {l_678} µm [NMOS Load Mirror]')
print('\nPassives Allocation:')
print(f' Miller Cap Cm = {c_m/1e-15:.1f} fF ({w_cm_approx:.1f} x {w_cm_approx:.1f} µm MOM cap)')
print(f' Nulling Resistor Rz = {rz/1e3:.2f} kΩ')
print('\nEvaluated Performance Metrics:')
print(f' Target Specification UGBW = {f_bw/1e6:.2f} MHz')
print(f' Actual Predicted UGBW = {f_bw_actual/1e6:.2f} MHz (including layout parasitics)')
print(f' Stage 1 Open-Loop Gain A1 = {20*np.log10(a0):.1f} dB')
print(f' Stage 2 Open-Loop Gain A2 = {20*np.log10(a1):.1f} dB')
print(f' Total Open-Loop Gain A0 = {20*np.log10(a0_total):.1f} dB (Specification: >= {gain} dB)')
print('\nCurrent Consumption Budget:')
print(f' Stage 1 Tail Branch (I_tail) = {i_tail/1e-6:.2f} µA')
print(f' Stage 2 Core Branch (Id_M5) = {id_m5/1e-6:.2f} µA')
print(f' Total Net Core Current Draw = {(i_tail + id_m5)/1e-6:.2f} µA (Limit Budget: <= {i_total_limit/1e-6:.2f} µA)')
print(f" Power Dissipation (at VDD = {vdd_max}V) = {power_dissipation_max*1e6:.2f} µW")
print('============================================================')--- Node Parasitic Extraction & Actual UGBW ---
slewing time = 0.046 µs
settling time = 0.032 µs
Internal Node Parasitic Load = 99.54 fF and of which m5 80.75863693703191
Effective Miller Capacitance = 599.54 fF
Total Current from VDD Rail = 47.00 µA
============================================================
Textbook-Aligned Industrial 2-Stage Miller OTA Blueprint
============================================================
Manufactured Device Sizes:
M1/2 W = 2.5 µm, L = 2 µm [NMOS Input Pair]
M3/4 W = 3.5 µm, L = 2 µm [PMOS Active Load]
M5 W = 9.5 µm, L = 2 µm [PMOS CS Driver]
M6 W = 2.0 µm, L = 2 µm [NMOS Ref Mirror]
M7 W = 1.5 µm, L = 2 µm [NMOS Tail Mirror]
M8 W = 1.0 µm, L = 2 µm [NMOS Load Mirror]
Passives Allocation:
Miller Cap Cm = 500.0 fF (18.3 x 18.3 µm MOM cap)
Nulling Resistor Rz = 20.00 kΩ
Evaluated Performance Metrics:
Target Specification UGBW = 10.00 MHz
Actual Predicted UGBW = 24.82 MHz (including layout parasitics)
Stage 1 Open-Loop Gain A1 = 31.6 dB
Stage 2 Open-Loop Gain A2 = 34.3 dB
Total Open-Loop Gain A0 = 65.9 dB (Specification: >= 60 dB)
Current Consumption Budget:
Stage 1 Tail Branch (I_tail) = 17.00 µA
Stage 2 Core Branch (Id_M5) = 10.00 µA
Total Net Core Current Draw = 27.00 µA (Limit Budget: <= 30.00 µA)
Power Dissipation (at VDD = 1.55V) = 72.85 µW
============================================================