# read table data
from pygmid import Lookup as lk
import numpy as np
= lk('sg13_lv_nmos.mat')
lv_nmos = lk('sg13_lv_pmos.mat')
lv_pmos # 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
Sizing for Differential OTA
Copyright 2025 Harald Pretl
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
# define the given parameters as taken from the specification table or initial guesses
# input differential pair
= 10
gm_id_m12 = 0.5
l_12 # diffpair load
= 5
gm_id_m35 = 0.5
l_35 # common-source output stage (we use the same sizing as for the load of the diffpair)
= gm_id_m35
gm_id_m46 = l_35
l_46 # current source output stage
= 5
gm_id_m78 = 5
l_78 # current sources
= 10
gm_id_m1011 = 5
l_1011 # bias currents
= 20e-6
i_bias_in = 200e-6
i_bias_diffpair = i_bias_diffpair/2
i_bias_stage2 = i_bias_in
i_bias_levelshift # supply voltage
= 1.45
vdd_min = 1.55
vdd_max = (vdd_min+vdd_max)/2
vdd # bandwidth requirement
= 1e9 gbw
# we set the bias current of the diffpair, so calculate gm
= gm_id_m12 * i_bias_diffpair/2
gm_m12 print('gm12 =', round(gm_m12/1e-3, 4), 'mS')
# we can also calculate the gm of the load
= gm_id_m35 * i_bias_diffpair/2
gm_m35 print('gm35 =', round(gm_m35/1e-3, 4), 'mS')
# lookup the gds of the input diffpair
= lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m12, L=l_12, VDS=0.4, VSB=0)
gm_gds_m12 = gm_m12 / gm_gds_m12
gds_m12 # lookup the gds of the diffpair load
= lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m35, L=l_35, VDS=0.5, VSB=0)
gm_gds_m35 = gm_m35 / gm_gds_m35
gds_m35 # show the values
print('gds12 =', round(gds_m12/1e-6, 1), 'µS')
print('gds35 =', round(gds_m35/1e-6, 1), 'µS')
# print the simplified impedance at the load of the diffpair (just using the gds)
print('R_load (due to MOSFETs) =', round(1/(gds_m12+gds_m35)/1e3, 1), 'kOhm')
gm12 = 1.0 mS
gm35 = 0.5 mS
gds12 = 47.3 µS
gds35 = 16.5 µS
R_load (due to MOSFETs) = 15.7 kOhm
# here we select the resistors large enough to not limit the resistance
= 100e3
R12
# calculate effect load resistance
= 1/(1/R12 + gds_m12 + gds_m35)
R_load print('R_load (MOSFET + R_12) =', round(R_load/1e3, 1), 'kOhm')
# calculate gain of first stage
= R_load * gm_m12
A1 print('Gain first stage (differential) = ', round(20*np.log10(abs(A1)), 1), 'dB')
R_load (MOSFET + R_12) = 13.6 kOhm
Gain first stage (differential) = 22.6 dB
# we get the gm and gds of the common-source output stage
= gm_id_m46 * i_bias_stage2
gm_m46 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m46, L=l_46, VDS=0.75, VSB=0)
gm_gds_m46 = gm_m46 / gm_gds_m46
gds_m46 # we get the gm and gds of the current source of the output stage
= gm_id_m78 * i_bias_stage2
gm_m78 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m78, L=l_78, VDS=0.75, VSB=0)
gm_gds_m78 = gm_m78 / gm_gds_m78
gds_m78
# output resistance due to second stage
print('R_out (due to MOSFETs) =', round(1/(gds_m78+gds_m46)/1e3, 1), 'kOhm')
# select R34 (based on above calculation), also consider value of offset current below
= 160e3
R34
# calculate the gain of the 2nd stage
= gm_m46 / (gds_m78 + gds_m46 + 1/R34)
A2 print('Gain second stage (differential) = ', round(20*np.log10(abs(A2)), 1), 'dB')
print('Gain total (differential) = ', round(20*np.log10(abs(A1*A2)), 1), 'dB')
R_out (due to MOSFETs) = 57.9 kOhm
Gain second stage (differential) = 26.6 dB
Gain total (differential) = 49.2 dB
# we calculate the MOSFET capacitance at the 1st stage load
= lv_nmos.lookup('GM_CDD', GM_ID=gm_id_m12, L=l_12, VDS=0.4, VSB=0)
gm_cdd_m12 = lv_pmos.lookup('GM_CDD', GM_ID=gm_id_m35, L=l_35, VDS=0.5, VSB=0)
gm_cdd_m35 = lv_pmos.lookup('GM_CGG', GM_ID=gm_id_m46, L=l_46, VDS=0.75, VSB=0)
gm_cgg_m46
= abs(gm_m12/gm_cdd_m12) + abs(gm_m35/gm_cdd_m35) + abs(gm_m46/gm_cgg_m46)
c_load print('Parasitic load capacitance (due to MOSFETs) =', round(c_load/1e-15, 1), 'fF')
# specify stabilization cap at load of 1st stage
= 2.4e-12
C_stab12 = 1/(2*np.pi * R_load * (c_load + C_stab12))
f_dompole print('Dominant pole =', round(f_dompole/1e6, 2), 'MHz')
= abs(A1*A2) * f_dompole
gbw_achieved print('Achieved GBW =', round(gbw_achieved/1e9, 2), 'GHz, wanted GBW =', round(gbw/1e9, 2), 'GHz')
Parasitic load capacitance (due to MOSFETs) = 58.2 fF
Dominant pole = 4.78 MHz
Achieved GBW = 1.38 GHz, wanted GBW = 1.0 GHz
# we can now look up the VGS of the MOSFET
= lv_nmos.look_upVGS(GM_ID=gm_id_m12, L=l_12, VDS=0.4, VSB=0.0)
vgs_m12 = lv_pmos.look_upVGS(GM_ID=gm_id_m35, L=l_35, VDS=0.5, VSB=0.0)
vgs_m35 = lv_pmos.look_upVGS(GM_ID=gm_id_m46, L=l_46, VDS=0.75, VSB=0.0)
vgs_m46 = lv_nmos.look_upVGS(GM_ID=gm_id_m78, L=l_78, VDS=0.75, VSB=0.0)
vgs_m78
print('vgs_12 =', round(float(vgs_m12), 3), 'V')
print('vgs_35 =', round(float(vgs_m35), 3), 'V')
print('vgs_46 =', round(float(vgs_m46), 3), 'V')
print('vgs_78 =', round(float(vgs_m78), 3), 'V')
vgs_12 = 0.442 V
vgs_35 = 0.743 V
vgs_46 = 0.745 V
vgs_78 = 0.591 V
# calculate all widths
= lv_nmos.lookup('ID_W', GM_ID=gm_id_m12, L=l_12, VDS=0.4, VSB=0)
id_w_m12 = i_bias_diffpair/2 / id_w_m12
w_12 = max(round(w_12*2)/2, 0.5)
w_12_round print('M1/2 W =', round(w_12, 2), 'um, rounded W =', w_12_round, 'um')
= lv_pmos.lookup('ID_W', GM_ID=gm_id_m35, L=l_35, VDS=vgs_m35, VSB=0)
id_w_m35 = i_bias_diffpair/2 / id_w_m35
w_35 = max(round(w_35*2)/2, 0.5)
w_35_round print('M3/5 W =', round(w_35, 2), 'um, rounded W =', w_35_round, 'um')
= lv_pmos.lookup('ID_W', GM_ID=gm_id_m46, L=l_46, VDS=0.75, VSB=0)
id_w_m46 = i_bias_stage2 / id_w_m46
w_46 = max(round(w_46*2)/2, 0.5)
w_46_round print('M4/6 W =', round(w_46, 2), 'um, rounded W =', w_46_round, 'um')
= lv_nmos.lookup('ID_W', GM_ID=gm_id_m78, L=l_78, VDS=0.75, VSB=0)
id_w_m78 = i_bias_stage2 / id_w_m78
w_78 = max(round(w_78*2)/2, 0.5)
w_78_round print('M7/8 W =', round(w_78, 2), 'um, rounded W =', w_78_round, 'um')
= lv_nmos.lookup('ID_W', GM_ID=gm_id_m1011, L=l_1011, VDS=0.75, VSB=0)
id_w_m10 = i_bias_diffpair / id_w_m10
w_10 = w_10 * i_bias_in / i_bias_diffpair
w_9 = max(round(w_9*2)/2, 0.5)
w_9_round print('M9 W =', round(w_9_round, 2), 'um')
= w_9_round * i_bias_diffpair / i_bias_in
w_10_round print('M10 W =', w_10_round, 'um')
M1/2 W = 10.08 um, rounded W = 10.0 um
M3/5 W = 8.56 um, rounded W = 8.5 um
M4/6 W = 8.56 um, rounded W = 8.5 um
M7/8 W = 18.26 um, rounded W = 18.5 um
M9 W = 17.5 um
M10 W = 175.0 um
# calculate offset current for output common-mode
= (vdd/2 - vgs_m78) / R34
i_offset print('Required output offset current =', round(i_offset/1e-6, 1), 'µA')
= w_9_round * i_offset / i_bias_in
w_11_round print('Ratio of M9/M11 =', round(i_bias_in/i_offset, 1))
Required output offset current = 1.0 µA
Ratio of M9/M11 = 20.2
# calculate common-mode gain
= gm_id_m1011 * i_bias_diffpair
gm_m10 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m1011, L=l_1011, VDS=0.4, VSB=0)
gm_gds_m10 = gm_m10 / gm_gds_m10
gds_m10 = -gm_m12 * gds_m10 / (gm_m35 * (gm_m12 + gds_m10))
A_cm1 = -gm_m46 / gm_m78
A_cm2
print('Gain total (common-mode) = ', round(20*np.log10(abs(A_cm1*A_cm2)), 1), 'dB')
Gain total (common-mode) = -20.7 dB