284 lines
8.2 KiB
Python
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))
|