Merge a512ddf81a
into 49d3c73c2c
This commit is contained in:
commit
c78a53bd3d
|
@ -17,9 +17,11 @@ name: skywater-pdk-scripts
|
|||
channels:
|
||||
- symbiflow
|
||||
- defaults
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- python=3.8
|
||||
- pip
|
||||
- ngspice
|
||||
# Packages installed from PyPI
|
||||
- pip:
|
||||
- -r requirements.txt
|
||||
|
|
|
@ -4,5 +4,8 @@ flake8
|
|||
# previews.
|
||||
rst_include
|
||||
|
||||
# the PySpice module for rendering FET bins plots
|
||||
PySpice
|
||||
|
||||
# The Python API for the SkyWater PDK.
|
||||
-e scripts/python-skywater-pdk
|
||||
|
|
|
@ -22,12 +22,16 @@ import os
|
|||
from dataclasses import dataclass
|
||||
from dataclasses_json import dataclass_json
|
||||
from enum import Enum
|
||||
from typing import Optional, Union, Tuple
|
||||
from typing import Optional, Union, Tuple, List
|
||||
from collections import namedtuple
|
||||
from pathlib import Path
|
||||
import csv
|
||||
|
||||
from .utils import comparable_to_none
|
||||
from .utils import dataclass_json_passthru_config as dj_pass_cfg
|
||||
|
||||
|
||||
FETBin = namedtuple('FETBin', ['device', 'bin', 'w', 'l'])
|
||||
LibraryOrCell = Union['Library', 'Cell']
|
||||
|
||||
|
||||
|
@ -545,6 +549,24 @@ class Cell:
|
|||
self))
|
||||
return "{}__{}".format(self.library.fullname, self.name)
|
||||
|
||||
@classmethod
|
||||
def parse_bins(cls, binsfile) -> List[FETBin]:
|
||||
"""
|
||||
Parse bins.csv file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
binsfile: path to the bins.csv file
|
||||
"""
|
||||
with open(binsfile, 'r') as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader)
|
||||
res = []
|
||||
for line in reader:
|
||||
res.append(FETBin(line[0], int(line[1]), float(line[2]), float(line[3])))
|
||||
return res
|
||||
|
||||
|
||||
@classmethod
|
||||
def parse(cls, s):
|
||||
kw = {}
|
||||
|
@ -555,7 +577,6 @@ class Cell:
|
|||
return cls(**kw)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -0,0 +1,300 @@
|
|||
#!/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
|
||||
|
||||
|
||||
"""Module for creating FET characteristics plots from bins.csv files.
|
||||
|
||||
This module allows simulating FET cells and creating:
|
||||
|
||||
* Id/W vs gm/Id
|
||||
* fT vs gm/Id
|
||||
* gm/gds vs gm/Id
|
||||
* gm/Id vs Vgg
|
||||
|
||||
plots based on different FET length and width values from bins.csv file.
|
||||
"""
|
||||
|
||||
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],
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
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))
|
|
@ -0,0 +1,131 @@
|
|||
#!/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
|
||||
|
||||
|
||||
"""Creates plots for FET characteristics for FET cells in Skywater libraries.
|
||||
|
||||
This script scans for FET cells in the Skywater PDK libraries and generates
|
||||
the FET cells using methods from the fet submodule.
|
||||
"""
|
||||
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import contextlib
|
||||
import traceback
|
||||
import errno
|
||||
|
||||
from fet import generate_fet_plots
|
||||
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=argv[0],
|
||||
description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'libraries_dir',
|
||||
help='Path to the libraries directory of skywater-pdk',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'corner_file',
|
||||
help='Path to the corner SPICE file',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'output_dir',
|
||||
help='Path to the output directory',
|
||||
type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
'--libname',
|
||||
help='Library name to generate the Symbolator diagrams for',
|
||||
type=str
|
||||
)
|
||||
parser.add_argument(
|
||||
'--version',
|
||||
help='Version for which the Symbolator diagrams should be generated',
|
||||
type=str
|
||||
)
|
||||
parser.add_argument(
|
||||
'--create-dirs',
|
||||
help='Create directories for output when not present',
|
||||
action='store_true'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--failed-inputs',
|
||||
help=('Path to an output file which will store all input filenames ' +
|
||||
'for which ngspice failed to simulate'
|
||||
),
|
||||
type=Path
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
fetbins = list(args.libraries_dir.rglob('*fet*bins.csv'))
|
||||
|
||||
nc = contextlib.nullcontext()
|
||||
|
||||
with open(args.failed_inputs, 'w') if args.failed_inputs else nc as err:
|
||||
for fetbin in fetbins:
|
||||
outdir = (args.output_dir /
|
||||
fetbin.parent.resolve()
|
||||
.relative_to(args.libraries_dir.resolve()))
|
||||
library = outdir.relative_to(args.output_dir).parts[0]
|
||||
ver = outdir.relative_to(args.output_dir).parts[1]
|
||||
if args.libname and args.libname != library:
|
||||
continue
|
||||
if args.version and args.version != ver:
|
||||
continue
|
||||
print(f'===> {str(fetbin)}')
|
||||
try:
|
||||
if not outdir.exists():
|
||||
if args.create_dirs:
|
||||
outdir.mkdir(parents=True)
|
||||
else:
|
||||
print(f'The output directory {str(outdir)} is missing')
|
||||
print('Run the script with --create-dirs')
|
||||
return errno.ENOENT
|
||||
|
||||
prefix = fetbin.name.replace('.bins.csv', '')
|
||||
generate_fet_plots(
|
||||
args.corner_file,
|
||||
fetbin,
|
||||
outdir,
|
||||
f'{prefix}_',
|
||||
ext='sim.svg'
|
||||
)
|
||||
except (Exception, IndexError):
|
||||
print(
|
||||
f'Failed to generate FET plot for {str(fetbin)}',
|
||||
file=sys.stderr
|
||||
)
|
||||
traceback.print_exc()
|
||||
if err:
|
||||
err.write(f'{fetbin}\n')
|
||||
|
||||
print('Finished generating FET plots')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
Loading…
Reference in New Issue