Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pyspice examples #332

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions examples/advanced-usages/gmID.py
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}')
3 changes: 3 additions & 0 deletions examples/advanced-usages/gm_id_01v8.csv
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
105 changes: 105 additions & 0 deletions examples/transistor/transistor_sky130.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#r# =====================
#r# Skywater 130 n-MOSFET Transistor
#r# =====================

#r# This example shows how to simulate the characteristic curves of an nmos transistor for skywater130 Technology.

####################################################################################################
import os
import argparse
import pdb
import numpy as np
import matplotlib.pyplot as plt
from pint import UnitRegistry
import re
####################################################################################################

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 *


if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Example python3.8 ./examples/transistor/nmos-transistor_sky130.py"
" --fet_types sky130_fd_pr__nfet_01v8 sky130_fd_pr__pfet_01v8")
parser.add_argument("--fet_types", required=True, help="Provide the FET Name list",nargs='+', default=[])
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()


####################################################################################################

libraries_path = find_libraries()
library_sky130 = os.path.abspath(args.libpath)
####################################################################################################

#Unit Registry
u = UnitRegistry()

#?# TODO: Extend to search for all possible allowable W and L ranges

circuit = Circuit('NMOS Transistor')
# Define the DC supply voltage value
Vdd = 1.2
circuit.lib(args.libpath, args.corner)
# Instantiate circuit elements and transistor
Vgate = circuit.V('gate', 'gatenode', circuit.gnd, 0@u_V)
Vdrain = circuit.V('drain', 'vdrain', circuit.gnd, u_V(Vdd))
Vvdd = circuit.V('vdd', 'vdd', circuit.gnd, u_V(Vdd))

gate_range = np.arange(0, Vdd+0.1, .1)
drain_range = np.arange(0.3, Vdd+0.1, .1)

for num, fet in enumerate(args.fet_types):
if re.search("nfet", fet):
circuit.X(num, fet, 'vdrain', 'gatenode', circuit.gnd, circuit.gnd, W=4.8, L=0.15, nf=1)
elif re.search("pfet", fet):
circuit.X(num, fet, 'vdrain', 'gatenode', 'vdd', 'vdd', W=4.8, L=0.15, nf=1)

# Raw Spice Equivalent
#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 vdrain 0 1.2V
#Vvdd vdd 0 1.2V
#X0 vdrain gatenode 0 0 sky130_fd_pr__nfet_01v8 L=0.15 W=4.8 nf=1
#X1 vdrain gatenode vdd vdd sky130_fd_pr__pfet_01v8 L=0.15 W=4.8 nf=1
#"""

# Simulation and Plotting
simulator = circuit.simulator(temperature=25, nominal_temperature=25)
# simulator = circuit_raw_spice.simulator(temperature=25, nominal_temperature=25)
# Passing Slices to the voltages are non-intuitive, need to dig deeper, cant pass ndarrays # TODO Can we pass lists ?
if re.search("nfet", fet):
analysis = simulator.dc(Vgate=slice(0, Vdd, 0.1), Vdrain=slice(0.3, Vdd, 0.1))
elif re.search("pfet", fet):
analysis = simulator.dc(Vgate=slice(Vdd, 0, -0.1), Vdrain=slice(Vdd-0.3, 0.0, -0.1))
figure, ax = plt.subplots(figsize=(20, 10))
# Plotting by slicing the sweep elements on Vsrc2 or Vdrain, Need a better way to access both dimensions
# Ideally the simulation object saves the object in different indexed arrays for vsrc2
for num, loopvar in enumerate(range(0, len(drain_range))):
if re.search("nfet", fet):
plt.plot(analysis['gatenode'][num*len(gate_range):(num*len(gate_range) + len(gate_range))],
u_mA(-analysis._branches['vdrain'])[num*len(gate_range):(num*len(gate_range) + len(gate_range))],
label=f'vds={drain_range[loopvar]:.2f}')
elif re.search("pfet", fet):
plt.plot(analysis['gatenode'][num*len(gate_range):(num*len(gate_range) + len(gate_range))],
u_mA(analysis._branches['vdrain'])[num*len(gate_range):(num*len(gate_range) + len(gate_range))],
label=f'vds={drain_range[loopvar]:.2f}')
ax.set_xlabel('Vgs [V]')
ax.set_ylabel('Id [mA]')
plt.title(label=f'{fet} id vgs for vds')
plt.legend()
plt.tight_layout()
plt.savefig(f'{fet}_igvgs.png')
#plt.show()