blob: 2ff66a736f678014754c70bce6ed576c555bd21e [file] [log] [blame]
#!/usr/bin/env python3
#------------------------------------------------------------------------------
#
# run_spice_tests_new.py --
#
# Run all ngspice tests, assuming an input file "devices.txt" containing,
# with one entry per line:
#
# <device_name> <device_type> <expected_value>
#
# where <device_type> is one of: mosfet, bipolar, capacitor, resistor, or diode.
#
#------------------------------------------------------------------------------
import os
import pprint
import re
import subprocess
import json
import sys
__dir__ = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.abspath(os.path.join(__dir__, "..")))
from common import get_cell_directory
import find_all_devices
import find_all_devices_new
from plot_mosfet_iv import make_mosfet_iv_plot
from plot_bipolar_iv import make_bipolar_iv_plot
from plot_bipolar_beta import make_bipolar_beta_plot
from plot_diode_iv import make_diode_iv_plot
#------------------------------------------------------------------------------
# Enumerate capacitors that have additional shield pin connection
#------------------------------------------------------------------------------
four_pin_caps = [
'sky130_fd_pr__cap_vpp_03p9x03p9_m1m2_shieldl1_floatm3',
'sky130_fd_pr__cap_vpp_04p4x04p6_l1m1m2_shieldpo_floatm3',
'sky130_fd_pr__cap_vpp_04p4x04p6_m1m2m3_shieldl1m5_floatm4',
'sky130_fd_pr__cap_vpp_06p8x06p1_l1m1m2m3_shieldpom4',
'sky130_fd_pr__cap_vpp_06p8x06p1_m1m2m3_shieldl1m4',
'sky130_fd_pr__cap_vpp_08p6x07p8_l1m1m2_shieldpo_floatm3',
'sky130_fd_pr__cap_vpp_08p6x07p8_m1m2m3_shieldl1m5_floatm4',
'sky130_fd_pr__cap_vpp_11p3x11p8_l1m1m2m3m4_shieldm5_nhv',
'sky130_fd_pr__cap_vpp_11p3x11p8_l1m1m2m3m4_shieldm5_nhv__base',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2_shieldpom3',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3_shieldm4',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3_shieldpom4',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldm5',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_x',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3_shieldl1m5_floatm4',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3m4_shieldl1m5',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3m4_shieldm5',
'sky130_fd_pr__cap_vpp_04p4x04p6_l1m1m2_shieldm3_floatpo',
'sky130_fd_pr__cap_vpp_04p4x04p6_l1m1m2m3m4_shieldpom5',
'sky130_fd_pr__cap_vpp_04p4x04p6_m1m2m3_shieldl1m5_floatm4_r',
'sky130_fd_pr__cap_vpp_04p4x04p6_m1m2m3_shieldl1m5_floatm4_top',
'sky130_fd_pr__cap_vpp_06p8x06p1_l1m1m2m3_shieldpom4_r',
'sky130_fd_pr__cap_vpp_06p8x06p1_l1m1m2m3_shieldpom4_top',
'sky130_fd_pr__cap_vpp_06p8x06p1_l1m1m2m3m4_shieldpo_floatm5',
'sky130_fd_pr__cap_vpp_06p8x06p1_m1m2m3_shieldl1m4_r',
'sky130_fd_pr__cap_vpp_06p8x06p1_m1m2m3_shieldl1m4_top',
'sky130_fd_pr__cap_vpp_08p6x07p8_m1m2m3_shieldl1m5_floatm4_r',
'sky130_fd_pr__cap_vpp_08p6x07p8_m1m2m3_shieldl1m5_floatm4_top',
'sky130_fd_pr__cap_vpp_08p6x07p8_m1m2m3m4_shieldpom5',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2_shieldpom3_r',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3_shieldm4_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3_shieldpom4_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldm5_r',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldm5_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_m5pullin',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_r',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_x6',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_x7',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_x8',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_x9',
'sky130_fd_pr__cap_vpp_11p5x11p7_l1m1m2m3m4_shieldpom5_xtop',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3_shieldl1m5_floatm4_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3m4_shieldl1m5_r',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3m4_shieldl1m5_top',
'sky130_fd_pr__cap_vpp_11p5x11p7_m1m2m3m4_shieldm5_r'
]
#------------------------------------------------------------------------------
# Enumerate nFETs that have additional p-well pin connection
#------------------------------------------------------------------------------
five_pin_fets = [
'sky130_fd_pr__nfet_20v0_iso',
'sky130_fd_pr__nfet_20v0_nvt_iso',
'sky130_fd_pr__nfet_20v0_reverse_iso'
]
#------------------------------------------------------------------------------
# Specific handling of include files to work around limitations of the
# ad hoc methods used in find_all_devices.py.
#------------------------------------------------------------------------------
def includes_special_handling(devicename, corner, includes):
newinclist = []
for incfile in includes:
# Pull the PDK path out of the include name
if '/models/' in incfile:
pdkbase_path = incfile.split('/models/')[0]
elif '/cells/' in incfile:
pdkbase_path = incfile.split('/cells/')[0]
else:
pdkbase_path = None
if pdkbase_path:
modpath = pdkbase_path + '/models'
rcpath = modpath + '/r+c'
parampath = modpath + '/parameters'
cornerpath = modpath + '/corners/' + corner
incbase = os.path.split(incfile)[1]
if incbase == 'sky130_fd_pr__model__cap_vpp.model.spice':
# Insert additional file. NOTE: Currently there is no way to pass
# a BEOL corner to this routine.
newinclist.append(rcpath + '/res_typical__cap_typical__lin.spice')
newinclist.append(rcpath + '/res_typical__cap_typical.spice')
elif incbase == 'sky130_fd_pr__model__cap_var.model.spice':
newinclist.append(rcpath + '/res_typical__cap_typical__lin.spice')
newinclist.append(rcpath + '/res_typical__cap_typical.spice')
newinclist.append(cornerpath + '/nonfet.spice')
elif incbase == corner + '.spice':
newinclist.append(rcpath + '/res_typical__cap_typical__lin.spice')
newinclist.append(rcpath + '/res_typical__cap_typical.spice')
elif '__npn_11v0' in devicename:
newinclist.append(modpath + '/sky130.lib.spice')
elif 'model__diode_pd2nw_11v0' in incbase:
newinclist.append(pdkbase_path + '/cells/pfet_g5v0d10v5/sky130_fd_pr__pfet_g5v0d10v5__' + corner + '.corner.spice')
elif 'model__diode_pw2nd_11v0' in incbase:
newinclist.append(modpath + '/sky130.lib.spice')
elif 'model__parasitic__diodes_pw2dn' in incbase:
newinclist.append(modpath + '/sky130.lib.spice')
elif '__npn_05v5__' in incbase:
newinclist.append(modpath + '/corners/' + corner + '/nonfet.spice')
elif '__pnp_05v5_W0' in devicename:
newinclist.append(modpath + '/corners/' + corner + '/nonfet.spice')
elif '__pnp_05v5_W3' in devicename:
newinclist.append(modpath + '/corners/' + corner + '.spice')
elif '__nfet_20v0_nvt_iso' in devicename:
newinclist.append(modpath + '/sky130.lib.spice')
newinclist.append(pdkbase_path + '/cells/nfet_20v0/sky130_fd_pr__nfet_20v0__tt_discrete.corner.spice')
newinclist.append(incfile)
return newinclist
#------------------------------------------------------------------------------
# Run ngspice on the indicated input file in the "results" directory.
#------------------------------------------------------------------------------
TIMEOUT = 360
def runspice(scriptpath, resultname, devicename, expectedval):
scriptdir = os.path.dirname(scriptpath)
scriptfile = os.path.basename(scriptpath)
scriptbase, scriptext = scriptfile.split('.', 1)
stdout_file = os.path.join(scriptdir, scriptbase+'.stdout')
stderr_file = os.path.join(scriptdir, scriptbase+'.stderr')
stdout_fobj = open(stdout_file, 'w')
stderr_fobj = open(stderr_file, 'w')
print('Running ngspice on', scriptfile, "(in %s)" % scriptdir)
with subprocess.Popen(
['ngspice', scriptpath],
stdin= subprocess.DEVNULL,
stdout = stdout_fobj,
stderr = stderr_fobj,
cwd = scriptdir,
universal_newlines = True,
close_fds=True,
) as sproc:
stdout_fobj.close()
stderr_fobj.close()
try:
sproc_retcode = sproc.wait(timeout=TIMEOUT)
except subprocess.TimeoutExpired as e:
print("WARNING: ngspice on %s timed out!" % scriptpath)
# Give ngspice 5 seconds to die on SIGTERM before sending SIGKILL
sproc.terminate()
try:
sproc_retcode = sproc.wait(timeout=5)
except subprocess.TimeoutExpired as e:
sproc.kill()
sproc_retcode = sproc.poll()
assert sproc_retcode is not None, str(sproc)+" won't terminate!?"
stdout = open(stdout_file).read().splitlines(True)
stderr = open(stderr_file).read().splitlines(True)
if True:
found = True if resultname == 'none' else False
valueline = False
returncode = 0
reason = 'Success'
for line in stdout:
if line.strip('\n').strip() == '':
continue
print('Diagnostic: ngspice output line is "' + line.strip('\n').strip() + '"')
if valueline:
try:
rvaluestring = line.split('=')[1].strip()
except:
rvaluestring = line.strip('\n').strip()
if rvaluestring != expectedval:
if expectedval == 'none':
returncode = rvaluestring
else:
print('Result error: Expected "' + resultname + '" with value "' + expectedval + '" but got ' + rvaluestring)
reason = 'Expected "' + resultname + '" with value "' + expectedval + '" but got ' + rvaluestring
returncode = -1
valueline = False
elif resultname != 'none' and resultname in line:
found = True
valueline = True
if stderr:
errors = False
messages = []
for line in stderr:
if len(line.strip()) > 0:
messages.append(line)
if 'error' in line.lower():
errors = True
if errors:
print('Execution error: Ngspice returned error messages:')
# Print errors. Also try to pull the most relevant error message.
for message in messages:
print(message)
if 'find model' in message.lower() or 'undefined' in message.lower() or 'unknown' in message.lower() or 'valid model' in message.lower() or 'many parameters' in message.lower() or 'few parameters' in message.lower():
reason = message
if reason == 'Success':
for message in messages:
if 'error' in message.lower():
reason = message
if reason == 'Success':
reason = message[0]
returncode = -1
else:
print('Ngspice warnings:')
for message in messages:
print(message)
return_code = sproc_retcode
if return_code != 0:
print('Execution error: Ngspice exited with error code ' + str(return_code))
if reason == 'Success':
reason = 'Ngspice exited with error code ' + str(return_code)
return (-2, reason)
if found:
return (returncode, reason)
else:
print('Result error: Expected result name ' + resultname + ' but did not receive it.')
reason = 'Expected result name ' + resultname + ' but did not receive it.'
return (-1, reason)
#------------------------------------------------------------------------------
# Read ".spice.in" file, replace DEVICENAME string with the device name,
# PDKVERSION with the version (v0.20.1), and CORNER with the simulation
# corner (tt), and write out as ".spice" file in "results" directory.
#------------------------------------------------------------------------------
def relpath(a, b):
"""
>>> relpath("a/c/1", "b/c")
"1"
>>> relpath("a/c/2", "a/b")
"../c/2"
>>> relpath(
... "/ssd/gob/foss-eda-tools/skywater-pdk-scratch-new/libraries/sky130_fd_pr/v0.20.1/cells/rf_pfet_01v8/tests/sky130_fd_pr__rf_pfet_01v8_aF02W0p84L0p15__pfet_vth.spice",
... "/ssd/gob/foss-eda-tools/skywater-pdk-scratch-new/skywater-pdk/libraries/sky130_fd_pr/v0.20.1/cells/rf_pfet_01v8/tests",
... )
"""
a = os.path.abspath(a).split('/')
b = os.path.abspath(b).split('/')
c = []
while a[0] == b[0]:
c = a.pop(0)
b.pop(0)
ac = a[len(c):]
bc = a[len(c):]
return os.path.join(['..']*len(bc)+ac)
def genspice(outdir, scripttemplate, devicename, includes, corner):
print('Generating simulation netlist for device ' + devicename)
templatefile = os.path.join(__dir__, scripttemplate+'.in')
assert os.path.exists(templatefile), templatefile
with open(templatefile) as ifile:
spicelines = ifile.read().splitlines()
# Some parameters to pass to the output
# Default works for low-voltage FET devices
# (More are needed---some are being shadowed by other errors)
if devicename == 'sky130_fd_pr__esd_nfet_01v8':
params = 'W=20.35 L=0.165 M=1'
elif devicename == 'sky130_fd_pr__esd_nfet_05v0_nvt':
params = 'W=10 L=2 M=1'
elif devicename == 'sky130_fd_pr__nfet_05v0_nvt':
params = 'W=10 L=2 M=1'
elif devicename == 'sky130_fd_pr__nfet_03v3_nvt':
params = 'W=10 L=0.5 M=1'
elif devicename == 'sky130_fd_pr__esd_nfet_03v3_nvt':
params = 'W=10 L=0.5 M=1'
elif devicename == 'sky130_fd_pr__esd_pfet_g5v0d10v5':
params = 'W=14.5 L=0.55 M=1'
elif devicename == 'sky130_fd_pr__esd_nfet_g5v0d10v5':
params = 'W=17.5 L=0.55 M=1'
elif devicename == 'sky130_fd_pr__pfet_01v8_mvt':
params = 'W=1.68 L=0.15 M=1'
elif devicename == 'sky130_fd_pr__pfet_01v8_lvt':
params = 'W=1.0 L=1.0 M=1'
elif devicename.startswith('sky130_fd_pr__nfet_g5v0d16v0'):
params = 'W=20 L=0.7 M=1'
elif devicename.startswith('sky130_fd_pr__pfet_g5v0d16v0'):
params = 'W=5 L=0.66 M=1'
elif devicename == 'sky130_fd_pr__pfet_g5v0d16v0':
params = 'W=20 L=0.8 M=1'
elif devicename == 'sky130_fd_pr__nfet_g5v0d16v0':
params = 'W=10 L=0.5 M=1'
elif devicename == 'sky130_fd_pr__special_nfet_pass_lvt':
params = 'W=0.3 L=0.15 M=1'
elif devicename == 'sky130_fd_pr__special_nfet_pass':
params = 'W=0.14 L=0.15 M=1'
elif devicename == 'sky130_fd_pr__special_pfet_pass':
params = 'W=0.14 L=0.15 M=1'
elif devicename == 'sky130_fd_pr__special_nfet_pass_flash':
params = 'W=0.45 L=0.15 M=1'
elif devicename == 'sky130_fd_pr__special_nfet_latch':
params = 'W=0.21 L=0.15 M=1'
elif devicename.startswith('sky130_fd_pr__nfet_20v0_nvt'):
params = 'W=20 L=1 M=1'
# sky130_fd_pr__pfet_01v8
elif devicename[-1] == 'v':
params = 'W=3.0 L=0.15 M=1'
# sky130_fd_pr__rf_pfet_01v8_aF02W0p84L0p15 -- no params
else:
params = ''
# More handling of subcircuits with partial parameter definitions
# and invalid defaults:
if 'bM' in devicename and not 'W' in devicename and not 'L' in devicename:
if '01v8' in devicename:
params = 'W=1.65 L=0.15'
elif '10v5' in devicename:
if 'nfet_g5v0d10v5_b' in devicename:
# The 5V nFET for some reason is characterized at 3.01um
params = 'W=3.01 L=0.5'
else:
params = 'W=3.0 L=0.5'
elif 'bM' in devicename and 'W' in devicename and not 'L' in devicename:
if '01v8' in devicename:
params = 'L=0.15'
elif '10v5' in devicename:
params = 'L=0.5'
foutpath = os.path.abspath(os.path.join(outdir, 'tests', devicename + '__' + scripttemplate))
foutdir = os.path.dirname(foutpath)
os.makedirs(foutdir, exist_ok=True)
includelines = []
for incfile in includes:
relincfile = os.path.relpath(incfile, foutdir)
if '.lib.spice' in incfile:
includelines.append('.lib "{}" {}'.format(relincfile, corner))
else:
includelines.append('.include "{}"'.format(relincfile))
outlines = []
for line in spicelines:
newline = line.replace('INCLUDELINES', '\n'.join(includelines))
newline = newline.replace('DEVICENAME', devicename)
newline = newline.replace('CORNER', corner)
newline = newline.replace('PARAMS', params)
outlines.append(newline)
with open(foutpath, 'w') as ofile:
for line in outlines:
print(line, file=ofile)
return foutpath
#------------------------------------------------------------------------------
# Run a spice simulation (or two) for the device specified in 'line' (obtained
# from the list of devices and expected values).
#------------------------------------------------------------------------------
def do_for_device(pdk_path, version, devicedir, devicename, corner):
passes = []
fails = []
ignores = []
baseline_run = False
baseline_entries = []
reasons = {}
# NOTE: When a line in the device list does not have an entry
# for expected value, then this is a baseline run, and will
# dump the device name and output measured to a file
# 'devices_baseline.txt'
baseline_run = True
# Determine the device type from the name
# (to do: Pull additional information about the device from the name)
# (also to do: separate nmos/pmos testbenches and pnp/npn testbenches)
# Inductors
# sky130_fd_pr__ind.*
if "__ind" in devicename:
devicetype = 'inductor'
# Capacitors
# sky130_fd_pr__cap_mim.*
# sky130_fd_pr__cap.*
elif "__cap_mim" in devicename:
devicetype = 'mimcap'
elif "__cap" in devicename:
devicetype = 'capacitor'
# Diodes (as subcircuits)
# sky130_fd_pr__esd_rf_diode.*
# Diodes (as SPICE primitive devices)
# sky130_fd_pr__diode.*
elif "_diode" in devicename:
devicetype = 'diode'
# MOSFETs (n-type)
# sky130_fd_pr__esd_nfet.*
# sky130_fd_pr__nfet.*
# sky130_fd_pr__rf_nfet.*
# sky130_fd_pr__special_nfet.*
elif "_nfet" in devicename:
devicetype = 'nfet'
# MOSFETs (p-type)
# sky130_fd_pr__esd_pfet.*
# sky130_fd_pr__pfet.*
# sky130_fd_pr__rf_pfet.*
# sky130_fd_pr__special_pfet.*
elif "_pfet" in devicename:
devicetype = 'pfet'
# Bipolars (NPN)
# sky130_fd_pr__rf_npn.*
# sky130_fd_pr__npn.*
elif "_npn" in devicename:
devicetype = 'npn'
# Bipolars (PNP)
# sky130_fd_pr__pnp.*
# sky130_fd_pr__rf_pnp.*
elif "_pnp" in devicename:
devicetype = 'pnp'
# Resistors
# sky130_fd_pr__res.*
elif "_res" in devicename:
devicetype = 'resistor'
else:
raise SystemError('Unknown device:'+devicename)
# Check list of devices for additional sub-types
if devicetype == 'nfet':
if devicename in five_pin_fets:
devicetype = 'nfet5term'
elif devicetype == 'capacitor':
if devicename in four_pin_caps:
devicetype = 'capacitor4pin'
#deps = {}
#provided_by = {}
#find_all_devices_new.get_deps_for_device(pdk_path, version, devicename, corner, deps, provided_by)
#assert devicename in provided_by, (devicename+'\n'+pprint.pformat(provided_by))
#includes = find_all_devices_new.flatten_deps_to_files(devicename, deps, provided_by)
top_dir = os.path.join(pdk_path, "skywater-pdk", "libraries", "sky130_fd_pr", version)
assert os.path.exists(top_dir), top_dir
includes = find_all_devices.do_find_all_devices(top_dir, None, devicename, feol=corner)
includes = [os.path.abspath(i) for i in includes]
# Special handling to include files that need to be included but are not
# handled properly by the various ad-hoc methods used in find_all_devices.py
includes = includes_special_handling(devicename, corner, includes)
print()
print("Includes needed:")
pprint.pprint(includes)
print()
try:
print('Diagnostic: Determined device ' + devicename + ' to be type ' + devicetype)
except:
print('Unknown device type for device name ' + devicename + '. Cannot simulate.')
reasons[devicename] = 'Unknown device type for device name ' + devicename + '. Cannot simulate.'
fails.append(devicename)
return (passes, fails, ignores, baseline_entries, reasons)
# Devices for which no include was found count as "ignores"
if includes == []:
ignores.append(devicename)
devicetype = 'ignored'
if baseline_run:
expectedval = 'none'
if devicetype == 'nfet' or devicetype == 'nfet5term' or devicetype == 'pfet':
spicefile = genspice(devicedir, devicetype + '_vth.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'threshold voltage', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
if result != -2:
make_mosfet_iv_plot(devicedir + '/tests', devicename, version, corner)
elif devicetype == 'npn' or devicetype == 'pnp':
spicefile = genspice(devicedir, devicetype + '.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'maximum beta', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
if result != -2:
make_bipolar_iv_plot(devicedir + '/tests', devicename, version, corner)
make_bipolar_beta_plot(devicedir + '/tests', devicename, version, corner)
elif devicetype == 'mimcap':
spicefile = genspice(devicedir, 'mimcap_test.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'capacitance', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
elif devicetype == 'capacitor':
spicefile = genspice(devicedir, 'capval_test.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'capacitance', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
elif devicetype == 'capacitor4pin':
spicefile = genspice(devicedir, 'cap4termval_test.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'capacitance', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
elif devicetype == 'inductor':
spicefile = genspice(devicedir, 'inductor_test.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'inductance', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
elif devicetype == 'resistor':
spicefile = genspice(devicedir, 'resval_test.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'resistance', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
elif devicetype == 'diode' or devicetype == 'diode_dev':
spicefile = genspice(devicedir, devicetype + '_vth.spice', devicename, includes, corner)
(result, reason) = runspice(spicefile, 'threshold voltage', devicename, expectedval)
reasons[devicename] = reason
if baseline_run and isinstance(result, str):
baseline_entries.append(devicename + ' ' + result)
passes.append(devicename)
elif result != 0:
fails.append(devicename)
else:
passes.append(devicename)
if result != -2:
make_diode_iv_plot(devicedir + '/tests', devicename, version, corner)
output = (passes, fails, ignores, baseline_entries, reasons)
bsim4v5_out = os.path.abspath(os.path.join(devicedir, 'tests', 'bsim4v5.out'))
if os.path.exists(bsim4v5_out):
os.unlink(bsim4v5_out)
foutpath = os.path.abspath(os.path.join(devicedir, 'tests', '%s_results.json' % devicename))
with open(foutpath, 'w') as f:
json.dump(dict(zip(('passes', 'fails', 'ignores', 'baseline_entries', 'reasons'), output)), f, sort_keys=True, indent=" ")
return output
#------------------------------------------------------------------------------
# Main script starts here (no arguments, at least for now)
#------------------------------------------------------------------------------
def main(pdk_path, version, device):
assert os.path.exists(pdk_path), pdk_path
prlib_path = os.path.join(pdk_path, "libraries", "sky130_fd_pr", version)
assert os.path.exists(prlib_path), prlib_path
devicedir = get_cell_directory(pdk_path, 'sky130_fd_pr', version, device)
bits = device.split('__')
assert len(bits) > 1, (bits, device)
assert bits.pop(0) == 'sky130_fd_pr', (bits, device)
ddir = os.path.join(prlib_path, "cells", bits.pop(0))
if bits:
corner = bits.pop(0)
else:
corner = 'tt'
# One-off problem: Some things pop up in the "corner" field that are not
# corners. . . Not sure that this is the best handling, but at least it
# produces a valid result.
if corner == 'subcell' or corner == 'base' or corner == 'parasitic':
corner = 'tt'
if device.endswith('__'+corner):
device = device[:-len('__'+corner)]
passes, fails, ignores, baseline_entries, reasons = do_for_device(
pdk_path, version, devicedir, device, corner)
if passes:
print("Pass:", passes)
if fails:
print("Fails:", fails)
if ignores:
print("Ignores:", fails)
if baseline_entries:
print("Baseline Entries:", baseline_entries)
if reasons:
print("Reasons:", reasons)
return len(fails)
if __name__ == "__main__":
args = list(sys.argv)
args.pop(0)
pdk_path = args.pop(0)
if args and args[0] and args[0][0] == 'v':
version = args.pop(0)
else:
version = 'v0.20.1'
assert len(args) == 1, args
sys.exit(main(pdk_path, version, args[0]))