Show 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, LogScale
from bokeh.layouts import layout
output_notebook(hide_banner=True)
Show code cell source
# 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.2: Intrinsic Gain Stage, with constant \(g_m / I_d\)#
Consider an IGS, similar to example 3.1, with:
\(C_L\) = 1 pF
\(g_m / I_d\) = 15 S/A
Find combos of \(L\), \(W\), and \(I_d\) that achieve \(f_u\) = 100 MHz and compute the corresponding DC gain and fan-out.
Assume:
\(V_{ds}\) = 0.6V
\(V_{sb}\) = 0.0V
Circuit analysis#
As before, we can define \(f_u\) as:
So if we need \(f_u\) to be 100 MHz and \(C_L\) is 1 pF, that means:
Show code cell source
gmid_spec = 15
f_u = 100e6
c_l = 1e-12
gm_spec = f_u * c_l * 2 * 3.14159
print(f"The required gm is {gm_spec*1e3:.3f} mS")
The required gm is 0.628 mS
We’ll use that later when we denormalize to find \(W\) and \(I_d\); for now, let’s plot intrinsic gain and transit frequency against \(L\) and \(\frac{g_m}{I_d}\), and see what values of \(L\) give us a transit frequency of 1 GHz at \(\frac{g_m}{I_d}\) of 15:
Show code cell source
TOOLTIPS = [
("gm/Id", "$x"),
("y", "$y"),
("L", "@L")
]
cmap = linear_palette(Turbo256, nch_data_df['L'].nunique())
length_color_pairs = list(zip(nch_data_df['L'].unique(), cmap))
length_color_dict = dict(length_color_pairs)
# create a figure
gmid_plot = bh.create_bokeh_plot(
title="Intrinsic gain and transit frequency vs. gm/Id and L",
x_axis_label="gm / Id (S/A)",
y_axis_label="ft (GHz)",
y_axis_type="log",
tooltips=TOOLTIPS,
width=800,
)
# create a secondary y-axis
gmid_plot.extra_y_ranges['gain'] = Range1d(0, 200)
gmid_plot.extra_y_scales['gain'] = LinearScale()
ax2 = LinearAxis(y_range_name='gain', axis_label="Intrinsic Gain",)
gmid_plot.add_layout(ax2, 'right')
plot_mask = ((nch_data_df['VDS'] == 0.6) &
(nch_data_df['VSB'] == 0.0))
for len, len_group in nch_data_df[plot_mask].groupby('L'):
# display(len_group)
data = ColumnDataSource(len_group)
gmid_plot.line(x='GM_ID', y='GM_CGG', source=data,
legend_label=f"l={len}", line_color=length_color_dict[len],
line_dash="dashed")
gmid_plot.line(x='GM_ID', y='GM_GDS', source=data,
legend_label=f"l={len}", line_color=length_color_dict[len],
line_dash="solid", y_range_name='gain')
gmid_plot.y_range = Range1d(1e8, 100e9)
# add vertical line for gmId = 15
gmid_plot.add_layout(Span(location=15, dimension='height'))
show(gmid_plot)
Show code cell source
# display the same data as the plots, but in table form
# let's grab nch data that gives us gm/Id of 15
biasing_mask = (
(nch_data_df['VDS'] == 0.6) &
(nch_data_df['VSB'] == 0.0)
)
lookup_df, interp_df = lookup(
df=nch_data_df[biasing_mask],
param='GM_ID',
target=gmid_spec
)
cols = ['L', 'ID', 'GM', 'GM_ID', 'GM_GDS', 'GM_CGG']
caption = f"Design points with gm/Id = {gmid_spec}"
display(pretty_table(
df=lookup_df,
cols=cols,
caption=caption,
))
| L | ID | GM | GM_ID | A_v0 | f_t |
|---|---|---|---|---|---|
| 0.180 | 56.78u | 850.55u | 15 | 37 | 84.28G |
| 0.200 | 50.57u | 756.06u | 15 | 42 | 70.06G |
| 0.220 | 45.56u | 680.21u | 15 | 47 | 59.20G |
| 0.240 | 41.46u | 617.96u | 15 | 53 | 50.71G |
| 0.260 | 38.02u | 565.93u | 15 | 58 | 43.93G |
| 0.280 | 35.11u | 521.77u | 15 | 63 | 38.43G |
| 0.300 | 32.60u | 483.80u | 15 | 68 | 33.91G |
| 0.320 | 30.41u | 450.79u | 15 | 72 | 30.13G |
| 0.340 | 28.46u | 421.81u | 15 | 76 | 26.95G |
| 0.360 | 26.72u | 396.16u | 15 | 79 | 24.24G |
| 0.380 | 25.15u | 373.28u | 15 | 83 | 21.91G |
| 0.400 | 23.71u | 352.75u | 15 | 85 | 19.90G |
| 0.420 | 22.40u | 334.21u | 15 | 88 | 18.15G |
| 0.440 | 21.19u | 317.40u | 15 | 90 | 16.62G |
| 0.460 | 20.22u | 302.50u | 15 | 92 | 15.29G |
| 0.480 | 19.38u | 289.00u | 15 | 94 | 14.11G |
| 0.500 | 18.59u | 276.61u | 15 | 96 | 13.07G |
| 0.600 | 15.32u | 227.18u | 15 | 103 | 9.24G |
| 0.700 | 12.84u | 192.07u | 15 | 109 | 6.86G |
| 0.800 | 11.20u | 166.76u | 15 | 115 | 5.31G |
| 0.900 | 9.93u | 147.30u | 15 | 120 | 4.23G |
| 1.000 | 8.89u | 131.78u | 15 | 125 | 3.45G |
| 1.100 | 8.02u | 119.13u | 15 | 131 | 2.86G |
| 1.200 | 7.28u | 108.63u | 15 | 136 | 2.41G |
| 1.300 | 6.66u | 99.79u | 15 | 142 | 2.06G |
| 1.400 | 6.18u | 92.46u | 15 | 147 | 1.78G |
| 1.500 | 5.78u | 86.13u | 15 | 153 | 1.56G |
| 1.600 | 5.42u | 80.61u | 15 | 159 | 1.38G |
| 1.700 | 5.10u | 75.74u | 15 | 165 | 1.22G |
| 1.800 | 4.81u | 71.42u | 15 | 171 | 1.09G |
| 1.900 | 4.55u | 67.56u | 15 | 177 | 0.98G |
| 2.000 | 4.32u | 64.09u | 15 | 183 | 0.89G |
The table above shows us entries from our device data where \(g_m / I_d\) is 15.
For each entry in this table, I can scale appropriately to get the device’s \(g_m\) at the spec’d value of 0.623 mMohs, and from that get the width and \(I_d\) needed for each value of \(L\).
Show code cell source
scale_factor = gm_spec / lookup_df['GM']
scaled_df = scale(
df=lookup_df,
scale_factor=scale_factor
)
cols = ['W', 'L', 'ID', 'GM', 'GM_ID', 'GM_GDS', 'GM_CGG']
caption = "Design points after scaling / denormalization"
display(pretty_table(
df=scaled_df,
cols=cols,
caption=caption,
))
| W | L | ID | GM | GM_ID | A_v0 | f_t |
|---|---|---|---|---|---|---|
| 3.693620 | 0.180 | 41.95u | 628.32u | 15 | 37 | 84.28G |
| 4.155230 | 0.200 | 42.03u | 628.32u | 15 | 42 | 70.06G |
| 4.618548 | 0.220 | 42.09u | 628.32u | 15 | 47 | 59.20G |
| 5.083778 | 0.240 | 42.15u | 628.32u | 15 | 53 | 50.71G |
| 5.551170 | 0.260 | 42.22u | 628.32u | 15 | 58 | 43.93G |
| 6.020993 | 0.280 | 42.28u | 628.32u | 15 | 63 | 38.43G |
| 6.493528 | 0.300 | 42.34u | 628.32u | 15 | 68 | 33.91G |
| 6.969062 | 0.320 | 42.38u | 628.32u | 15 | 72 | 30.13G |
| 7.447862 | 0.340 | 42.39u | 628.32u | 15 | 76 | 26.95G |
| 7.930173 | 0.360 | 42.38u | 628.32u | 15 | 79 | 24.24G |
| 8.416200 | 0.380 | 42.33u | 628.32u | 15 | 83 | 21.91G |
| 8.906103 | 0.400 | 42.24u | 628.32u | 15 | 85 | 19.90G |
| 9.400001 | 0.420 | 42.11u | 628.32u | 15 | 88 | 18.15G |
| 9.897979 | 0.440 | 41.94u | 628.32u | 15 | 90 | 16.62G |
| 10.385510 | 0.460 | 42.00u | 628.32u | 15 | 92 | 15.29G |
| 10.870413 | 0.480 | 42.13u | 628.32u | 15 | 94 | 14.11G |
| 11.357568 | 0.500 | 42.23u | 628.32u | 15 | 96 | 13.07G |
| 13.828370 | 0.600 | 42.37u | 628.32u | 15 | 103 | 9.24G |
| 16.356338 | 0.700 | 42.01u | 628.32u | 15 | 109 | 6.86G |
| 18.839113 | 0.800 | 42.19u | 628.32u | 15 | 115 | 5.31G |
| 21.327329 | 0.900 | 42.36u | 628.32u | 15 | 120 | 4.23G |
| 23.839500 | 1.000 | 42.37u | 628.32u | 15 | 125 | 3.45G |
| 26.371473 | 1.100 | 42.28u | 628.32u | 15 | 131 | 2.86G |
| 28.919608 | 1.200 | 42.12u | 628.32u | 15 | 136 | 2.41G |
| 31.480883 | 1.300 | 41.91u | 628.32u | 15 | 142 | 2.06G |
| 33.979349 | 1.400 | 42.02u | 628.32u | 15 | 147 | 1.78G |
| 36.474470 | 1.500 | 42.13u | 628.32u | 15 | 153 | 1.56G |
| 38.975004 | 1.600 | 42.21u | 628.32u | 15 | 159 | 1.38G |
| 41.480043 | 1.700 | 42.27u | 628.32u | 15 | 165 | 1.22G |
| 43.988826 | 1.800 | 42.32u | 628.32u | 15 | 171 | 1.09G |
| 46.500748 | 1.900 | 42.35u | 628.32u | 15 | 177 | 0.98G |
| 49.015334 | 2.000 | 42.36u | 628.32u | 15 | 183 | 0.89G |
From the table above, we can see that:
intrinsic gain ranges from 37 to 183,
transit frequency ranges from 84.94 GHz -> 1.45 GHz
If we follow our guideline that \(f_t\) should always be greater than 10x \(f_u\), we should limit the possible lengths to \(f_t\) greater than 1 GHz (we wanted \(f_u\) >= 100 MHz):
Show code cell source
min_ft = f_u * 10
ft_mask = (scaled_df['GM_CGG'] > 1e9)
soln_df = scaled_df[ft_mask]
print(f"The maximum gate length that achieves f_u greater than 100 MHz is {soln_df['L'].max()} um.")
The maximum gate length that achieves f_u greater than 100 MHz is 1.8 um.
Plotting \(f_t\), \(A_{vo}\) and \(W\) vs. \(L\) gives us:
Show code cell source
metrics_plot = bh.create_bokeh_plot(
title="IGS metrics vs. device length",
x_axis_label="L (um)",
y_axis_type='log',
y_axis_label='Transit Frequency',
width=800,
)
# create a secondary y-axis
metrics_plot.extra_y_ranges['gain'] = Range1d(0, 200)
metrics_plot.extra_y_scales['gain'] = LinearScale()
ax2 = LinearAxis(y_range_name='gain', axis_label="Intrinsic Gain, Width")
metrics_plot.add_layout(ax2, 'right')
metrics_plot.y_range = Range1d(1e9, 1e11)
data = ColumnDataSource(soln_df)
metrics_plot.line(
x='L', y='GM_GDS', source=data,
legend_label='A_v0', line_color='red',
y_range_name='gain'
)
metrics_plot.line(
x='L', y='GM_CGG', source=data,
legend_label='f_t', line_color='blue'
)
metrics_plot.line(
x='L', y='W', source=data,
legend_label='W', line_color='green',
y_range_name='gain'
)
metrics_plot.legend.location='top_left'
show(metrics_plot)
One thing that jumps out is that up to \(L\) ~ 0.5um, you get good bang for you buck in terms of gain vs. area
In the book, the intrinsic gain flattens more than it does here, and so the authors conclude that there’s not much point in increasing \(L\) past the \(L_{max}\) they find based on \(C_L\) and \(FO=10\).
But that’s not so obvious here; it looks like gain still increases, though at a lower rate, for longer channel lengths without much saturation.