forked from PySpice-org/PySpice
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added simple and an advanced (gmID) examples on sky130 Addressing pos…
…sibly issue PySpice-org#327
- Loading branch information
1 parent
1fb97dc
commit c0b8eac
Showing
3 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
# Adapted and modified from https://github.com/tclarke/sky130radio/tree/4eca853b7e4fd6bc0d69998f65c04f97e73bee84/utils | ||
# Thanks to T Clarke https://github.com/tclarke | ||
|
||
import PySpice.Logging.Logging as Logging | ||
from PySpice.Spice.Netlist import Circuit | ||
from PySpice.Unit import * | ||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
import os.path | ||
import csv | ||
import h5py | ||
import sys | ||
import pandas as pd | ||
import argparse | ||
# Set Logging | ||
logger = Logging.setup_logging() | ||
# Set Defaults If you want to adapt for different technologies | ||
_SKY130_defaults = { | ||
'w': 1, | ||
'l': 0.15, | ||
'nf': 1 | ||
} | ||
_plot_defaults = { | ||
'id_W_vs_gm_id': ['gm_id', 'id_W'], | ||
'ft_vs_gm_id': ['gm_id', 'ft'], | ||
'gm_gds_vs_gm_id': ['gm_id', 'gm_gds'], | ||
'gm_id_vs_vgg': ['v-sweep', 'gm_id'], | ||
} | ||
|
||
|
||
def create_test_circuit(libpath, fet_typ='sky130_fd_pr__nfet_01v8', w=1, l=0.15, nf=1, corner='tt'): | ||
""" | ||
Create the test gmID circuit and instantiate the liberty file in a particular corner | ||
Fet W and L are instantiated | ||
""" | ||
ckt =Circuit('gm_id') | ||
ckt.lib(libpath, section=corner) | ||
|
||
# create the circuit | ||
ckt.V('gg', 1, ckt.gnd, 0@u_V) | ||
ckt.V('dd', 2, ckt.gnd, 1.8@u_V) | ||
ckt.X('M1', fet_typ, 2, 1, ckt.gnd, ckt.gnd, L=l, W=w, nf=1) | ||
return ckt | ||
|
||
|
||
def run_sim(c, iparam, w, l): | ||
""" | ||
Simulation Method which runs DC sim to get the values needed for gm-ID analysis | ||
Parameters id, gm, gds and cgg | ||
:param c: Circuit Object | ||
:param iparam: Mosfet identifier string | ||
:param w: | ||
:return: Values of gm_id, ft, id_W, gm_gds, vgs, gm, id, cgg, gds | ||
""" | ||
sim = c.simulator() | ||
sim.save_internal_parameters(iparam%'gm', iparam%'id', iparam%'gds', iparam%'cgg') | ||
# run the dc simulation | ||
an = sim.dc(Vgg=slice(0, 1.8, 0.01)) | ||
# calculate needed values..need as_ndarray() since most of these have None as the unit and that causes an error | ||
gm = an.internal_parameters[iparam%'gm'].as_ndarray() | ||
id = an.internal_parameters[iparam%'id'].as_ndarray() | ||
gm_id = gm / id | ||
cgg = an.internal_parameters[iparam%'cgg'].as_ndarray() | ||
ft = gm / cgg | ||
id_W = id / w | ||
gds = an.internal_parameters[iparam%'gds'].as_ndarray() | ||
gm_gds = gm / gds | ||
w_arr, l_arr = np.empty(len(gm)), np.empty(len(gm)) | ||
for i in range(len(gm)): | ||
w_arr[i] = w | ||
l_arr[i] = l | ||
gmid_dict = { | ||
'w': list(w_arr), | ||
'l': list(l_arr), | ||
'id_W': list(id_W), | ||
'gm_id': list(gm_id), | ||
'ft': list(ft), | ||
'gm_gds': list(gm_gds), | ||
'v-sweep': list(an.nodes['v-sweep']), | ||
'gm': list(gm), | ||
'id': list(id), | ||
'cgg': list(cgg), | ||
'gds': list(gds), | ||
} | ||
return gmid_dict | ||
# #return id_W, gm_id, ft, gm_gds, an.nodes['v-sweep'], gm, id, cgg, gds | ||
|
||
|
||
def init_plots(): | ||
''' | ||
Plot the figures with the given | ||
:return: | ||
''' | ||
figs, plts = [], [] | ||
for loopnum, plotname in enumerate(_plot_defaults.keys()): | ||
figs.append(plt.figure()) | ||
plts.append(figs[loopnum].subplots()) | ||
figs[loopnum].suptitle(plotname) | ||
plts[loopnum].set_xlabel(_plot_defaults[plotname][0]) | ||
plts[loopnum].set_ylabel(_plot_defaults[plotname][1]) | ||
return plts, figs | ||
# #figs = [plt.figure(), plt.figure(), plt.figure(), plt.figure()] | ||
# #plts = [f.subplots() for f in figs] | ||
# #figs[0].suptitle('Id/W vs gm/Id') | ||
# #plts[0].set_xlabel("gm/Id") | ||
# #plts[0].set_ylabel("Id/W") | ||
# #figs[1].suptitle('fT vs gm/Id') | ||
# #plts[1].set_xlabel("gm/Id") | ||
# #plts[1].set_ylabel("f_T") | ||
# #figs[2].suptitle('gm/gds vs gm/Id') | ||
# #plts[2].set_xlabel("gm/Id") | ||
# #plts[2].set_ylabel("gm/gds") | ||
# #figs[3].suptitle('gm/Id vs Vgg') | ||
# #plts[3].set_xlabel("Vgg") | ||
# #plts[3].set_ylabel("gm/Id") | ||
# #return figs, plts | ||
|
||
|
||
# def gen_plots(gm_id, id_W, ft, gm_gds, vsweep, fet_W, fet_L, plts): | ||
def gen_plots(df_gmid, plts): | ||
# plot some interesting things | ||
for loopnum, plotname in enumerate(_plot_defaults.keys()): | ||
for (w, l) in np.unique(df_gmid.index.values): | ||
curr_x = df_gmid.loc[(w, l)][_plot_defaults[plotname][0]] | ||
curr_y = df_gmid.loc[(w, l)][_plot_defaults[plotname][1]] | ||
plts[loopnum].plot(curr_x, curr_y, label= f'W_{w} x L_{l}') | ||
# #plts[0].plot(gm_id, id_W, label=f'W {fet_W} x L {fet_L}') | ||
# #plts[1].plot(gm_id, ft, label=f'W {fet_W} x L {fet_L}') | ||
# #plts[2].plot(gm_id, gm_gds, label=f'W {fet_W} x L {fet_L}') | ||
# #plts[3].plot(vsweep, gm_id, label=f'W {fet_W} x L {fet_L}') | ||
|
||
|
||
def read_bins(fname): | ||
r = csv.reader(open(fname, 'r')) | ||
return r | ||
|
||
|
||
if __name__ == '__main__': | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--fet_types", required=True, help="Provide the FET Name list",nargs='+', default=[]) | ||
parser.add_argument("--WL_csv", required=True, help="Provide the WL bin") | ||
parser.add_argument("--plot_dir", required=False, help="Provide the WL bin", default="gmid_plots") | ||
parser.add_argument("--hdf_db", required=True, help="hdf5 database", default="gmid_plots") | ||
parser.add_argument("--SingleW", required=False, help="Provide Single W for run on the W", default=None) | ||
parser.add_argument("--libpath", required=False, help="path to library", | ||
default="/usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice") | ||
parser.add_argument("--corner", required=False, help="Corner to run the sims on", default='tt') | ||
|
||
|
||
args = parser.parse_args() | ||
# #if len(sys.argv) < 4: | ||
# print(f'{sys.argv[0]} <fet_type> <bins_csv> <out file> [width]') | ||
# print('<out file> is a template with 1 \%s which will contain the plot name. 4 are generated per LxW combo.') | ||
# print('If [width] is specified, only W/L pairs for that width are processed.') | ||
# sys.exit(0) | ||
fet_types = args.fet_types | ||
bins_fname = args.WL_csv | ||
figname = args.plot_dir | ||
only_W = args.SingleW | ||
for fet_type in fet_types: | ||
print(f'Simulating {fet_type} with bins {bins_fname}') | ||
# Fet Identifier to pass | ||
iparam = f'@m.xm1.m{fet_type}[%s]' | ||
c = create_test_circuit(args.libpath, fet_type, 0.15, 1, args.corner) | ||
bins = read_bins(bins_fname) | ||
next(bins) | ||
# #figtitles = ['Id_w', 'fT', 'gm_gds', 'gm_id'] | ||
# #figs, plts = init_plots() | ||
# #h5name = os.path.splitext(figname % 'data')[0] + '.h5' | ||
h5name = f'{fet_type}.h5' | ||
|
||
out = h5py.File(h5name, "w") | ||
bins_d = out.create_dataset('bins', (0, 2), maxshape=(None,2)) | ||
gm_d = out.create_dataset('gm', (0, 0), maxshape=(None,None)) | ||
id_d = out.create_dataset('id', (0, 0), maxshape=(None,None)) | ||
cgg_d = out.create_dataset('cgg', (0, 0), maxshape=(None,None)) | ||
gds_d = out.create_dataset('gds', (0, 0), maxshape=(None,None)) | ||
vsweep_d = out.create_dataset('vsweep', (0,0), maxshape=(None,None)) | ||
idx = 0 | ||
# loop W and L | ||
run_dict = {} | ||
for dev, bin, fet_W, fet_L in bins: | ||
fet_W, fet_L = float(fet_W), float(fet_L) | ||
if only_W is not None and fet_W != only_W: | ||
continue | ||
print(f'{bin}: {dev} W {fet_W} x L {fet_L}') | ||
# Update parameters in the loop before runing simulations | ||
c.element('XM1').parameters['W'] = fet_W | ||
c.element('XM1').parameters['L'] = fet_L | ||
# Run Simulations | ||
# #id_W, gm_id, ft, gm_gds, vsweep, gm, id, cgg, gds = run_sim(c, iparam, fet_W, fet_L) | ||
curr_dict = run_sim(c, iparam, fet_W, fet_L) | ||
for k, v in curr_dict.items(): | ||
if k in run_dict.keys(): | ||
run_dict[k] = run_dict[k]+curr_dict[k] | ||
else: | ||
run_dict[k] = v | ||
gmid_df = pd.DataFrame.from_dict(run_dict) | ||
# #if idx == 0: | ||
# # gm_d.resize(len(gm_id), 1) | ||
# # id_d.resize(len(id_W), 1) | ||
# # cgg_d.resize(len(ft), 1) | ||
# # gds_d.resize(len(gm_gds), 1) | ||
# # vsweep_d.resize(len(vsweep), 1) | ||
# #bins_d.resize(idx+1, 0) | ||
# #gm_d.resize(idx+1, 0) | ||
# #id_d.resize(idx+1, 0) | ||
# #cgg_d.resize(idx+1, 0) | ||
# #gds_d.resize(idx+1, 0) | ||
# #vsweep_d.resize(idx+1, 0) | ||
# #bins_d[idx,:] = [fet_W, fet_L] | ||
# #gm_d[idx, :] = gm | ||
# #id_d[idx, :] = id | ||
# #cgg_d[idx, :] = cgg | ||
# #gds_d[idx, :] = gds | ||
# #vsweep_d[idx, :] = vsweep # should be the same for every row | ||
# #idx += 1 | ||
gmid_df.to_csv(f'{fet_type}.csv') | ||
gmid_df.set_index(['w', 'l'], inplace=True) | ||
plts, figs = init_plots() | ||
gen_plots(gmid_df, plts) | ||
for f, nm in zip(figs, _plot_defaults.keys()): | ||
f.legend() | ||
f.tight_layout() | ||
f.savefig(f'{fet_type}_{nm}') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
bin,dev, W, L | ||
0,nfet1v8,1.0,1.0 | ||
1,nfet1v8,1.0,0.5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
#r# ===================== | ||
#r# n-MOSFET Transistor | ||
#r# ===================== | ||
|
||
#r# This example shows how to simulate the characteristic curves of an nmos transistor. | ||
|
||
#################################################################################################### | ||
import os | ||
import pdb | ||
|
||
import matplotlib.pyplot as plt | ||
from pint import UnitRegistry | ||
#################################################################################################### | ||
|
||
import PySpice.Logging.Logging as Logging | ||
logger = Logging.setup_logging() | ||
from PySpice.Spice.Netlist import Circuit, SubCircuit, SubCircuitFactory | ||
#################################################################################################### | ||
|
||
from PySpice.Doc.ExampleTools import find_libraries | ||
from PySpice.Probe.Plot import plot | ||
from PySpice.Spice.Library import SpiceLibrary | ||
from PySpice.Spice.Netlist import Circuit | ||
from PySpice.Unit import * | ||
|
||
#################################################################################################### | ||
|
||
libraries_path = find_libraries() | ||
library_sky130 = os.path.abspath("/usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/") | ||
#spice_library = SpiceLibrary(library_sky130, recurse=True) | ||
#for i in spice_library.__dict__['_subcircuits'].keys(): | ||
# print(i) | ||
#################################################################################################### | ||
|
||
#Unit Registry | ||
u = UnitRegistry() | ||
class SkyNmosNfet1p8(SubCircuitFactory): | ||
NAME = 'sky130_fd_pr__nfet_01v8 NMOS' | ||
NODES = ('d', 'g', 's', 'b') | ||
|
||
def __init__(self, w=1*u.m, l=0.15*u.m, nf=1): | ||
super().__init__() | ||
self.nameid = 'nfet_01v8' | ||
# Figure out the allowable ranges sot that assertions can be added to error our for wrong | ||
self.W = w | ||
self.L = l | ||
self.nf = nf | ||
w_sky, l_sky = self.transform_unit(1e6) | ||
self.X(f'{self.nameid}_1', 'sky130_fd_pr__nfet_01v8' 'd', 'g', 's', 'b', w_sky, l_sky, self.nf) | ||
|
||
def transform_unit(self, mult: float): | ||
"""Transforming the unit to the model specific unit by applying a multiplier""" | ||
w_trans = self.W*mult | ||
L_trans = self.L*mult | ||
return w_trans, L_trans | ||
#r# We define a basic circuit to drive an nmos transistor using two voltage sources. | ||
#r# The nmos transistor demonstrated in this example is a low-level device description. | ||
|
||
#?# TODO: Write the : circuit_macros('nmos_transistor.m4') | ||
|
||
circuit = Circuit('NMOS Transistor') | ||
#circuit.include(spice_library['ptm65nm_nmos']) | ||
#circuit.include(spice_library['sky130_fd_pr__nfet_01v8']) | ||
#ngspice = NgSpiceShared.new_instance() | ||
# Define the DC supply voltage value | ||
Vdd = 1.1 | ||
circuit.lib("/usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice",section="tt") | ||
# Instanciate circuit elements | ||
Vgate = circuit.V('gate', 'gatenode', circuit.gnd, 0@u_V) | ||
Vdrain = circuit.V('drain', 'vdd', circuit.gnd, u_V(Vdd)) | ||
circuit.X(1, 'sky130_fd_pr__nfet_01v8', 'vdd', 'gatenode', circuit.gnd, circuit.gnd, W=4.8, L=0.15, nf=1) | ||
|
||
#print(circuit) | ||
#exit() | ||
#circuit.raw_spice = """ | ||
#.title NMOS Transistor | ||
#.lib /usr/bin/miniconda3/share/pdk/sky130A/libs.tech/ngspice/sky130.lib.spice tt | ||
#Vgate gatenode 0 0V | ||
#Vdrain vdd 0 1.1V | ||
##X1 vdd gatenode 0 0 sky130_fd_pr__nfet_01v8 L=0.15 W=4.8 nf=1 | ||
#""" | ||
print(circuit) | ||
|
||
#r# We plot the characteristics :math:`Id = f(Vgs)` using a DC sweep simulation. | ||
#print(circuit) | ||
simulator = circuit.simulator(temperature=25, nominal_temperature=25) | ||
analysis = simulator.dc(Vgate=slice(0, Vdd, .1)) | ||
|
||
figure, ax = plt.subplots(figsize=(20, 10)) | ||
#print(analysis.parameters) | ||
ax.plot(analysis['gatenode'], u_mA(-analysis.Vdrain)) | ||
ax.legend('NMOS characteristic') | ||
ax.grid() | ||
ax.set_xlabel('Vgs [V]') | ||
ax.set_ylabel('Id [mA]') | ||
|
||
plt.tight_layout() | ||
plt.show() | ||
|
||
#f# save_figure('figure', 'transistor-nmos-plot.png') |