= ['sg13_lv_nmos', 'sg13_lv_pmos']
devices = 1 # select which device to plot, start from 0 choice
Copyright 2024 Boris Murmann and 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
This notebook displays various important metrics for the SG13G2 CMOS technology. The corresponding Xschem testbenches are named techsweep_sg13g2_lv_nmos.sch
and techsweep_sg13g2_lv_pmos.sch
(to be found in the xschem
folder).
In [1]:
In [2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# widths used for characterization
= np.array([5, 5]) w
In [3]:
# read ngspice data
= pd.read_csv('./techsweep_'+devices[choice]+'.txt', sep=r'\s+')
df_raw = df_raw.columns.to_list()
par_names = par_names[1].split('[')[0]
par_prefix
# remove extra headers in file body and unwanted columns
#df_raw = df_raw[~df_raw['v-sweep'].str.contains('v-sweep')]
= df_raw.drop(['frequency', 'frequency.1'], axis=1)
df = df.apply(pd.to_numeric)
df
# rename columns for readability
= df.columns.str.removeprefix(par_prefix+'[')
df.columns = df.columns.str.replace(par_prefix[1:], '')
df.columns = df.columns.str.removesuffix(']')
df.columns
# round sweep vectors to easily addressable values
'l'] = df['l'].apply(lambda x: round(x/1e-6, 3))
df['vgs'] = df['vgs'].apply(lambda x: round(x, 3))
df['vds'] = df['vds'].apply(lambda x: round(x, 3))
df['vsb'] = df['vsb'].apply(lambda x: round(x, 3))
df[
print(df.columns)
# Note on noise data:
# 1.: sid, sfl are thermal and flicker noise current densities at 1 Hz in A^2/Hz from operating point data
Index(['cdd', 'cgb', 'cgd', 'cgdol', 'cgg', 'cgs', 'cgsol', 'cjd', 'cjs',
'css', 'fug', 'gds', 'gm', 'gmb', 'ids', 'l', 'rg', 'sfl', 'sid', 'vds',
'vdss', 'vgs', 'vsb', 'vth'],
dtype='object')
In [4]:
# sweep variable vectors
= np.unique(abs(df['l']))
l = np.unique(abs(df['vgs']))
vgs = np.unique(abs(df['vds']))
vds = np.unique(abs(df['vsb'])) vsb
In [5]:
# plot gm/ID and fT versus gate bias
#| label: fig-pmos-gmid-ft-vs-vgs
#| fig-cap: $g_m/I_D$ and $f_T$ as a function of the gate-source voltage.
= min(l); VDS1=0.75; VSB1=0
L1 = df.loc[(df['l'] == L1) & (abs(df['vds']) == VDS1) & (abs(df['vsb']) == VSB1)]
df1 = df1['gm'].values/df1['ids'].values
gm_id1 = df1['cgg'].values + df1['cgdol'].values+df1['cgsol'].values
cgg1 = df1['gm'].values/cgg1/2/np.pi
ft1 = df1['fug'].values
ft2
= plt.subplots()
fig, ax1 ='x')
ax1.grid(axisr'$V_\mathrm{GS}$ (V)')
ax1.set_xlabel(= 'tab:blue'
color r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)', color=color)
ax1.set_ylabel(=color)
ax1.plot(vgs, gm_id1, color='y', labelcolor=color)
ax1.tick_params(axis= ax1.twinx()
ax2 = 'tab:red'
color r'$f_\mathrm{T}$ (GHz)', color=color)
ax2.set_ylabel(/1e9, color=color)
ax2.plot(vgs, ft1/1e9, color=color)
ax2.plot(vgs, ft2='y', labelcolor=color)
ax2.tick_params(axis
fig.tight_layout()+', $L$='+str(L1)+r'µm, $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]0, 1.5)
plt.xlim(= df1['vth'].values[0], color='k', linestyle='--')
plt.axvline(x
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_gmID_fT_VGS.eps", bbox_inches='tight') fig.savefig(
In [6]:
# plot product of gm/ID and fT versus gm/ID
= plt.subplots()
fig, ax1 *ft1/1e9)
plt.plot(gm_id1, gm_id12, 26)
plt.xlim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$f_\mathrm{T} \cdot g_\mathrm{m}/I_\mathrm{D}$ (GHz $\cdot$ S/A)')
plt.ylabel(+', $L$='+str(L1)+r'µm, $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.grid()
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_fTgmID_gmID.eps", bbox_inches='tight') fig.savefig(
In [7]:
# plot fT versus gm/ID
#| label: fig-pmos-ft-vs-gmid
#| fig-cap: $f_T$ vs. $g_m/I_D$.
/1e9)
plt.plot(gm_id1, ft12, 26)
plt.xlim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$f_\mathrm{T}$ (GHz)')
plt.ylabel(+', $L$='+str(L1)+r'µm, $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.grid() plt.show()
In [8]:
# plot fT versus gm/ID for all L
#| label: fig-pmos-ft-vs-gmid-vs-l
#| fig-cap: $f_T$ vs. $g_m/I_D$ as a function of $L$.
=0.75; VSB2=0
VDS2= df.loc[(abs(df['vds']) == VDS2) & (abs(df['vsb']) == VSB2)]
df2 = df2['gm'].values/df2['ids'].values
gm_id2 = np.reshape(gm_id2, (len(vgs), -1), order='F')
gm_id2 = df2['gm'].values/(df2['cgg'].values+df2['cgdol'].values+df2['cgsol'].values)/2/np.pi
ft2 = np.reshape(ft2, (len(vgs), -1), order='F')
ft2
= plt.subplots()
fig, ax /1e9)
ax.plot(gm_id2, ft2='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$f_\mathrm{T}$ (GHz)')
plt.ylabel(+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.grid()
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_fT_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
In [9]:
# plot gm/gds versus gm/ID for all L
#| label: fig-pmos-gmgds-vs-gmid-vs-l
#| fig-cap: $g_m/g_{ds}$ vs. $g_m/I_D$ as a function of $L$.
= df2['gm'].values/df2['gds'].values
gm_gds2 = np.reshape(gm_gds2, (len(vgs), -1), order='F')
gm_gds2
= plt.subplots()
fig, ax
ax.plot(gm_id2, gm_gds2)='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(0, 200)
plt.ylim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$g_\mathrm{m}/g_\mathrm{ds}$ (V/V)')
plt.ylabel(+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.grid()
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_gmgds_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
In [10]:
# plot Id/W (Jd) versus gm/ID for all L
#| label: fig-pmos-jd-vs-gmid-vs-l
#| fig-cap: $I_D/W$ vs. $g_m/I_D$ as a function of $L$.
= df2['ids'].values/w[choice]/1e-6
jd2 = np.reshape(jd2, (len(vgs), -1), order='F')
jd2
= plt.subplots()
fig, ax
ax.semilogy(gm_id2, jd2)='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(if choice == 0:
1e-2, 1e3)
plt.ylim(else:
1e-2, 1e3)
plt.ylim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$I_\mathrm{D}/W$ ($\mu$A/$\mu$m)')
plt.ylabel(='minor')
plt.grid(which='major')
plt.grid(which+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_IDW_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
In [11]:
# plot Vdss versus gm/ID for all L
#| label: fig-pmos-vdsat-vs-gmid-vs-l
#| fig-cap: $V_{ds,sat}$ vs. $g_m/I_D$ as a function of $L$.
= df2['vdss']
vdss2 = np.reshape(vdss2, (len(vgs), -1), order='F')
vdss2 = plt.subplots()
fig, ax
ax.plot(gm_id2, vdss2)='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(0, 1)
plt.ylim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$V_\mathrm{ds,sat}$ (V)')
plt.ylabel(='minor')
plt.grid(which='major')
plt.grid(which+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_Vdssat_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
In [12]:
# plot gamma versus gm/ID for all L
#| label: fig-pmos-gamma-vs-gmid-vs-l
#| fig-cap: $\gamma$ vs. $g_m/I_D$ as a function of $L$.
= df2['sid'].values
sid2 = df2['gm'].values
gm2 = np.reshape(sid2, (len(vgs), -1), order='F')
sid2 = np.reshape(gm2, (len(vgs), -1), order='F')
gm2 = sid2/4/1.38e-23/300/gm2
gamma2
= plt.subplots()
fig, ax
ax.plot(gm_id2, gamma2)='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(if choice == 0:
0.6,1.2)
plt.ylim(else:
1,2.5)
plt.ylim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'Noise factor $\gamma$ (1)')
plt.ylabel(='minor')
plt.grid(which='major')
plt.grid(which+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_gamma_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.
In [13]:
# plot fco versus gm/ID for all L
#| label: fig-pmos-fco-vs-gmid-vs-l
#| fig-cap: Flicker-noise corner frequency vs. $g_m/I_D$ as a
= df2['sfl'].values
sfl2 = np.reshape(sfl2, (len(vgs), -1), order='F')
sfl2 = sfl2/sid2
fco = plt.subplots()
fig, ax
ax.semilogy(gm_id2, fco)='center right', bbox_to_anchor=(1.2, 0.5), title='L (µm)', labels=l.tolist())
ax.legend(loc2, 26)
plt.xlim(1e2, 1e8)
plt.ylim(r'$g_\mathrm{m}/I_\mathrm{D}$ (S/A)')
plt.xlabel(r'$f_\mathrm{co}$ (Hz)')
plt.ylabel(='minor')
plt.grid(which='major')
plt.grid(which+r', $V_\mathrm{DS}$='+str(VDS1)+r'V, $V_\mathrm{SB}$='+str(VSB1)+'V')
plt.title(devices[choice]
plt.show()"techsweep_sg13_plots_pmos_overview/plots/PMOS_fco_gmID.eps", bbox_inches='tight') fig.savefig(
The PostScript backend does not support transparency; partially transparent artists will be rendered opaque.