blob: 3c09c374af47133d13e3990e1c1b52b5ddd50c17 [file] [log] [blame]
#!/usr/bin/env python3
#-----------------------------------------------------------
#
# Find all devices which have subcircuit definitions in the path
# skywater-pdk/libraries/sky130_fd_pr/VERSION/cells/. List all of
# these devices. Then find all paths from the directory models/
# that will read the subcircuit definition through a hierarchical
# series of includes.
#-----------------------------------------------------------
import re
import os
import pprint
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
SPICE_X_DEF = re.compile('^(x.*?)$', flags=re.MULTILINE|re.I)
def extract_x(data):
"""
>>> extract_x('''
... xsky130_fd_pr__pfet_01v8 d g s b sky130_fd_pr__pfet_01v8__model l = {l} w = {w} ad = {ad} as = {as} pd = {pd} ps = {ps} nrd = {nrd} nrs = {nrs} sa = {sa} sb = {sb} sd = {sd} nf = {nf}
... ''')
['sky130_fd_pr__pfet_01v8__model']
>>> extract_x('''
... X0 Drain Gate Source BULK sky130_fd_pr__pfet_01v8 w=840000u l=150000u
... ''')
['sky130_fd_pr__pfet_01v8']
"""
defs = []
for m in SPICE_X_DEF.finditer(data):
xdef = m.group(0).split()
i = 0
while i < len(xdef)-1:
if '=' in xdef[i+1]:
break
if i+2 < len(xdef) and xdef[i+2] == '=':
break
i += 1
defs.append(xdef[i])
return defs
incs = []
for m in SPICE_INC.finditer(data):
print('INC', m.group(1))
SPICE_MODEL = re.compile('^.model +([^.]+)(.[0-9]*)? (.*?)$', flags=re.MULTILINE)
SPICE_SUBCKT = re.compile('^.subckt +([^ ]+) (.*?)$', flags=re.MULTILINE)
def extract_defines(data):
"""
>>> extract_defines('''
... .subckt sky130_fd_pr__rf_nfet_g5v0d10v5_bM02 d g s b
... ''')
['sky130_fd_pr__rf_nfet_g5v0d10v5_bM02']
>>> extract_defines('''
... .model sky130_fd_pr__nfet_05v0_nvt__model.9 nmos
... .model sky130_fd_pr__nfet_05v0_nvt__model.10 nmos
... ''')
['sky130_fd_pr__nfet_05v0_nvt__model']
"""
defines = set()
for m in SPICE_MODEL.finditer(data):
defines.add(m.group(1))
for m in SPICE_SUBCKT.finditer(data):
defines.add(m.group(1))
defines = list(defines)
defines.sort()
return defines
RE_INCLUDE = re.compile('^.include "([^"]*)"$', flags=re.MULTILINE)
def flatten(f):
fdir = os.path.dirname(f)
data = open(f).read()
odata = []
oend = 0
for m in RE_INCLUDE.finditer(data):
odata.append(data[oend:m.start(0)])
incf = m.group('f')
if not incf:
raise IOError('invalid-include: {!r} in {!r}'.format(m.group(0), f))
incpath = os.path.abspath(os.path.join(fdir, incf))
if not os.path.isfile(incpath):
raise FileNotFoundError('invalid-include: {!r} in {!r}'.format(incpath, f))
try:
odata.append(flatten(incpath))
except IOError as e:
raise e.__class__(str(e) + '\n(included in {!r} byte:{})'.format(f, m.start(0)))
oend = m.end(0)
odata.append(data[oend:])
odata = ''.join(odata)
m = RE_INCLUDE.search(odata)
assert not m, m
return odata
def get_deps_for_device(pdk_path, version, device, corner, dependencies, provided_by):
devicedir = get_cell_directory(pdk_path, 'sky130_fd_pr', version, device)
print("Device", device, "found in", devicedir)
if corner:
if device.endswith('__'+corner):
device = device[:-len('__'+corner)]
spicefiles = [f for f in os.listdir(devicedir) if f.startswith(device) and f.endswith('.spice')]
spicefiles.sort()
corner_spicefiles = [f for f in spicefiles if f.startswith(device+'__')]
#if corner:
# topfile_prefix = device+'__'+corner+'.'
#else:
topfile_prefix = device+'.'
top_spicefiles = [f for f in spicefiles if f.startswith(topfile_prefix)]
if len(top_spicefiles) != 1:
print(device, "Spice files:")
print(spicefiles)
print(device, "Top:")
pprint.pprint(top_spicefiles)
print(device, "Corner:")
pprint.pprint(corner_spicefiles)
assert len(top_spicefiles) == 1, top_spicefiles
top_spice_file = top_spicefiles[0]
top_spice_path = os.path.abspath(os.path.join(devicedir, top_spice_file))
top_spice_data = flatten(top_spice_path)
provides = extract_defines(top_spice_data)
deps = extract_x(top_spice_data)
assert device in provides, "{} not found in {}\n---\n{}".format(
device, top_spice_file, pprint.pformat(provides))
for d in provides:
assert d not in provided_by, '{} in {} already provided by {}\n---\n{}'.format(
d, top_spicefile, provided_by[d], pprint.pformat(provided_by))
provided_by[d] = top_spice_path
assert d not in dependencies, device+'\n'+pprint.pformat(dependencies)
dependencies[d] = deps
to_load = set()
for d in deps:
if d not in provided_by:
to_load.add(d)
for d in to_load:
if d in provided_by:
continue
get_deps_for_device(pdk_path, version, d, corner, dependencies, provided_by)
return
def flatten_deps_to_files(device, dependencies, provided_by):
files = []
devices = [device]
while devices:
d = devices.pop(0)
assert d in provided_by, d+'\n'+pprint.pformat(provided_by)
if provided_by[d] in files:
continue
files.append(provided_by[d])
assert d in dependencies, d+'\n'+pprint.pformat(dependencies)
devices.extend(dependencies[d])
files.reverse()
return files
def main(pdk_path, version='v0.20.1', device=None):
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)
assert '__' not in devicedir, devicedir
bits = device.split('__')
assert len(bits) > 1, (bits, device)
assert bits.pop(0) == 'sky130_fd_pr', (bits, device)
bits.pop(0) # Device directory
if not bits:
corner = 'tt'
else:
corner = bits.pop(0)
if device.endswith('__'+corner):
device = device[:-len('__'+corner)]
print(devicedir, device, corner)
dependencies = {}
provided_by = {}
get_deps_for_device(pdk_path, version, device, corner, dependencies, provided_by)
print()
print()
print(devicedir, device, corner)
pprint.pprint(dependencies)
pprint.pprint(provided_by)
print('------')
pprint.pprint(flatten_deps_to_files(device, dependencies, provided_by))
#-----------------------------------------------------------
# Command-line entry point
#-----------------------------------------------------------
if __name__ == "__main__":
import doctest
fails, _ = doctest.testmod()
if fails != 0:
sys.exit("Some test failed!")
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, device=args[0]))
optionlist = []
arguments = []
for option in sys.argv[1:]:
if option.find('-', 0) == 0:
optionlist.append(option)
else:
arguments.append(option)
# Defaults: Set up for the most recent PDK version.
version = 'v0.20.1'
pathtop = '../../libraries/sky130_fd_pr/' + version
# Default FEOL corner is "tt", and default BEOL corner is "tt"
feol = 'tt'
beol = 'tt'
cellname = None
debug = False
doall = False
notop = False
# Override defaults from options
for option in optionlist:
if option.startswith('-version'):
try:
version = option.split('=')[1]
except:
print('Option usage: -version=<versionstring>')
sys.exit(1)
elif option.startswith('-corner') or option.startswith('-feol'):
try:
feol = option.split('=')[1]
except:
print('Option usage: -feol=<corner_name>')
sys.exit(1)
elif option.startswith('-beol'):
try:
beol = option.split('=')[1]
except:
print('Option usage: -beol=<corner_name>')
sys.exit(1)
elif option.startswith('-cell'):
try:
cellname = option.split('=')[1]
except:
print('Option usage: -cell=<cell_name>')
sys.exit(1)
elif option == '-notop':
notop = True
elif option == '-all':
doall = True
elif option == '-debug':
debug = True
# Parse "-pdkpath" after the others because it is dependent on any option
# "-version" passed on the command line.
for option in optionlist:
if option.startswith('-pdkpath'):
try:
pathroot = option.split('=')[1]
except:
print('Option usage: -pdkpath=<pathname>')
sys.exit(1)
if not os.path.isdir(pathroot):
print('Cannot find PDK directory ' + pathroot)
sys.exit(1)
pathtop = pathroot + '/libraries/sky130_fd_pr/' + version
if not os.path.isdir(pathtop):
print('Cannot find primitive device directory ' + pathtop)
sys.exit(1)
# To be done: Make this a useful routine that can insert one or more
# .include statements into a SPICE netlist. Should take any number of
# files on the arguments line and modify the files in place.
if len(arguments) > 0:
sourcefile = arguments[0]
if not os.path.isfile(sourcefile):
print('Cannot read SPICE source file ' + sourcefile)
sys.exit(1)
else:
sourcefile = None
if not os.path.isdir(pathtop):
print('Cannot find PDK path top level directory ' + pathtop)
sys.exit(1)
elif debug:
print('\nFinding everything in ' + pathtop + '.')
incfiles = do_find_all_devices(pathtop, sourcefile, cellname, feol, beol, doall, notop, debug)
for incfile in incfiles:
if incfile.endswith('.lib.spice'):
print('.lib ' + incfile + ' ' + feol)
else:
print('.include "' + incfile + '"')
sys.exit(0)