Back to Article
Sizing for Basic (Improved) OTA
Download Notebook

Sizing for Basic (Improved) OTA

Copyright 2024 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

In [1]:
# Read table data
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 [2]:
# Define the given parameters as taken from the specification table or inital guesses
c_load = 50e-15
gm_id_m12 = 13
gm_id_m12c = 13
gm_id_m34 = 13
gm_id_m34c = 13
gm_id_m56 = 13
gm_id_m56c = 13
l_12 = 0.5
l_12c = 0.5
l_34 = 0.5
l_34c = 0.5
l_56 = 1
l_56c = 1
f_bw = 10e6
i_total_limit = 10e-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
vds_headroom = 0.2
In [3]:
# We get the required gm of M1/2 from the bandwidth requirement
# We add a factor of 3 to allow for PVT variation plus additional MOSFET parasitic loading
# We also add an additional factor of 2 to get more dc gain (and there is power still in the budget)
gm_m12 = f_bw * 3 * 4*np.pi*c_load * 3
print('gm12 =', gm_m12/1e-3, 'mS')
gm12 = 0.05654866776461628 mS
In [4]:
# Since we know gm12 and the gmid we can calculate the bias current
id_m12 = gm_m12 / gm_id_m12
i_total = 2*id_m12
print('i_total (exact) =', i_total/1e-6, 'µA')
# we round to 0.5µA bias currents
i_total = max(round(i_total / 1e-6 * 2) / 2 * 1e-6, 0.5e-6)
# here is a manual override to set the current; we keep a reserve of 2µA for bias branch
i_total = 8e-6
id_m12 = i_total/2

print('i_total (rounded) =', i_total/1e-6, 'µA')
if i_total < i_total_limit:
    print('[info] power consumption target is met!')
else:
    print('[info] power consumption target is NOT met!') 
i_total (exact) = 8.699795040710196 µA
i_total (rounded) = 8.0 µA
[info] power consumption target is met!
In [5]:
# We calculate the dc gain
gm_gds_m12 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m12, L=l_12, VDS=vds_headroom, VSB=2*vds_headroom)
gm_gds_m12c = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_m12c, L=l_12c, VDS=vds_headroom, VSB=3*vds_headroom)
gm_gds_m34 = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m34, L=l_34, VDS=vds_headroom, VSB=0)
gm_gds_m34c = lv_pmos.lookup('GM_GDS', GM_ID=gm_id_m34c, L=l_34c, VDS=vds_headroom, VSB=vds_headroom)
# conductance of lower cascoded differential pair
gds_m12 = gm_m12 / gm_gds_m12
gds_m12_casc = gds_m12 / gm_gds_m12c
# conductance of upper cascoded current mirror
gm_m34 = gm_id_m34 * i_total/2
gds_m34 = gm_m34 / gm_gds_m34
gds_m34_casc = gds_m34 / gm_gds_m34c

print('gds_12 =', gds_m12/1e-6, 'µs')
print('gm_12c/gds_12c =',gm_gds_m12c)
print('gds_34 =', gds_m34/1e-6, 'µs')
print('gm_34c/gds_34c =', gm_gds_m34c)

a0 = gm_m12 / (gds_m12_casc + gds_m34_casc)
print('a0 =', 20*np.log10(a0), 'dB')
gds_12 = 4.025519543033504 µs
gm_12c/gds_12c = 13.377388738589055
gds_34 = 2.031305802765517 µs
gm_34c/gds_34c = 24.877072747451322
a0 = 43.394151889182424 dB
In [6]:
# We calculate the MOSFET capacitance which adds to Cload, to see the impact on the BW
gm_cgs_m12 = lv_nmos.lookup('GM_CGS', GM_ID=gm_id_m12, L=l_12, VDS=vds_headroom, VSB=2*vds_headroom)
gm_cdd_m12c = lv_nmos.lookup('GM_CDD', GM_ID=gm_id_m12c, L=l_12c, VDS=vds_headroom, VSB=3*vds_headroom)
gm_cdd_m34c = lv_pmos.lookup('GM_CDD', GM_ID=gm_id_m34c, L=l_34c, VDS=vds_headroom, VSB=vds_headroom)

c_load_parasitic = abs(gm_m12/gm_cgs_m12) + abs(gm_m12/gm_cdd_m12c) + abs(gm_m34/gm_cdd_m34c)
print('additional load capacitance =', c_load_parasitic/1e-15, 'fF')

f_bw = gm_m12 / (4*np.pi * (c_load + c_load_parasitic))
print('-3dB bandwidth incl. parasitics =', f_bw/1e6, 'MHz')
additional load capacitance = 5.4535230735668225 fF
-3dB bandwidth incl. parasitics = 81.14903707795307 MHz
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=vds_headroom, VSB=2*vds_headroom)
vgs_m12c = lv_nmos.look_upVGS(GM_ID=gm_id_m12c, L=l_12c, VDS=vds_headroom, VSB=3*vds_headroom)
vgs_m34 = lv_pmos.look_upVGS(GM_ID=gm_id_m34, L=l_34, VDS=vds_headroom, VSB=0.0) 
vgs_m34c = lv_pmos.look_upVGS(GM_ID=gm_id_m34c, L=l_34c, VDS=vds_headroom, VSB=vds_headroom) 
vgs_m56 = lv_nmos.look_upVGS(GM_ID=gm_id_m56, L=l_56, VDS=vds_headroom, VSB=0.0) 
vgs_m56c = lv_nmos.look_upVGS(GM_ID=gm_id_m56c, L=l_56c, VDS=vds_headroom, VSB=vds_headroom) 

print('vgs_12  =', vgs_m12, 'V')
print('vgs_12c =', vgs_m12c, 'V')
print('vgs_34  =', vgs_m34, 'V')
print('vgs_34c =', vgs_m34c, 'V')
print('vgs_56  =', vgs_m56, 'V')
print('vgs_56c =', vgs_m56c, 'V')
vgs_12  = 0.4363351047848177 V
vgs_12c = 0.4575885897698743 V
vgs_34  = 0.4745538724103232 V
vgs_34c = 0.5115503281076889 V
vgs_56  = 0.35764913382485647 V
vgs_56c = 0.3835723411901689 V
In [8]:
# Calculate settling time due to slewing with the calculated bias current
t_slew = (c_load + c_load_parasitic) * output_voltage / i_total
print('slewing time  =', t_slew/1e-6, 'µs')
t_settle = 5/(2*np.pi*f_bw)
print('settling time =', t_settle/1e-6, 'µs')
slewing time  = 0.009011197499454612 µs
settling time = 0.009806335898909592 µs
In [9]:
# Calculate voltage gain error
gain_error = a0 / (1 + a0)
print('voltage gain error =', (gain_error-1)*100, '%')
voltage gain error = -0.6719920439173688 %
In [10]:
# Calculate total rms output noise
sth_m12 = lv_nmos.lookup('STH_GM', VGS=vgs_m12, L=l_12, VDS=vds_headroom, VSB=2*vds_headroom) * gm_m12
gamma_m12 = sth_m12/(4*1.38e-23*300*gm_m12)

sth_m34 = lv_pmos.lookup('STH_GM', VGS=vgs_m34, L=l_34, VDS=vds_headroom, VSB=0) * gm_m34
gamma_m34 = sth_m34/(4*1.38e-23*300*gm_m34)

output_noise_rms = 1.38e-23*300 / (c_load + c_load_parasitic) * (2*gamma_m12 + 2*gamma_m34 * gm_m34/gm_m12)
print('output noise (rms) =', output_noise_rms/1e-6, 'µV')
output noise (rms) = 0.3084826103706843 µV
In [11]:
# Calculate all widths
id_w_m12 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m12, L=l_12, VDS=vds_headroom, VSB=2*vds_headroom)
w_12 = id_m12 / id_w_m12
w_12_round = max(round(w_12*2)/2, 0.5)
print('M1/2  W =', w_12, 'um, rounded W =', w_12_round, 'um')

id_m12c = id_m12
id_w_m12c = lv_nmos.lookup('ID_W', GM_ID=gm_id_m12c, L=l_12c, VDS=vds_headroom, VSB=3*vds_headroom)
w_12c = id_m12c / id_w_m12c
w_12c_round = max(round(w_12c*2)/2, 0.5)
print('M1/2c W =', w_12c, 'um, rounded W =', w_12c_round, 'um')

id_m34 = id_m12
id_w_m34 = lv_pmos.lookup('ID_W', GM_ID=gm_id_m34, L=l_34, VDS=vds_headroom, VSB=0)
w_34 = id_m34 / id_w_m34
w_34_round = max(round(w_34*2)/2, 0.5) 
print('M3/4  W =', w_34, 'um, rounded W =', w_34_round, 'um')

id_m34c = id_m12
id_w_m34c = lv_pmos.lookup('ID_W', GM_ID=gm_id_m34c, L=l_34c, VDS=vds_headroom, VSB=vds_headroom)
w_34c = id_m34c / id_w_m34c
w_34c_round = max(round(w_34c*2)/2, 0.5) 
print('M3/4c W =', w_34c, 'um, rounded W =', w_34c_round, 'um')

id_w_m5 = lv_nmos.lookup('ID_W', GM_ID=gm_id_m56, L=l_56, VDS=vds_headroom, VSB=0)
w_5 = i_total / id_w_m5
w_5_round = max(round(w_5*2)/2, 0.5)
print('M5    W =', w_5, 'um, rounded W =', w_5_round, 'um')

id_w_m5c = lv_nmos.lookup('ID_W', GM_ID=gm_id_m56c, L=l_56c, VDS=vds_headroom, VSB=vds_headroom)
w_5c = i_total / id_w_m5c
w_5c_round = max(round(w_5c*2)/2, 0.5)
print('M5c   W =', w_5c, 'um, rounded W =', w_5c_round, 'um')

w_6 = w_5_round * i_bias_in / i_total
print('M6    W =', w_6, 'um')

w_6c = w_5c_round * i_bias_in / i_total
print('M6c   W =', w_6c, 'um')
M1/2  W = 0.8310873754124203 um, rounded W = 1.0 um
M1/2c W = 0.8017034903234459 um, rounded W = 1.0 um
M3/4  W = 3.281329534410626 um, rounded W = 3.5 um
M3/4c W = 2.98941704661016 um, rounded W = 3.0 um
M5    W = 3.0508401171424118 um, rounded W = 3.0 um
M5c   W = 2.8749923811908227 um, rounded W = 3.0 um
M6    W = 7.500000000000002 um
M6c   W = 7.500000000000002 um
In [12]:
# Print out final design values
print('Improved OTA dimensioning:')
print('--------------------------')
print('M1/2  W=', w_12_round, ', L=', l_12)
print('M1/2c W=', w_12c_round, ', L=', l_12c)
print('M3/4  W=', w_34_round, ', L=', l_34)
print('M3/4c W=', w_34c_round, ', L=', l_34c)
print('M5   W=', w_5_round, ', L=', l_56)
print('M5c  W=', w_5c_round, ', L=', l_56c)
print('M6   W=', w_6, ', L=', l_56)
print('M6c  W=', w_6c, ', L=', l_56c)
print()
print('Improved OTA performance summary:')
print('---------------------------------')
print('supply current =', i_total/1e-6, 'µA')
print('output noise =', output_noise_rms/1e-6, 'µVrms')
print('voltage gain error =', (gain_error-1)*100, '%')
print('-3dB bandwidth incl. parasitics =', f_bw/1e6, 'MHz')
print('turn-on time (slewing+settling) =', (t_slew+t_settle)/1e-6, 'µs')
print()
print('Improved OTA bias point check:')
print('------------------------------')
print('headroom M1+M1c =', vdd_min-vgs_m34+vgs_m12-vin_max, 'V')
print('headroom M4+M4c =', vdd_min-vin_max, 'V')
print('headroom M5+M5c =', vin_min-vgs_m12, 'V')
Improved OTA dimensioning:
--------------------------
M1/2  W= 1.0 , L= 0.5
M1/2c W= 1.0 , L= 0.5
M3/4  W= 3.5 , L= 0.5
M3/4c W= 3.0 , L= 0.5
M5   W= 3.0 , L= 1
M5c  W= 3.0 , L= 1
M6   W= 7.500000000000002 , L= 1
M6c  W= 7.500000000000002 , L= 1

Improved OTA performance summary:
---------------------------------
supply current = 8.0 µA
output noise = 0.3084826103706843 µVrms
voltage gain error = -0.6719920439173688 %
-3dB bandwidth incl. parasitics = 81.14903707795307 MHz
turn-on time (slewing+settling) = 0.018817533398364204 µs

Improved OTA bias point check:
------------------------------
headroom M1+M1c = 0.5117812323744945 V
headroom M4+M4c = 0.5499999999999999 V
headroom M5+M5c = 0.26366489521518227 V