# read table data
from pygmid import Lookup as lk
import numpy as np
lv_nmos = lk('sg13_lv_nmos.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 Sizing for Different Current Mirror Topologies
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
We want to compare the performance of different current mirror topologies. For this, we will size them for a given output current and minimum compliance voltage of 0.4V.
# define the common values
id_spec = 50e-6
vsat_spec = 0.4# current mirror 1: standard (basic) current mirror
# We use maximum length, and would select gm/Id of 3 to 5, because we are only bound by Vds,sat, and lower is better for current sources
gm_id_spec1 = 5
l_spec1 = 5
vgs1 = lv_nmos.look_upVGS(GM_ID=gm_id_spec1, L=l_spec1, VDS=vsat_spec, VSB=0.0)
print('Vgs =', round(vgs1/1e-3, 1), 'mV')
gm1 = gm_id_spec1 * id_spec
gm_gds1 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec1, L=l_spec1, VDS=vsat_spec, VSB=0)
gds1 = gm1 / gm_gds1
print('gds =', round(gds1/1e-6, 3), 'uS, rout =', rout1:=round(1/gds1/1e3,2), 'kOhm')
id_w1 = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec1, L=l_spec1, VDS=vsat_spec, VSB=0)
w1 = id_spec / id_w1
print('W =', round(w1, 2), 'um, rounded W =', round(w1*2)/2, 'um')Vgs = 593.4 mV
gds = 9.397 uS, rout = 106.42 kOhm
W = 9.42 um, rounded W = 9.5 um
# current mirror 2: cascoded current mirror
gm_id_spec2 = 10
l_spec2 = 2
vgs2 = lv_nmos.look_upVGS(GM_ID=gm_id_spec2, L=l_spec2, VDS=vsat_spec/2, VSB=0.0)
print('Vgs =', round(vgs2/1e-3, 1), 'mV')
gm2 = gm_id_spec2 * id_spec
gm_gds2 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec2, L=l_spec2, VDS=vsat_spec/2, VSB=0)
gds2 = gm2 / gm_gds2
print('gds =', round(gds2/1e-6, 3), 'uS')
id_w2 = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec2, L=l_spec2, VDS=vsat_spec/2, VSB=0)
w2 = id_spec / id_w2
print('W =', round(w2, 2), 'um, rounded W =', round(w2*2)/2, 'um')
gds_total2 = gds2 * gds2 / (gds2 + gm2 + gds2)
print('Total gds =', round(gds_total2/1e-6, 3), 'uS, rout =', rout2:=round(1/gds_total2/1e3,2), 'kOhm')Vgs = 405.2 mV
gds = 35.257 uS
W = 13.22 um, rounded W = 13.0 um
Total gds = 2.179 uS, rout = 458.96 kOhm
# current mirror 3: resistively degenerated current mirror
gm_id_spec3 = 10
l_spec3 = 3
vgs3 = lv_nmos.look_upVGS(GM_ID=gm_id_spec3, L=l_spec3, VDS=vsat_spec/2, VSB=0.0)
print('Vgs =', round(vgs3/1e-3, 1), 'mV')
gm3 = gm_id_spec3 * id_spec
gm_gds3 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec3, L=l_spec3, VDS=vsat_spec/2, VSB=0)
gds3 = gm3 / gm_gds3
print('gds =', round(gds3/1e-6, 3), 'uS')
id_w3 = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec3, L=l_spec3, VDS=vsat_spec/2, VSB=0)
w3 = id_spec / id_w3
print('W =', round(w3, 2), 'um, rounded W =', round(w3*2)/2, 'um')
r_deg = (vsat_spec/2) / id_spec
print('Rdeg =', round(r_deg/1e3,2), 'kOhm')
gds_total3 = gds3 * (1/r_deg) / (gds3 + gm3 + (1/r_deg))
print('Total gds =', round(gds_total3/1e-6, 3), 'uS, rout =', rout3:=round(1/gds_total3/1e3,2), 'kOhm')# current mirror 4: regulated current mirror
gm_id_spec4 = 10
l_spec4 = 1
# The auxiliary amplifier has to be sized first, because it is what sets the
# operating point of the whole structure: the amplifier holds the drain of the
# bottom device at its own gate-source voltage, so Vds(bottom) = Vgs(aux).
gm_id_spec4_aux = 25
id_aux = 5e-6
l_spec4_aux = 0.13
vgs4_aux = lv_nmos.look_upVGS(GM_ID=gm_id_spec4_aux, L=l_spec4_aux, VDS=0.5, VSB=0.0)
print('Vgs (amp) =', round(vgs4_aux/1e-3, 1), 'mV')
gm4_aux = gm_id_spec4_aux * id_aux
print('gm (amp) =', round(gm4_aux/1e-6, 3), 'uS')
gm_gds4_aux = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec4_aux, L=l_spec4_aux, VDS=0.5, VSB=0)
gds4_aux = gm4_aux / gm_gds4_aux
print('gds (amp) =', round(gds4_aux/1e-6, 3), 'uS')
id_w4_aux = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec4_aux, L=l_spec4_aux, VDS=0.5, VSB=0)
w4_aux = id_aux / id_w4_aux
print('W (amp) =', round(w4_aux, 2), 'um, rounded W =', round(w4_aux*2)/2, 'um')
# The bottom device sits at Vds = Vgs(aux); the cascode on top of it still needs
# its own saturation voltage of roughly 2/(gm/Id). Their sum is the compliance
# voltage this topology can actually reach -- note that it is well above the
# vsat_spec = 0.4 V that the other three structures are sized for. This is the
# price of the regulated cascode, and sizing it for 0.4 V would simply leave the
# cascode device in triode, where it provides no cascoding at all.
vds4_bottom = vgs4_aux
vds4_casc = 2 / gm_id_spec4
v_compliance4 = vds4_bottom + vds4_casc
print('Vds (bottom) =', round(vds4_bottom/1e-3, 1), 'mV, Vds (cascode) =',
round(vds4_casc/1e-3, 1), 'mV')
print('=> compliance voltage =', round(v_compliance4, 3), 'V (vs.', vsat_spec,
'V for the other structures)')
vgs4 = lv_nmos.look_upVGS(GM_ID=gm_id_spec4, L=l_spec4, VDS=vds4_bottom, VSB=0.0)
print('Vgs (bottom) =', round(vgs4/1e-3, 1), 'mV')
gm4 = gm_id_spec4 * id_spec
gm_gds4 = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec4, L=l_spec4, VDS=vds4_bottom, VSB=0)
gds4 = gm4 / gm_gds4
print('gds (bottom) =', round(gds4/1e-6, 3), 'uS')
id_w4 = lv_nmos.lookup('ID_W', GM_ID=gm_id_spec4, L=l_spec4, VDS=vds4_bottom, VSB=0)
w4 = id_spec / id_w4
print('W =', round(w4, 2), 'um, rounded W =', round(w4*2)/2, 'um')
gm_gds4_top = lv_nmos.lookup('GM_GDS', GM_ID=gm_id_spec4, L=l_spec4, VDS=vds4_casc, VSB=0)
gds4_top = gm4 / gm_gds4_top
print('gds (top) =', round(gds4_top/1e-6, 3), 'uS')
# calculate total output conductance
rout4 = 1/gds4 + 1/gds4_top + gm4*(1+gm4_aux/gds4_aux)/(gds4_top*gds4)
gds_total4 = 1/rout4
print('Total gds =', round(gds_total4/1e-6, 3), 'uS, rout =', round(rout4/1e3,2), 'kOhm')
# Treat this last number as an optimistic upper bound rather than a prediction.
# The expression multiplies three separate gains, so any optimism in them
# compounds; the simulation gives about 1.7 MOhm at the compliance voltage and
# 3.7 MOhm at Vout = 1.5 V. Neither the body effect on the cascode nor the
# loading of the auxiliary amplifier explains the gap (both are worth well under
# 20%), it is inherent to the idealised cascade-of-gains formula. For the other
# three structures above the same style of estimate lands within ~10..40% of the
# simulated value.