skywater-pdk/scripts/python-skywater-pdk/skywater_pdk/simulation/analog/fet.py

284 lines
8.2 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 SkyWater PDK Authors
#
# 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
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
import PySpice.Logging.Logging as Logging
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import u_V
import matplotlib.pyplot as plt
from pathlib import Path
import csv
from collections import defaultdict
import os
import sys
sys.path.insert(0, os.path.abspath(__file__ + '/../../../../'))
from skywater_pdk.base import Cell
logger = Logging.setup_logging()
def create_test_circuit(fet_type, fet_L, fet_W, corner_path):
"""
Creates a simple test circuit that contains only given FET.
Parameters
----------
fet_type: name of the FET model
fet_L: FET length
fet_W: FET width
corner_path:
path to the spice corner file with model definitions and parameters
"""
c = Circuit('gm_id')
c.include(corner_path)
# create the circuit
c.V('gg', 1, c.gnd, 0@u_V)
c.V('dd', 2, c.gnd, 1.8@u_V)
c.X(
'M1', fet_type, 2, 1, c.gnd, c.gnd, L=fet_L, W=fet_W, ad="'W*0.29'",
pd="'2*(W+0.29)'", as_="'W*0.29'", ps="'2*(W+0.29)'", nrd="'0.29/W'",
nrs="'0.29/W'", sa=0, sb=0, sd=0, nf=1, mult=1
)
return c
def run_sim(c, iparam, fet_W):
"""
Runs simulation for a given circuit with FET cell.
Parameters
----------
c: circuit to simulate
iparam:
template string for creating internal parameter names (gm, id, gds,
cgg) for a given FET
fet_W: FET width
"""
sim = c.simulator()
sim.save_internal_parameters(
iparam % 'gm', iparam % 'id', iparam % 'gds', iparam % 'cgg'
)
try:
# 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_id = (an.internal_parameters[iparam % 'gm'].as_ndarray() /
an.internal_parameters[iparam % 'id'].as_ndarray())
ft = (an.internal_parameters[iparam % 'gm'].as_ndarray() /
an.internal_parameters[iparam % 'cgg'].as_ndarray())
id_W = (an.internal_parameters[iparam % 'id'].as_ndarray() / fet_W)
gm_gds = (an.internal_parameters[iparam % 'gm'].as_ndarray() /
an.internal_parameters[iparam % 'gds'].as_ndarray())
except Exception:
sim.ngspice.destroy('all')
sim.ngspice.reset()
raise
sim.ngspice.destroy('all')
sim.ngspice.reset()
return gm_id, ft, id_W, gm_gds, an.nodes['v-sweep']
def init_plots(fet_name, W):
"""
Initializes plots for FET bins simulation.
Parameters
----------
fet_name: name of the current FET cell
W: FET width
"""
figs = [plt.figure(), plt.figure(), plt.figure(), plt.figure()]
plots = [f.subplots() for f in figs]
figs[0].suptitle(f'{fet_name} Id/W vs gm/Id (W = {W})')
plots[0].set_xlabel("gm/Id")
plots[0].set_ylabel("Id/W")
plots[0].grid(True)
figs[1].suptitle(f'{fet_name} fT vs gm/Id (W = {W})')
plots[1].set_xlabel("gm/Id")
plots[1].set_ylabel("f_T")
plots[1].grid(True)
figs[2].suptitle(f'{fet_name} gm/gds vs gm/Id (W = {W})')
plots[2].set_xlabel("gm/Id")
plots[2].set_ylabel("gm/gds")
plots[2].grid(True)
figs[3].suptitle(f'{fet_name} gm/Id vs Vgg (W = {W})')
plots[3].set_xlabel("Vgg")
plots[3].set_ylabel("gm/Id")
plots[3].grid(True)
return figs, plots
def gen_plots(gm_id, id_W, ft, gm_gds, vsweep, fet_W, fet_L, plots):
"""
Generates plot lines for FET bins simulation parameters.
Parameters
----------
gm_id: gm/Id values
id_W: Id/W values
ft: f_T values
gm_gds: gm/gds values
vsweep: v-sweep values
fet_W: FET width
fet_L: FET length
plots: plots on which plot lines should be drawn
"""
# plot some interesting things
plots[0].plot(gm_id, id_W, label=f'W {fet_W} x L {fet_L}')
plots[1].plot(gm_id, ft, label=f'W {fet_W} x L {fet_L}')
plots[2].plot(gm_id, gm_gds, label=f'W {fet_W} x L {fet_L}')
plots[3].plot(vsweep, gm_id, label=f'W {fet_W} x L {fet_L}')
def close_plots(figures):
"""
Closes plots.
"""
for figure in figures:
plt.close(figure)
def generate_fet_plots(
corner_path,
bins_csv,
outdir,
outprefix,
only_W=None,
ext='svg'):
"""
Generates FET bins plots.
Parameters
----------
corner_path: Path to FET model definitions and parameters
bins_csv: Path to the CSV file with bin parameters and FET model names
outdir: Directory where outputs should be stored
outprefix: Prefix for the output plot images
only_W: List of FET widths for which the plots should be generated
ext: extension for plot files
"""
print(f'[generate_fet_plots] {corner_path} {bins_csv}' +
f'{outdir} {outprefix} {only_W}')
bins = Cell.parse_bins(bins_csv)
bins_by_W = defaultdict(list)
# group bins by W
for fetbin in bins:
bins_by_W[(fetbin.device, fetbin.w)].append(fetbin)
Ws = [key for key in bins_by_W.keys()
if only_W is None or key[1] in only_W]
for fet_type, W in Ws:
if outprefix is None:
outprefix = fet_type
print(f'======> {fet_type}: {W}')
iparam = f'@m.xm1.m{fet_type}[%s]'
# fet_W and fet_L values here are only for initialization, they are
# later changed in the for loop
c = create_test_circuit(fet_type, 0.15, 1, corner_path)
figures, plots = init_plots(fet_type, W)
try:
for fetbin in bins_by_W[(fet_type, W)]:
if only_W is not None and fetbin.w not in only_W:
continue
c.element('XM1').parameters['W'] = fetbin.w
c.element('XM1').parameters['L'] = fetbin.l
gm_id, ft, id_W, gm_gds, vsweep = run_sim(c, iparam, fetbin.w)
gen_plots(gm_id, id_W, ft, gm_gds, vsweep, fetbin.w, fetbin.l, plots)
except Exception:
close_plots(figures)
raise
figtitles = ['Id_w', 'fT', 'gm_gds', 'gm_id']
for figure, name in zip(figures, figtitles):
lg = figure.legend(
bbox_to_anchor=(1.05, 1),
loc='upper left'
)
figure.tight_layout()
figure.savefig(
Path(outdir) / (
outprefix +
f'_{fet_type}_{name}_W{str(W).replace(".", "_")}.{ext}'),
bbox_extra_artists=(lg,),
bbox_inches='tight'
)
close_plots(figures)
def main(argv):
import argparse
parser = argparse.ArgumentParser(prog=argv[0])
parser.add_argument(
'corner_path',
help='Path to corner SPICE file containing FET definition',
type=Path
)
parser.add_argument(
'bins_csv',
help='Path to CSV file with fet_type, bin, fet_W and fet_L parameters',
type=Path
)
parser.add_argument(
'outdir',
help='Path to the directory to save the plots to',
type=Path
)
parser.add_argument(
'--outprefix',
help='The prefix to add to plot file names'
)
parser.add_argument(
'--only-w',
help='Simulate the FET only for a given fet_W values',
nargs='+',
type=float
)
parser.add_argument(
'--ext',
help='The image extension to use for figures',
default='svg'
)
args = parser.parse_args(argv[1:])
generate_fet_plots(
args.corner_path,
args.bins_csv,
args.outdir,
args.outprefix,
args.only_w,
args.ext)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))