diff --git a/examples/advanced-usages/gmID.py b/examples/advanced-usages/gmID.py new file mode 100644 index 000000000..18f0dfbdd --- /dev/null +++ b/examples/advanced-usages/gmID.py @@ -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]} [width]') + # print(' 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}') \ No newline at end of file diff --git a/examples/advanced-usages/gm_id_01v8.csv b/examples/advanced-usages/gm_id_01v8.csv new file mode 100644 index 000000000..ea3dee98d --- /dev/null +++ b/examples/advanced-usages/gm_id_01v8.csv @@ -0,0 +1,3 @@ +bin,dev, W, L +0,nfet1v8,1.0,1.0 +1,nfet1v8,1.0,0.5 diff --git a/examples/advanced-usages/sky130_fd_pr__nfet_01v8_ft_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_ft_vs_gm_id.png new file mode 100644 index 000000000..8275b32c6 Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_ft_vs_gm_id.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_gds_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_gds_vs_gm_id.png new file mode 100644 index 000000000..8b89598b8 Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_gds_vs_gm_id.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_id_vs_vgg.png b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_id_vs_vgg.png new file mode 100644 index 000000000..5942a16cc Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_gm_id_vs_vgg.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__nfet_01v8_id_W_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_id_W_vs_gm_id.png new file mode 100644 index 000000000..d8d0e8358 Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__nfet_01v8_id_W_vs_gm_id.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__pfet_01v8_ft_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_ft_vs_gm_id.png new file mode 100644 index 000000000..95f0f25cb Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_ft_vs_gm_id.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_gds_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_gds_vs_gm_id.png new file mode 100644 index 000000000..fe3bfd8a7 Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_gds_vs_gm_id.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_id_vs_vgg.png b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_id_vs_vgg.png new file mode 100644 index 000000000..a74ff0e38 Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_gm_id_vs_vgg.png differ diff --git a/examples/advanced-usages/sky130_fd_pr__pfet_01v8_id_W_vs_gm_id.png b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_id_W_vs_gm_id.png new file mode 100644 index 000000000..c3882c05b Binary files /dev/null and b/examples/advanced-usages/sky130_fd_pr__pfet_01v8_id_W_vs_gm_id.png differ diff --git a/examples/transistor/sky130_fd_pr__nfet_01v8_igvgs.png b/examples/transistor/sky130_fd_pr__nfet_01v8_igvgs.png new file mode 100644 index 000000000..224cbcad5 Binary files /dev/null and b/examples/transistor/sky130_fd_pr__nfet_01v8_igvgs.png differ diff --git a/examples/transistor/sky130_fd_pr__pfet_01v8_igvgs.png b/examples/transistor/sky130_fd_pr__pfet_01v8_igvgs.png new file mode 100644 index 000000000..9414811ba Binary files /dev/null and b/examples/transistor/sky130_fd_pr__pfet_01v8_igvgs.png differ diff --git a/examples/transistor/transistor_sky130.py b/examples/transistor/transistor_sky130.py new file mode 100644 index 000000000..4ded98528 --- /dev/null +++ b/examples/transistor/transistor_sky130.py @@ -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() \ No newline at end of file