Example 3.7: Iterative sizing to account for self-loading

Contents

Hide code cell source
import pandas as pd
import numpy as np
import sys, os
import schemdraw

# use engineering format in pandas tables
pd.set_eng_float_format(accuracy=2, use_eng_prefix=True)

# import my helper functions
sys.path.append('../helpers')
from xtor_data_helpers import load_mat_data, lookup, scale
import bokeh_helpers as bh
from pandas_helpers import pretty_table

from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource, LinearAxis, Range1d
from bokeh.palettes import Turbo10, Turbo256, linear_palette
from bokeh.transform import linear_cmap
from bokeh.models import LogAxis, Span, LinearScale
from bokeh.layouts import layout
output_notebook(hide_banner=True)

# load up device data
nch_data_df = load_mat_data("../../Book-on-gm-ID-design-main/starter_kit/180nch.mat")
Loading data from ../../Book-on-gm-ID-design-main/starter_kit/180nch.mat
Found the following columns: ['ID', 'VT', 'GM', 'GMB', 'GDS', 'CGG', 'CGS', 'CGD', 'CGB', 'CDD', 'CSS', 'STH', 'SFL', 'INFO', 'CORNER', 'TEMP', 'VGS', 'VDS', 'VSB', 'L', 'W', 'NFING']
/Users/sean/.pyenv/versions/3.10.4/lib/python3.10/site-packages/pandas/core/internals/construction.py:576: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.
  values = np.array([convert(v) for v in values])

Example 3.7: Iterative sizing to account for self-loading#

Thus far, we’ve ignored the effect of extrinsic caps (i.e., caps that aren’t needed for device operation, like $C_{db}, C_{sb}, C_{gd}). But in reality, these caps will effect our device’s transit frequency, and the IGS’ unity gain bandwidth.

For low-frequency designs, it might not matter much, but it’ll play a major role for higher-speed designs.

For what we’re doing, we’ll be concerned with \(C_{db}\) and \(C_{gd}\) (\(C_{gs}\) and \(C_{gb}\) are shorted by our ideal input source). And we’ll ignore the feedforward path created by \(C_{gd}\), since that only plays a part at frequencies past the device’s transit frequency.

We’ll call the drain capacitances \(C_{dd}\): $\( C_{dd} = C_{gd} + C_{db} \)$

The tricky bit here is that we don’t know the value of these caps until we’ve sized the device, so we can’t include them in the initial sizing. So, we’ll take a 3 step approach:

  1. Initial sizing without extrinsic caps

  2. Find the value of \(C_{dd}\) (we’ll call this \(C_{dd1}\))

  3. Scale the device width and current by this scaling factor:

\[ S = \frac{1}{1-\frac{C_{dd1}}{C_L}} \]

We can derive \(S\) by examing how \(\omega_u\) scales with device width and current:

\[ \omega_u = \frac{g_m}{C_L} = \frac{Sg_m}{C_L + SC_{dd}} \]

Solving for S, we get the equation from a few lines up.

This works well for circuits were device width (and associated parasitic caps) scale linearly with the device transconductance that sets the UGF. But that’s not always going to be the case; more complicated circuits will require an iterative approach instead:

  1. Start by assuming \(C_{dd}\) is 0

  2. Size the circuit to meet BW specs for \(C + C_{dd}\) (we’re assuming \(C_{dd}=0\))

  3. Estimate \(C_{dd}\) for the obtained design, using device width from step 2

  4. Repeat step two with new \(C_{dd}\) estimate

  5. Repeat until convergence, i.e. until resulting gain, BW, etc. settle to some value

So, let’s repeat example 3.3 using this approach:

Consider an IGS, similar to example 3.1 & 3.2, with:

  • \(C_L\) = 1 pF

  • \(f_u\) = 1 GHz

Find the combination of \(L\) and \(g_m\over{I_d}\) that achieves minimum current consumption.

Assume:

  • \(V_{ds}\) = 0.6V

  • \(V_{sb}\) = 0.0V

  • \(FO\) = 10

Solution#

As before, let’s find design points that satisfy our \(f_u\) spec given \(C_L\) = 1pF:

\[ f_u = \frac{g_m}{2 * \pi * C_L} \]
Hide code cell source
f_u = 1e9
c_l = 1e-12
gm_spec = f_u * 2 * 3.14159 * c_l
print(f"The required gm is: {gm_spec*1e3:0.2f} mS")
The required gm is: 6.28 mS

Additionally, if \(f_u\) is 1 GHz and \(FO\) is 10, then \(f_t\) is 10 GHz.

So, we can:

  1. Lookup data points with \(f_t >= 10 GHz\), then

  2. scale those data points to find combinations with \(g_m\) equal to our spec, and pick the one with minimum current:

Hide code cell source
f_t = 10e9

# filter by our assumptions. Also filtering out very low
# values of gm/id, because things get weird for low values.
biasing_mask = (
    (nch_data_df['VDS'] == 0.6) &
    (nch_data_df['VSB'] == 0.0) &
    (nch_data_df['GM_ID'] > 2.5)
    )

filtered_df = nch_data_df[biasing_mask]

lookup_df, interp_df = lookup(df=filtered_df, param='GM_CGG', target=f_t)
lookup_df = lookup_df.reset_index(drop=True)

caption = f"Design points that satisfy f_t = {f_t/1e6} MHz"
show_cols = ['L', 'W', 'VGS', 'ID', 'GM', 'GM_ID', 'GM_GDS', 'GM_CGG', 'CDD']
display(pretty_table(
    df=lookup_df,
    cols=show_cols,
    caption=caption
))

scale_factors = gm_spec / lookup_df['GM']
# display(lookup_df)
# display(scale_factors)
scaled_df = scale(df=lookup_df, scale_factor=scale_factors)

caption = f"Design points that satisfy f_t = {f_t/1e6} MHz, scaled to give gm={gm_spec*1e3:0.2f} mS"
display(pretty_table(
    df=scaled_df,
    cols=show_cols,
    caption=caption
))
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.4; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.5; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.6; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.7; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.8; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 1.9; skipping
The target of 1.00e+10 for GM_CGG is outside                  the existing data for length 2.0; skipping
Design points that satisfy f_t = 10000.0 MHz
L W VGS ID GM GM_ID A_v0 f_t CDD
0.180 5.000000 454.69m 3.30u 76.39u 23 37 10.00G 6.30f
0.200 5.000000 462.33m 3.63u 83.20u 23 42 10.00G 6.30f
0.220 5.000000 470.80m 3.96u 90.25u 23 47 10.00G 6.30f
0.240 5.000000 478.27m 4.36u 98.17u 23 52 10.00G 6.30f
0.260 5.000000 484.60m 4.81u 106.73u 22 58 10.00G 6.30f
0.280 5.000000 490.99m 5.27u 115.29u 22 63 10.00G 6.30f
0.300 5.000000 497.37m 5.72u 123.85u 22 68 10.00G 6.30f
0.320 5.000000 502.70m 6.25u 132.63u 21 73 10.00G 6.30f
0.340 5.000000 507.31m 6.82u 141.47u 21 77 10.00G 6.30f
0.360 5.000000 511.95m 7.40u 150.19u 20 81 10.00G 6.30f
0.380 5.000000 516.64m 7.99u 158.80u 20 85 10.00G 6.30f
0.400 5.000000 521.39m 8.59u 167.32u 20 88 10.00G 6.30f
0.420 5.000000 526.00m 9.23u 175.73u 19 90 10.00G 6.30f
0.440 5.000000 530.06m 9.99u 183.99u 19 92 10.00G 6.30f
0.460 5.000000 534.29m 10.75u 192.16u 18 94 10.00G 6.30f
0.480 5.000000 538.70m 11.54u 200.24u 18 96 10.00G 6.30f
0.500 5.000000 543.31m 12.34u 208.23u 17 98 10.00G 6.30f
0.600 5.000000 568.42m 17.36u 246.75u 14 103 10.00G 6.31f
0.700 5.000000 600.15m 24.40u 283.55u 12 104 10.00G 6.32f
0.800 5.000000 643.04m 35.28u 319.12u 9 103 10.00G 6.34f
0.900 5.000000 700.47m 51.14u 354.05u 7 97 10.00G 6.37f
1.000 5.000000 772.38m 72.88u 388.69u 5 87 10.00G 6.42f
1.100 5.000000 854.89m 99.63u 423.32u 4 72 10.00G 6.53f
1.200 5.000000 947.82m 131.97u 458.10u 3 54 10.00G 6.73f
1.300 5.000000 1,058.81m 174.20u 493.36u 3 32 10.00G 7.20f
Design points that satisfy f_t = 10000.0 MHz, scaled to give gm=6.28 mS
L W VGS ID GM GM_ID A_v0 f_t CDD
0.180 411.254728 454.69m 271.29u 6,283.18u 23 37 10.00G 517.94f
0.200 377.575952 462.33m 273.86u 6,283.18u 23 42 10.00G 475.53f
0.220 348.083584 470.80m 275.81u 6,283.18u 23 47 10.00G 438.40f
0.240 320.029146 478.27m 279.10u 6,283.18u 23 52 10.00G 403.08f
0.260 294.351016 484.60m 283.28u 6,283.18u 22 58 10.00G 370.75f
0.280 272.496703 490.99m 286.96u 6,283.18u 22 63 10.00G 343.24f
0.300 253.658337 497.37m 290.39u 6,283.18u 22 68 10.00G 319.53f
0.320 236.862237 502.70m 296.09u 6,283.18u 21 73 10.00G 298.39f
0.340 222.067833 507.31m 303.05u 6,283.18u 21 77 10.00G 279.77f
0.360 209.173583 511.95m 309.71u 6,283.18u 20 81 10.00G 263.55f
0.380 197.827028 516.64m 316.19u 6,283.18u 20 85 10.00G 249.27f
0.400 187.759733 521.39m 322.58u 6,283.18u 20 88 10.00G 236.61f
0.420 178.776067 526.00m 330.11u 6,283.18u 19 90 10.00G 225.31f
0.440 170.743339 530.06m 341.00u 6,283.18u 19 92 10.00G 215.21f
0.460 163.487305 534.29m 351.62u 6,283.18u 18 94 10.00G 206.09f
0.480 156.894660 538.70m 362.02u 6,283.18u 18 96 10.00G 197.81f
0.500 150.873484 543.31m 372.23u 6,283.18u 17 98 10.00G 190.25f
0.600 127.320088 568.42m 442.11u 6,283.18u 14 103 10.00G 160.70f
0.700 110.794612 600.15m 540.65u 6,283.18u 12 104 10.00G 140.06f
0.800 98.444169 643.04m 694.60u 6,283.18u 9 103 10.00G 124.78f
0.900 88.733698 700.47m 907.65u 6,283.18u 7 97 10.00G 113.00f
1.000 80.824454 772.38m 1,178.15u 6,283.18u 5 87 10.00G 103.84f
1.100 74.212692 854.89m 1,478.78u 6,283.18u 4 72 10.00G 96.89f
1.200 68.578667 947.82m 1,810.04u 6,283.18u 3 54 10.00G 92.34f
1.300 63.676820 1,058.81m 2,218.46u 6,283.18u 3 32 10.00G 91.67f

The minimum current solution is in the first row, with \(I_d = 271.29uA\) and \(L = 180 nm\).

Let’s see what happens if we include that design points’ \(C_{dd}\):

Hide code cell source
f_u_self = gm_spec / (2 * 3.14159 * (c_l + scaled_df.loc[0, 'CDD']))
print(f"Including self loading, the unity gain bandwidth drops to {f_u_self/1e9:0.3f} GHz")
Including self loading, the unity gain bandwidth drops to 0.659 GHz

Quite a drop in UGBW! To get that back, we’ll have to scale up the device’s width and drain current.

We’d probably write a loop to do this a few times, but first let’s just walk through the initial iteration:

Hide code cell source
gm_spec_mk2 = f_u * (2 * 3.14159 * (c_l + scaled_df.loc[0, 'CDD']))
print(f"Including self loading, the required gm becomes: {gm_spec_mk2*1e3:0.3f} mS")
Including self loading, the required gm becomes: 9.537 mS
Hide code cell source
# scale up the design points that we previously found with
# our updated gm spec

scale_factors_mk2 = gm_spec_mk2 / lookup_df['GM']
# display(lookup_df)
# display(scale_factors)
scaled_mk2_df = scale(df=lookup_df, scale_factor=scale_factors_mk2)

caption = f"Design points that satisfy f_t = {f_t/1e6} MHz, scaled to give gm={gm_spec_mk2*1e3:0.2f} mS"
display(pretty_table(
    df=scaled_mk2_df,
    cols=show_cols,
    caption=caption
))
Design points that satisfy f_t = 10000.0 MHz, scaled to give gm=9.54 mS
L W VGS ID GM GM_ID A_v0 f_t CDD
0.180 624.260438 454.69m 411.80u 9,537.50u 23 37 10.00G 786.20f
0.200 573.138041 462.33m 415.71u 9,537.50u 23 42 10.00G 721.83f
0.220 528.370365 470.80m 418.66u 9,537.50u 23 47 10.00G 665.47f
0.240 485.785381 478.27m 423.66u 9,537.50u 23 52 10.00G 611.85f
0.260 446.807492 484.60m 430.00u 9,537.50u 22 58 10.00G 562.78f
0.280 413.633933 490.99m 435.59u 9,537.50u 22 63 10.00G 521.02f
0.300 385.038405 497.37m 440.80u 9,537.50u 22 68 10.00G 485.03f
0.320 359.542915 502.70m 449.44u 9,537.50u 21 73 10.00G 452.94f
0.340 337.085882 507.31m 460.01u 9,537.50u 21 77 10.00G 424.68f
0.360 317.513170 511.95m 470.12u 9,537.50u 20 81 10.00G 400.05f
0.380 300.289769 516.64m 479.96u 9,537.50u 20 85 10.00G 378.38f
0.400 285.008207 521.39m 489.66u 9,537.50u 20 88 10.00G 359.16f
0.420 271.371532 526.00m 501.09u 9,537.50u 19 90 10.00G 342.01f
0.440 259.178324 530.06m 517.62u 9,537.50u 19 92 10.00G 326.68f
0.460 248.164093 534.29m 533.73u 9,537.50u 18 94 10.00G 312.84f
0.480 238.156847 538.70m 549.52u 9,537.50u 18 96 10.00G 300.26f
0.500 229.017056 543.31m 565.02u 9,537.50u 17 98 10.00G 288.78f
0.600 193.264389 568.42m 671.09u 9,537.50u 14 103 10.00G 243.94f
0.700 168.179691 600.15m 820.68u 9,537.50u 12 104 10.00G 212.61f
0.800 149.432447 643.04m 1,054.37u 9,537.50u 9 103 10.00G 189.41f
0.900 134.692523 700.47m 1,377.76u 9,537.50u 7 97 10.00G 171.53f
1.000 122.686757 772.38m 1,788.36u 9,537.50u 5 87 10.00G 157.62f
1.100 112.650492 854.89m 2,244.71u 9,537.50u 4 72 10.00G 147.08f
1.200 104.098375 947.82m 2,747.54u 9,537.50u 3 54 10.00G 140.16f
1.300 96.657659 1,058.81m 3,367.49u 9,537.50u 3 32 10.00G 139.15f

Again, the minimum current solution is in the first row, with \(I_d = 411.8uA\) and \(L = 180 nm\).

Let’s run a few iterations of the sizing routine in a loop, and see if we converge on something:

Hide code cell source
# iterate 5 times and hopefully converge on a design point
gm_specs = [gm_spec]
num_iters = 10

for i in range(num_iters):
    soln_col = scaled_df.loc[0,:]
    self_load_cap = soln_col['CDD']
    gm_spec = f_u * (2*3.14159*(c_l + self_load_cap))
    gm_specs.append(gm_spec)
    print(f"The new gm_spec is: {gm_spec*1e3:0.2f}, scaling devices...")

    scaling_factor = gm_spec / lookup_df['GM']
    scaled_df = scale(df=lookup_df, scale_factor=scaling_factor)

    # print(f"The updated solution is:")

    # caption = f"Design points that satisfy f_t = {f_t/1e6} MHz, scaled to give gm={gm_spec*1e3:0.2f} mS"
    # display(pretty_table(
    #     df=scaled_df,
    #     cols=show_cols,
    #     caption=caption
    # ))

print(f"After {num_iters} iterations the solution is:")

caption = f"Design points that satisfy f_t = {f_t/1e6} MHz, scaled to give gm={gm_spec*1e3:0.2f} mS"
display(pretty_table(
    df=scaled_df,
    cols=show_cols,
    caption=caption
))
The new gm_spec is: 9.54, scaling devices...
The new gm_spec is: 11.22, scaling devices...
The new gm_spec is: 12.10, scaling devices...
The new gm_spec is: 12.55, scaling devices...
The new gm_spec is: 12.78, scaling devices...
The new gm_spec is: 12.90, scaling devices...
The new gm_spec is: 12.97, scaling devices...
The new gm_spec is: 13.00, scaling devices...
The new gm_spec is: 13.02, scaling devices...
The new gm_spec is: 13.02, scaling devices...
After 10 iterations the solution is:
Design points that satisfy f_t = 10000.0 MHz, scaled to give gm=13.02 mS
L W VGS ID GM GM_ID A_v0 f_t CDD
0.180 852.507358 454.69m 562.37u 13,024.67u 23 37 10.00G 1,073.66f
0.200 782.693196 462.33m 567.70u 13,024.67u 23 42 10.00G 985.76f
0.220 721.557216 470.80m 571.74u 13,024.67u 23 47 10.00G 908.78f
0.240 663.401982 478.27m 578.57u 13,024.67u 23 52 10.00G 835.56f
0.260 610.172697 484.60m 587.23u 13,024.67u 22 58 10.00G 768.55f
0.280 564.869965 490.99m 594.85u 13,024.67u 22 63 10.00G 711.52f
0.300 525.819119 497.37m 601.97u 13,024.67u 22 68 10.00G 662.37f
0.320 491.001772 502.70m 613.77u 13,024.67u 21 73 10.00G 618.54f
0.340 460.333824 507.31m 628.20u 13,024.67u 21 77 10.00G 579.95f
0.360 433.604786 511.95m 642.00u 13,024.67u 20 81 10.00G 546.32f
0.380 410.084032 516.64m 655.44u 13,024.67u 20 85 10.00G 516.73f
0.400 389.215108 521.39m 668.69u 13,024.67u 20 88 10.00G 490.48f
0.420 370.592487 526.00m 684.31u 13,024.67u 19 90 10.00G 467.06f
0.440 353.941104 530.06m 706.87u 13,024.67u 19 92 10.00G 446.12f
0.460 338.899764 534.29m 728.88u 13,024.67u 18 94 10.00G 427.22f
0.480 325.233591 538.70m 750.44u 13,024.67u 18 96 10.00G 410.05f
0.500 312.752039 543.31m 771.61u 13,024.67u 17 98 10.00G 394.37f
0.600 263.927207 568.42m 916.46u 13,024.67u 14 103 10.00G 333.13f
0.700 229.670848 600.15m 1,120.74u 13,024.67u 12 104 10.00G 290.34f
0.800 204.069092 643.04m 1,439.87u 13,024.67u 9 103 10.00G 258.66f
0.900 183.939843 700.47m 1,881.51u 13,024.67u 7 97 10.00G 234.25f
1.000 167.544436 772.38m 2,442.23u 13,024.67u 5 87 10.00G 215.25f
1.100 153.838634 854.89m 3,065.43u 13,024.67u 4 72 10.00G 200.85f
1.200 142.159627 947.82m 3,752.12u 13,024.67u 3 54 10.00G 191.41f
1.300 131.998378 1,058.81m 4,598.74u 13,024.67u 3 32 10.00G 190.02f

Let’s take a look at the gm spec across iterations:

Hide code cell source
gm_spec_plot = bh.create_bokeh_plot(
    title="gm spec across iterations",
    x_axis_label='Iteration',
    y_axis_label='gm spec (S)',
)
xs = [i for i in range(num_iters+1)]
gm_spec_plot.line(x=xs, y=gm_specs,)

show(gm_spec_plot)

Lovely! We’ve converged on a solution!!

To drive a 1pF loading cap and achive a unity gain frequency of 1 GHz while minimizing bias current, we’d need this device:

\(g_m = 13.02 mS\)

\(I_d = 562uA\)

\(W=852um\)

\(L=180nm\)