blob: 20676639827edc112e434b2247725ef0560ad437 [file] [log] [blame]
#!/usr/bin/env python3
#
# foundry_install.py
#
# This file generates the local directory structure and populates the
# directories with foundry vendor data. The local directory (target)
# should be a staging area, not a place where files are kept permanently.
#
# Options:
# -ef_format Use efabless naming (libs.ref/techLEF),
# otherwise use generic naming (libs.tech/lef)
# -clean Clear out and remove target directory before starting
# -source <path> Path to source data top level directory
# -target <path> Path to target (staging) top level directory
#
# All other options represent paths to vendor files. They may all be
# wildcarded with "*", or with specific escapes like "%l" for library
# name or "%v" for version number (see below for a complete list of escape
# sequences).
#
# Note only one of "-spice" or "-cdl" need be specified. Since the
# open source tools use ngspice, CDL files are converted to ngspice
# syntax when needed.
#
# -techlef <path> Path to technology LEF file
# -doc <path> Path to technology documentation
# -lef <path> Path to LEF file
# -spice <path> Path to SPICE netlists
# -cdl <path> Path to CDL netlists
# -models <path> Path to SPICE (primitive device) models
# -liberty <path> Path to Liberty timing files
# -gds <path> Path to GDS data
# -verilog <path> Path to verilog models
#
# -library <type> <name> [<target>] See below
#
# For the "-library" option, any number of libraries may be supported, and
# one "-library" option should be provided for each supported library.
# <type> is one of: "digital", "primitive", or "general". Analog and I/O
# libraries fall under the category "general", as they are all treated the
# same way. <name> is the vendor name of the library. [<target>] is the
# (optional) local name of the library. If omitted, then the vendor name
# is used for the target (there is no particular reason to specify a
# different local name for a library).
#
# In special cases using options (see below), path may be "-", indicating
# that there are no source files, but only to run compilations or conversions
# on the files in the target directory.
#
# All options "-lef", "-spice", etc., can take the additional arguments
# up <number>
#
# to indicate that the source hierarchy should be copied from <number>
# levels above the files. For example, if liberty files are kept in
# multiple directories according to voltage level, then
#
# -liberty x/y/z/PVT_*/*.lib
#
# would install all .lib files directly into libs.ref/<libname>/liberty/*.lib
# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/*.lib)
# while
#
# -liberty x/y/z/PVT_*/*.lib up 1
#
# would install all .lib files into libs.ref/liberty/<libname>/PVT_*/*.lib
# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/PVT_*/*.lib)
#
# Please note that the INSTALL variable in the Makefile starts with "set -f"
# to suppress the OS from doing wildcard substitution; otherwise the
# wildcards in the install options will get expanded by the OS before
# being passed to the install script.
#
# Other library-specific arguments are:
#
# nospec : Remove timing specification before installing
# (used with verilog files; needs to be extended to
# liberty files)
# compile : Create a single library from all components. Used
# when a foundry library has inconveniently split
# an IP library (LEF, CDL, verilog, etc.) into
# individual files.
# stub : Remove contents of subcircuits from CDL or SPICE
# netlist files.
#
# priv : Mark the contents being installed as privleged, and
# put them in a separate root directory libs.priv
# where they can be given additional read/write
# restrictions.
#
# exclude : Followed by "=" and a comma-separated list of names.
# exclude these files/modules/subcircuits. Names may
# also be wildcarded in "glob" format.
#
# rename : Followed by "=" and an alternative name. For any
# file that is a single entry, change the name of
# the file in the target directory to this (To-do:
# take regexps for multiple files). When used with
# "compile" or "compile-only", this refers to the
# name of the target compiled file.
#
# noconvert : Install only; do not attempt to convert to other
# formats (applies only to GDS, CDL, and LEF).
#
# NOTE: This script can be called once for all libraries if all file
# types (gds, cdl, lef, etc.) happen to all work with the same wildcards.
# However, it is more likely that it will be called several times for the
# same PDK, once to install I/O cells, once to install digital, and so
# forth, as made possible by the wild-carding.
import re
import os
import sys
import glob
import stat
import shutil
import fnmatch
import subprocess
def usage():
print("foundry_install.py [options...]")
print(" -copy Copy files from source to target (default)")
print(" -ef_format Use efabless naming conventions for local directories")
print("")
print(" -source <path> Path to top of source directory tree")
print(" -target <path> Path to top of target directory tree")
print("")
print(" -techlef <path> Path to technology LEF file")
print(" -doc <path> Path to technology documentation")
print(" -lef <path> Path to LEF file")
print(" -spice <path> Path to SPICE netlists")
print(" -cdl <path> Path to CDL netlists")
print(" -models <path> Path to SPICE (primitive device) models")
print(" -lib <path> Path to Liberty timing files")
print(" -liberty <path> Path to Liberty timing files")
print(" -gds <path> Path to GDS data")
print(" -verilog <path> Path to verilog models")
print(" -library <type> <name> [<target>] See below")
print("")
print(" All <path> names may be wild-carded with '*' ('glob'-style wild-cards)")
print("")
print(" All options with <path> other than source and target may take the additional")
print(" arguments 'up <number>', where <number> indicates the number of levels of")
print(" hierarchy of the source path to include when copying to the target.")
print("")
print(" Library <type> may be one of:")
print(" digital Digital standard cell library")
print(" primitive Primitive device library")
print(" general All other library types (I/O, analog, etc.)")
print("")
print(" If <target> is unspecified then <name> is used for the target.")
# Return a list of files after glob-style substituting into pathname. This
# mostly relies on glob.glob(), but uses the additional substitutions with
# escape strings:
#
# %v : Match a version number in the form "major[.minor[.rev]]"
# %l : substitute the library name
# %% : substitute the percent character verbatim
from distutils.version import LooseVersion
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def makeuserwritable(filepath):
if os.path.exists(filepath):
st = os.stat(filepath)
os.chmod(filepath, st.st_mode | stat.S_IWUSR)
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def substitute(pathname, library):
if library:
# Do %l substitution
newpathname = re.sub('%l', library, pathname)
else:
newpathname = pathname
if '%v' in newpathname:
vglob = re.sub('%v.*', '*', newpathname)
vlibs = glob.glob(vglob)
try:
vstr = vlibs[0][len(vglob)-1:]
except IndexError:
pass
else:
for vlib in vlibs[1:]:
vtest = vlib[len(vglob)-1:]
if LooseVersion(vtest) > LooseVersion(vstr):
vstr = vtest
newpathname = re.sub('%v', vstr, newpathname)
if '%%' in newpathname:
newpathname = re.sub('%%', '%', newpathname)
return newpathname
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def get_gds_properties(magfile):
proprex = re.compile('^[ \t]*string[ \t]+(GDS_[^ \t]+)[ \t]+([^ \t]+)$')
proplines = []
if os.path.isfile(magfile):
with open(magfile, 'r') as ifile:
magtext = ifile.read().splitlines()
for line in magtext:
lmatch = proprex.match(line)
if lmatch:
propline = lmatch.group(1) + ' ' + lmatch.group(2)
proplines.append(propline)
return proplines
#----------------------------------------------------------------------------
# Read subcircuit ports from a CDL file, given a subcircuit name that should
# appear in the file as a subcircuit entry, and return a dictionary of ports
# and their indexes in the subcircuit line.
#----------------------------------------------------------------------------
def get_subckt_ports(cdlfile, subname):
portdict = {}
pidx = 1
portrex = re.compile('^\.subckt[ \t]+([^ \t]+)[ \t]+(.*)$', flags=re.IGNORECASE)
with open(cdlfile, 'r') as ifile:
cdltext = ifile.read()
cdllines = cdltext.replace('\n+', ' ').splitlines()
for line in cdllines:
lmatch = portrex.match(line)
if lmatch:
if lmatch.group(1).lower() == subname.lower():
ports = lmatch.group(2).split()
for port in ports:
portdict[port.lower()] = pidx
pidx += 1
break
return portdict
#----------------------------------------------------------------------------
# Filter a verilog file to remove any backslash continuation lines, which
# iverilog does not parse. If targetroot is a directory, then find and
# process all files in the path of targetroot. If any file to be processed
# is unmodified (has no backslash continuation lines), then ignore it. If
# any file is a symbolic link and gets modified, then remove the symbolic
# link before overwriting with the modified file.
#----------------------------------------------------------------------------
def vfilefilter(vfile):
modified = False
with open(vfile, 'r') as ifile:
vtext = ifile.read()
# Remove backslash-followed-by-newline and absorb initial whitespace. It
# is unclear what initial whitespace means in this context, as the use-
# case that has been seen seems to work under the assumption that leading
# whitespace is ignored up to the amount used by the last indentation.
vlines = re.sub('\\\\\n[ \t]*', '', vtext)
if vlines != vtext:
# File contents have been modified, so if this file was a symbolic
# link, then remove it. Otherwise, overwrite the file with the
# modified contents.
if os.path.islink(vfile):
os.unlink(vfile)
with open(vfile, 'w') as ofile:
ofile.write(vlines)
#----------------------------------------------------------------------------
# Run a filter on verilog files that cleans up known syntax issues.
# This is embedded in the foundry_install script and is not a custom
# filter largely because the issue is in the tool, not the PDK.
#----------------------------------------------------------------------------
def vfilter(targetroot):
if os.path.isfile(targetroot):
vfilefilter(targetroot)
else:
vlist = glob.glob(targetroot + '/*')
for vfile in vlist:
if os.path.isfile(vfile):
vfilefilter(vfile)
#----------------------------------------------------------------------------
# For issues that are PDK-specific, a script can be written and put in
# the PDK's custom/scripts/ directory, and passed to the foundry_install
# script using the "filter" option.
#----------------------------------------------------------------------------
def tfilter(targetroot, filterscript, outfile=[]):
filterroot = os.path.split(filterscript)[1]
if os.path.isfile(targetroot):
print(' Filtering file ' + targetroot + ' with ' + filterroot)
sys.stdout.flush()
if not outfile:
outfile = targetroot
else:
# Make sure this file is writable (as the original may not be)
makeuserwritable(outfile)
fproc = subprocess.run([filterscript, targetroot, outfile],
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, universal_newlines = True)
if fproc.stdout:
for line in fproc.stdout.splitlines():
print(line)
if fproc.stderr:
print('Error message output from filter script:')
for line in fproc.stderr.splitlines():
print(line)
else:
tlist = glob.glob(targetroot + '/*')
for tfile in tlist:
if os.path.isfile(tfile):
print(' Filtering file ' + tfile + ' with ' + filterroot)
sys.stdout.flush()
fproc = subprocess.run([filterscript, tfile, tfile],
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, universal_newlines = True)
if fproc.stdout:
for line in fproc.stdout.splitlines():
print(line)
if fproc.stderr:
print('Error message output from filter script:')
for line in fproc.stderr.splitlines():
print(line)
#----------------------------------------------------------------------------
# Given a destination directory holding individual verilog files of a number
# of modules, create a single verilog library file named <alllibname> and place
# it in the same directory. This is done for the option "compile" if specified
# for the "-verilog" install.
#----------------------------------------------------------------------------
def create_verilog_library(destlibdir, destlib, do_compile_only, do_stub, excludelist):
alllibname = destlibdir + '/' + destlib + '.v'
if os.path.isfile(alllibname):
os.remove(alllibname)
print('Diagnostic: Creating consolidated verilog library ' + destlib + '.v')
vlist = glob.glob(destlibdir + '/*.v')
if alllibname in vlist:
vlist.remove(alllibname)
# Create exclude list with glob-style matching using fnmatch
if len(vlist) > 0:
vlistnames = list(os.path.split(item)[1] for item in vlist)
notvlist = []
for exclude in excludelist:
notvlist.extend(fnmatch.filter(vlistnames, exclude))
# Apply exclude list
if len(notvlist) > 0:
for file in vlist[:]:
if os.path.split(file)[1] in notvlist:
vlist.remove(file)
if len(vlist) > 1:
print('New file is: ' + alllibname)
with open(alllibname, 'w') as ofile:
allmodules = []
for vfile in vlist:
with open(vfile, 'r') as ifile:
# print('Adding ' + vfile + ' to library.')
vtext = ifile.read()
modules = re.findall(r'[ \t\n]module[ \t]+([^ \t\n\(]+)', vtext)
mseen = list(item for item in modules if item in allmodules)
allmodules.extend(list(item for item in modules if item not in allmodules))
vfilter = remove_redundant_modules(vtext, allmodules, mseen)
# NOTE: The following workaround resolves an issue with iverilog,
# which does not properly parse specify timing paths that are not in
# parentheses. Easy to work around
vlines = re.sub(r'\)[ \t]*=[ \t]*([01]:[01]:[01])[ \t]*;', r') = ( \1 ) ;', vfilter)
print(vlines, file=ofile)
print('\n//--------EOF---------\n', file=ofile)
if do_compile_only == True:
print('Compile-only: Removing individual verilog files')
for vfile in vlist:
if os.path.isfile(vfile):
os.remove(vfile)
elif os.path.islink(vfile):
os.unlink(vfile)
else:
print('Only one file (' + str(vlist) + '); ignoring "compile" option.')
#----------------------------------------------------------------------------
# Remove redundant module entries from a verilog file. "m2list" is a list of
# module names gleaned from all previously read files using re.findall().
# "mlist" is a list of all module names including those in "ntext".
# The reason for doing this is that some verilog files may includes modules used
# by all the files, and if included more than once, then iverilog complains.
#----------------------------------------------------------------------------
def remove_redundant_modules(ntext, mlist, m2list):
updated = ntext
for module in mlist:
# Determine the number of times the module appears in the text
if module in m2list:
# This module seen before outside of ntext, so remove all occurrances in ntext
new = re.sub(r'[ \t\n]+module[ \t]+' + module + '[ \t\n\(]+.*[ \t\n]endmodule', '\n', updated, flags=re.DOTALL)
updated = new
else:
n = len(re.findall(r'[ \t\n]module[ \t]+' + module + '[ \t\n\(]+.*[ \t\n]endmodule', updated, flags=re.DOTALL))
# This module defined more than once inside ntext, so remove all but one
# Optimization: Just keep original text if n < 2
if n < 2:
continue
# Remove all but one
updated = re.sub(r'[ \t\n]+module[ \t]+' + module + '[ \t\n]+.*[ \t\n]endmodule', '\n', n - 1, updated, flags=re.IGNORECASE | re.DOTALL)
return updated
#----------------------------------------------------------------------------
# Given a destination directory holding individual LEF files of a number
# of cells, create a single LEF library file named <alllibname> and place
# it in the same directory. This is done for the option "compile" if specified
# for the "-lef" install.
#----------------------------------------------------------------------------
def create_lef_library(destlibdir, destlib, do_compile_only, excludelist):
alllibname = destlibdir + '/' + destlib + '.lef'
if os.path.isfile(alllibname):
os.remove(alllibname)
print('Diagnostic: Creating consolidated LEF library ' + destlib + '.lef')
llist = glob.glob(destlibdir + '/*.lef')
if alllibname in llist:
llist.remove(alllibname)
# Create exclude list with glob-style matching using fnmatch
if len(llist) > 0:
llistnames = list(os.path.split(item)[1] for item in llist)
notllist = []
for exclude in excludelist:
notllist.extend(fnmatch.filter(llistnames, exclude))
# Apply exclude list
if len(notllist) > 0:
for file in llist[:]:
if os.path.split(file)[1] in notllist:
llist.remove(file)
if len(llist) > 1:
print('New file is: ' + alllibname)
with open(alllibname, 'w') as ofile:
headerdone = False
for lfile in llist:
with open(lfile, 'r') as ifile:
# print('Adding ' + lfile + ' to library.')
ltext = ifile.read()
llines = ltext.splitlines()
headerseen = False
for lline in llines:
if headerdone:
if not headerseen:
if not lline.startswith('MACRO'):
continue
else:
headerseen = True
print(lline, file=ofile)
headerdone = True
print('#--------EOF---------\n', file=ofile)
if do_compile_only == True:
print('Compile-only: Removing individual LEF files')
for lfile in llist:
if os.path.isfile(lfile):
os.remove(lfile)
if newname:
if os.path.isfile(newname):
os.remove(newname)
else:
print('Only one file (' + str(llist) + '); ignoring "compile" option.')
#----------------------------------------------------------------------------
# Given a destination directory holding individual liberty files of a number
# of cells, create a single liberty library file named <alllibname> and place
# it in the same directory. This is done for the option "compile" if specified
# for the "-lib" install.
#----------------------------------------------------------------------------
# Warning: This script is unfinished. Needs to parse the library header
# in each cell and generate a new library header combining the contents of
# all cell headers. Also: The library name in the header needs to be
# changed to the full library name. Also: There is no mechanism for
# collecting all files belonging to a single process corner/temperature/
# voltage.
def create_lib_library(destlibdir, destlib, do_compile_only, excludelist):
alllibname = destlibdir + '/' + destlib + '.lib'
if os.path.isfile(alllibname):
os.remove(alllibname)
print('Diagnostic: Creating consolidated liberty library ' + destlib + '.lib')
# Create exclude list with glob-style matching using fnmatch
if len(llist) > 0:
llistnames = list(os.path.split(item)[1] for item in llist)
notllist = []
for exclude in excludelist:
notllist.extend(fnmatch.filter(llistnames, exclude))
# Apply exclude list
if len(notllist) > 0:
for file in llist[:]:
if os.path.split(file)[1] in notllist:
llist.remove(file)
if len(llist) > 1:
print('New file is: ' + alllibname)
with open(alllibname, 'w') as ofile:
headerdone = False
for lfile in llist:
with open(lfile, 'r') as ifile:
# print('Adding ' + lfile + ' to library.')
ltext = ifile.read()
llines = ltext.splitlines()
headerseen = False
for lline in llines:
if headerdone:
if not headerseen:
if not lline.split()[0] == 'cell':
continue
else:
headerseen = True
print(lline, file=ofile)
headerdone = True
print('/*--------EOF---------*/\n', file=ofile)
if do_compile_only == True:
print('Compile-only: Removing individual LEF files')
for lfile in llist:
if os.path.isfile(lfile):
os.remove(lfile)
if newname:
if os.path.isfile(newname):
os.remove(newname)
else:
print('Only one file (' + str(llist) + '); ignoring "compile" option.')
#----------------------------------------------------------------------------
# Given a destination directory holding individual GDS files of a number
# of cells, create a single GDL library file named <alllibname> and place
# it in the same directory. This is done for the option "compile" if specified
# for the "-gds" install.
#----------------------------------------------------------------------------
def create_gds_library(destlibdir, destlib, startup_script, do_compile_only, excludelist):
alllibname = destlibdir + '/' + destlib + '.gds'
if os.path.isfile(alllibname):
os.remove(alllibname)
print('Diagnostic: Creating consolidated GDS library ' + destlib + '.gds')
glist = glob.glob(destlibdir + '/*.gds')
glist.extend(glob.glob(destlibdir + '/*.gdsii'))
glist.extend(glob.glob(destlibdir + '/*.gds2'))
if alllibname in glist:
glist.remove(alllibname)
# Create exclude list with glob-style matching using fnmatch
if len(glist) > 0:
glistnames = list(os.path.split(item)[1] for item in glist)
notglist = []
for exclude in excludelist:
notglist.extend(fnmatch.filter(glistnames, exclude))
# Apply exclude list
if len(notglist) > 0:
for file in glist[:]:
if os.path.split(file)[1] in notglist:
glist.remove(file)
if len(glist) > 1:
print('New file is: ' + alllibname)
if os.path.isfile(startup_script):
# If the symbolic link exists, remove it.
if os.path.isfile(destlibdir + '/.magicrc'):
os.remove(destlibdir + '/.magicrc')
os.symlink(startup_script, destlibdir + '/.magicrc')
# A GDS library is binary and requires handling in Magic
print('Creating magic generation script to generate GDS library.')
with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
print('#!/usr/bin/env wish', file=ofile)
print('#--------------------------------------------', file=ofile)
print('# Script to generate .gds library from files ', file=ofile)
print('#--------------------------------------------', file=ofile)
print('drc off', file=ofile)
print('gds readonly true', file=ofile)
print('gds flatten true', file=ofile)
print('gds rescale false', file=ofile)
print('tech unlock *', file=ofile)
for gdsfile in glist:
print('gds read ' + gdsfile, file=ofile)
print('puts stdout "Creating cell ' + destlib + '"', file=ofile)
print('load ' + destlib, file=ofile)
print('puts stdout "Adding cells to library"', file=ofile)
print('box values 0 0 0 0', file=ofile)
for gdsfile in glist:
gdsroot = os.path.split(gdsfile)[1]
gdsname = os.path.splitext(gdsroot)[0]
print('getcell ' + gdsname, file=ofile)
# Could properly make space for the cell here. . .
print('box move e 200', file=ofile)
print('puts stdout "Writing GDS file ' + destlib + '"', file=ofile)
print('gds write ' + destlib, file=ofile)
print('puts stdout "Done."', file=ofile)
print('quit -noprompt', file=ofile)
# Run magic to read in the individual GDS files and
# write out the consolidated GDS library
print('Running magic to create GDS library.')
sys.stdout.flush()
mproc = subprocess.run(['magic', '-dnull', '-noconsole',
destlibdir + '/generate_magic.tcl'],
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = destlibdir,
universal_newlines = True)
if mproc.stdout:
for line in mproc.stdout.splitlines():
print(line)
if mproc.stderr:
print('Error message output from magic:')
for line in mproc.stderr.splitlines():
print(line)
if mproc.returncode != 0:
print('ERROR: Magic exited with status ' + str(mproc.returncode))
if do_compile_only == True:
print('Compile-only: Removing individual GDS files')
for gfile in glist:
if os.path.isfile(gfile):
os.remove(gfile)
if newname:
if os.path.isfile(newname):
os.remove(newname)
else:
print('Only one file (' + str(glist) + '); ignoring "compile" option.')
#----------------------------------------------------------------------------
# Given a destination directory holding individual SPICE netlists of a number
# of cells, create a single SPICE library file named <alllibname> and place
# it in the same directory. This is done for the option "compile" if specified
# for the "-spice" install.
#----------------------------------------------------------------------------
def create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist):
fformat = 'CDL' if spiext == '.cdl' else 'SPICE'
allstubname = destlibdir + '/stub' + spiext
alllibname = destlibdir + '/' + destlib + spiext
if do_stub:
outputname = allstubname
else:
outputname = alllibname
print('Diagnostic: Creating consolidated ' + fformat + ' library ' + outputname)
if os.path.isfile(outputname):
os.remove(outputname)
if fformat == 'CDL':
slist = glob.glob(destlibdir + '/*.cdl')
else:
# Sadly, there is no consensus on what a SPICE file extension should be.
slist = glob.glob(destlibdir + '/*.spc')
slist.extend(glob.glob(destlibdir + '/*.spice'))
slist.extend(glob.glob(destlibdir + '/*.spi'))
slist.extend(glob.glob(destlibdir + '/*.ckt'))
if alllibname in slist:
slist.remove(alllibname)
if allstubname in slist:
slist.remove(allstubname)
# Create exclude list with glob-style matching using fnmatch
if len(slist) > 0:
slistnames = list(os.path.split(item)[1] for item in slist)
notslist = []
for exclude in excludelist:
notslist.extend(fnmatch.filter(slistnames, exclude))
# Apply exclude list
if len(notslist) > 0:
for file in slist[:]:
if os.path.split(file)[1] in notslist:
slist.remove(file)
if len(slist) > 1:
with open(outputname, 'w') as ofile:
allsubckts = []
for sfile in slist:
with open(sfile, 'r') as ifile:
# print('Adding ' + sfile + ' to library.')
stext = ifile.read()
subckts = re.findall(r'\.subckt[ \t]+([^ \t\n]+)', stext, flags=re.IGNORECASE)
sseen = list(item for item in subckts if item in allsubckts)
allsubckts.extend(list(item for item in subckts if item not in allsubckts))
sfilter = remove_redundant_subckts(stext, allsubckts, sseen)
print(sfilter, file=ofile)
print('\n******* EOF\n', file=ofile)
if do_compile_only == True:
print('Compile-only: Removing individual SPICE files')
for sfile in slist:
if os.path.isfile(sfile):
os.remove(sfile)
elif os.path.islink(sfile):
os.unlink(sfile)
else:
print('Only one file (' + str(slist) + '); ignoring "compile" option.')
#----------------------------------------------------------------------------
# Remove redundant subcircuit entries from a SPICE or CDL netlist file. "sseen"
# is a list of subcircuit names gleaned from all previously read files using
# re.findall(). "slist" is a list of subcircuits including those in "ntext".
# If a subcircuit is defined outside of "ntext", then remove all occurrences in
# "ntext". Otherwise, if a subcircuit is defined more than once in "ntext",
# remove all but one copy. The reason for doing this is that some netlists will
# include primitive device definitions used by all the standard cell subcircuits.
#
# It may be necessary to remove redundant .include statements and redundant .model
# and/or .option statements as well.
#----------------------------------------------------------------------------
def remove_redundant_subckts(ntext, slist, sseen):
updated = ntext
for subckt in slist:
if subckt in sseen:
# Remove all occurrences of subckt
updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', updated, flags=re.IGNORECASE | re.DOTALL)
else:
# Determine the number of times the subcircuit appears in the text
n = len(re.findall(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', updated, flags=re.IGNORECASE | re.DOTALL))
# Optimization: Just keep original text if n < 2
if n < 2:
continue
# Remove all but one
updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', n - 1, updated, flags=re.IGNORECASE | re.DOTALL)
return updated
#----------------------------------------------------------------------------
# This is the main entry point for the foundry install script.
#----------------------------------------------------------------------------
if __name__ == '__main__':
if len(sys.argv) == 1:
print("No options given to foundry_install.py.")
usage()
sys.exit(0)
optionlist = []
newopt = []
sourcedir = None
targetdir = None
ef_format = False
do_clean = False
have_lef = False
have_techlef = False
have_lefanno = False
have_gds = False
have_spice = False
have_cdl = False
have_verilog = False
have_lib = False
# Break arguments into groups where the first word begins with "-".
# All following words not beginning with "-" are appended to the
# same list (optionlist). Then each optionlist is processed.
# Note that the first entry in optionlist has the '-' removed.
for option in sys.argv[1:]:
if option.find('-', 0) == 0:
if newopt != []:
optionlist.append(newopt)
newopt = []
newopt.append(option[1:])
else:
newopt.append(option)
if newopt != []:
optionlist.append(newopt)
# Pull library names from optionlist
libraries = []
for option in optionlist[:]:
if option[0] == 'library':
optionlist.remove(option)
libraries.append(option[1:])
# Check for option "ef_format" or "std_format" or "clean"
for option in optionlist[:]:
if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
optionlist.remove(option)
ef_format = True
elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
optionlist.remove(option)
ef_format = False
elif option[0] == 'clean':
do_clean = True
# Check for options "source" and "target"
for option in optionlist[:]:
if option[0] == 'source':
optionlist.remove(option)
sourcedir = option[1]
elif option[0] == 'target':
optionlist.remove(option)
targetdir = option[1]
if not targetdir:
print("No target directory specified. Exiting.")
sys.exit(1)
# Take the target PDK name from the target path last component
pdkname = os.path.split(targetdir)[1]
# If targetdir (the staging area) exists, make sure it's empty.
if os.path.isdir(targetdir):
# Error if targetdir exists but is not writeable
if not os.access(targetdir, os.W_OK):
print("Target installation directory " + targetdir + " is not writable.")
sys.exit(1)
# Clear out the staging directory if specified
if do_clean:
shutil.rmtree(targetdir)
elif os.path.exists(targetdir):
print("Target installation directory " + targetdir + " is not a directory.")
sys.exit(1)
# Error if no source or dest specified unless "-clean" was specified
if not sourcedir:
if do_clean:
print("Done removing staging area.")
sys.exit(0)
else:
print("No source directory specified. Exiting.")
sys.exit(1)
# Create the target directory
os.makedirs(targetdir, exist_ok=True)
#----------------------------------------------------------------
# Installation part 1: Install files into the staging directory
#----------------------------------------------------------------
# Diagnostic
print("Installing in target (staging) directory " + targetdir)
# Create the top-level directories
os.makedirs(targetdir + '/libs.tech', exist_ok=True)
os.makedirs(targetdir + '/libs.ref', exist_ok=True)
# Path to magic techfile depends on ef_format
if ef_format == True:
mag_current = '/libs.tech/magic/current/'
else:
mag_current = '/libs.tech/magic/'
# Check for magic version and set flag if it does not exist or if
# it has the wrong version.
have_mag_8_2 = False
try:
mproc = subprocess.run(['magic', '--version'],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
universal_newlines = True)
if mproc.stdout:
mag_version = mproc.stdout.splitlines()[0]
mag_version_info = mag_version.split('.')
try:
if int(mag_version_info[0]) > 8:
have_mag_8_2 = True
elif int(mag_version_info[0]) == 8:
if int(mag_version_info[1]) >= 2:
have_mag_8_2 = True
print('Magic version 8.2 available on the system.')
except ValueError:
print('Error: "magic --version" did not return valid version number.')
except FileNotFoundError:
print('Error: Failed to find executable for magic in standard search path.')
if not have_mag_8_2:
print('WARNING: Magic version 8.2 cannot be executed from the standard executable search path.')
print('Please install or correct the search path.')
print('Magic database files will not be created, and other missing file formats may not be generated.')
# Populate any targets that do not specify a library, or where the library is
# specified as "primitive".
# Populate the techLEF and SPICE models, if specified. Also, this section can add
# to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
# install into libs.tech/ngspice/.
if libraries == [] or 'primitive' in libraries[0]:
for option in optionlist[:]:
# Legacy behavior is to put libs.tech models and techLEF files in
# the same grouping as files for the primdev library (which go in
# libs.ref). Current behavior is to put all libs.tech files in
# a grouping with no library, with unrestricted ability to write
# into any subdirectory of libs.tech/. Therefore, need to restrict
# legacy use to just 'techlef' and 'models'.
if len(libraries) > 0 and 'primitive' in libraries[0]:
if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
continue
# Normally technology LEF files are associated with IP libraries.
# However, if no library is specified or the library is 'primitive'
# (legacy behavior), then put in the techLEF directory with no subdirectory.
filter_scripts = []
if option[0] == 'techlef' or option[0] == 'techLEF':
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
if ef_format:
techlefdir = targetdir + '/libs.ref/' + 'techLEF'
else:
techlefdir = targetdir + '/libs.tech/lef'
os.makedirs(techlefdir, exist_ok=True)
# All techlef files should be copied, so use "glob" on the wildcards
techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
for lefname in techlist:
leffile = os.path.split(lefname)[1]
targname = techlefdir + '/' + leffile
if os.path.isfile(lefname):
shutil.copy(lefname, targname)
else:
shutil.copytree(lefname, targname)
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script)
optionlist.remove(option)
# All remaining options will refer to specific tools (e.g., -ngspice, -magic)
# although generic names (.e.g, -models) are acceptable if the tools know
# where to find the files. Currently, most tools have their own formats
# and standards for setup, and so generally each install directory will be
# unique to one EDA tool.
else:
filter_scripts = []
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
print('Diagnostic: installing ' + option[0] + '.')
tooldir = targetdir + '/libs.tech/' + option[0]
os.makedirs(tooldir, exist_ok=True)
# All files should be linked or copied, so use "glob" on
# the wildcards. Copy each file and recursively copy each
# directory.
toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
for toolname in toollist:
toolfile = os.path.split(toolname)[1]
targname = tooldir + '/' + toolfile
if os.path.isdir(toolname):
# Remove any existing directory, and its contents
if os.path.isdir(targname):
shutil.rmtree(targname)
os.makedirs(targname)
# Recursively find and copy or link the whole directory
# tree from this point.
alltoollist = glob.glob(toolname + '/**', recursive=True)
commonpart = os.path.commonpath(alltoollist)
for subtoolname in alltoollist:
if os.path.isdir(subtoolname):
continue
# Get the path part that is not common between toollist and
# alltoollist.
subpart = os.path.relpath(subtoolname, commonpart)
subtargname = targname + '/' + subpart
os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
if os.path.isfile(subtoolname):
shutil.copy(subtoolname, subtargname)
else:
shutil.copytree(subtoolname, subtargname)
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(subtargname, filter_script)
else:
# Remove any existing file
if os.path.isfile(targname):
os.remove(targname)
elif os.path.isdir(targname):
shutil.rmtree(targname)
if os.path.isfile(toolname):
shutil.copy(toolname, targname)
else:
shutil.copytree(toolname, targname)
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script)
optionlist.remove(option)
# Do an initial pass through all of the options and determine what is being
# installed, so that we know in advance which file formats are missing and
# need to be generated.
for option in optionlist[:]:
if option[0] == 'lef':
have_lef = True
if option[0] == 'techlef' or option[0] == 'techLEF':
have_techlef = True
elif option[0] == 'gds':
have_gds = True
elif option[0] == 'spice' or option[0] == 'spi':
have_spice = True
elif option[0] == 'cdl':
have_cdl = True
elif option[0] == 'verilog':
have_verilog = True
elif option[0] == 'lib' or option[0] == 'liberty':
have_lib = True
# The remaining options in optionlist should all be types like 'lef' or 'liberty'
# and there should be a corresponding library list specified by '-library'
for option in optionlist[:]:
# Ignore if no library list---should have been taken care of above.
if libraries == []:
break
# Diagnostic
print("Install option: " + str(option[0]))
# For ef_format: always make techlef -> techLEF and spice -> spi
if ef_format:
if option[0] == 'techlef':
option[0] = 'techLEF'
elif option[0] == 'spice':
option[0] = 'spi'
destdir = targetdir + '/libs.ref/' + option[0]
os.makedirs(destdir, exist_ok=True)
# If the option is followed by the keyword "up" and a number, then
# the source should be copied (or linked) from <number> levels up
# in the hierarchy (see below).
if 'up' in option:
uparg = option.index('up')
try:
hier_up = int(option[uparg + 1])
except:
print("Non-numeric option to 'up': " + option[uparg + 1])
print("Ignoring 'up' option.")
hier_up = 0
else:
hier_up = 0
filter_scripts = []
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
# Option 'stub' applies to netlists ('cdl' or 'spice') and generates
# a file with only stub entries.
do_stub = 'stub' in option
# Option 'compile' is a standalone keyword ('comp' may be used).
do_compile = 'compile' in option or 'comp' in option
do_compile_only = 'compile-only' in option or 'comp-only' in option
# Option 'nospecify' is a standalone keyword ('nospec' may be used).
do_remove_spec = 'nospecify' in option or 'nospec' in option
# Option 'exclude' has an argument
try:
excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
except IndexError:
excludelist = []
else:
print('Excluding files: ' + (',').join(excludelist))
# Option 'rename' has an argument
try:
newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
except IndexError:
newname = None
else:
print('Renaming file to: ' + newname)
# 'anno' may be specified for LEF, in which case the LEF is used only
# to annotate GDS and is not itself installed; this allows LEF to
# be generated from Magic and avoids quirky use of obstruction layers.
have_lefanno = True if 'annotate' in option or 'anno' in option else False
if have_lefanno:
if option[0] != 'lef':
print("Warning: 'annotate' option specified outside of -lef. Ignoring.")
else:
# Mark as NOT having LEF since we want to use it only for annotation.
have_lef = False
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
else:
destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
destlibdir = destdir
os.makedirs(destlibdir, exist_ok=True)
# Populate the library subdirectory
# Parse the option and replace each '/*/' with the library name,
# and check if it is a valid directory name. Then glob the
# resulting option name. Warning: This assumes that all
# occurences of the text '/*/' match a library name. It should
# be possible to wild-card the directory name in such a way that
# this is always true.
testpath = substitute(sourcedir + '/' + option[1], library[1])
liblist = glob.glob(testpath)
# Create a file "sources.txt" (or append to it if it exists)
# and add the source directory name so that the staging install
# script can know where the files came from.
with open(destlibdir + '/sources.txt', 'a') as ofile:
print(testpath, file=ofile)
# Create exclude list with glob-style matching using fnmatch
if len(liblist) > 0:
liblistnames = list(os.path.split(item)[1] for item in liblist)
notliblist = []
for exclude in excludelist:
notliblist.extend(fnmatch.filter(liblistnames, exclude))
# Apply exclude list
if len(notliblist) > 0:
for file in liblist[:]:
if os.path.split(file)[1] in notliblist:
liblist.remove(file)
if len(excludelist) > 0 and len(notliblist) == 0:
print('Warning: Nothing from the exclude list found in sources.')
print('excludelist = ' + str(excludelist))
print('destlibdir = ' + destlibdir)
# Diagnostic
print('Collecting files from ' + testpath)
print('Files to install:')
if len(liblist) < 10:
for item in liblist:
print(' ' + item)
else:
for item in liblist[0:4]:
print(' ' + item)
print(' .')
print(' .')
print(' .')
for item in liblist[-6:-1]:
print(' ' + item)
print('(' + str(len(liblist)) + ' files total)')
for libname in liblist:
# Note that there may be a hierarchy to the files in option[1],
# say for liberty timing files under different conditions, so
# make sure directories have been created as needed.
libfile = os.path.split(libname)[1]
libfilepath = os.path.split(libname)[0]
destpathcomp = []
for i in range(hier_up):
destpathcomp.append('/' + os.path.split(libfilepath)[1])
libfilepath = os.path.split(libfilepath)[0]
destpathcomp.reverse()
destpath = ''.join(destpathcomp)
if newname:
if len(liblist) == 1:
destfile = newname
else:
if not do_compile and not do_compile_only:
print('Error: rename specified but more than one file found!')
destfile = libfile
else:
destfile = libfile
targname = destlibdir + destpath + '/' + destfile
# NOTE: When using "up" with link_from, could just make
# destpath itself a symbolic link; this way is more flexible
# but adds one symbolic link per file.
if destpath != '':
if not os.path.isdir(destlibdir + destpath):
os.makedirs(destlibdir + destpath, exist_ok=True)
# Remove any existing file
if os.path.isfile(targname):
os.remove(targname)
elif os.path.isdir(targname):
shutil.rmtree(targname)
# NOTE: Diagnostic, probably much too much output.
print(' Install:' + libname + ' to ' + targname)
if os.path.isfile(libname):
shutil.copy(libname, targname)
else:
shutil.copytree(libname, targname)
# File filtering options: Two options 'stub' and 'nospec' are
# handled by scripts in ../common/. Custom filters can also be
# specified.
local_filter_scripts = filter_scripts[:]
if option[0] == 'verilog':
# Internally handle syntactical issues with verilog and iverilog
vfilter(targname)
if do_remove_spec:
scriptdir = os.path.split(os.getcwd())[0] + '/common'
local_filter_scripts.append(scriptdir + '/remove_specify.py')
elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
if do_stub:
scriptdir = os.path.split(os.getcwd())[0] + '/common'
local_filter_scripts.append(scriptdir + '/makestub.py')
for filter_script in local_filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script)
if do_compile == True or do_compile_only == True:
# NOTE: The purpose of "rename" is to put a destlib-named
# library elsewhere so that it can be merged with another
# library into a compiled <destlib>.<ext>
compname = destlib
# To do: Make this compatible with linking from another PDK.
if option[0] == 'verilog':
# If there is not a single file with all verilog cells in it,
# then compile one, because one does not want to have to have
# an include line for every single cell used in a design.
create_verilog_library(destlibdir, compname, do_compile_only, do_stub, excludelist)
elif option[0] == 'gds' and have_mag_8_2:
# If there is not a single file with all GDS cells in it,
# then compile one.
# Link to the PDK magic startup file from the target directory
startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
if not os.path.isfile(startup_script):
startup_script = targetdir + mag_current + pdkname + '.magicrc'
create_gds_library(destlibdir, compname, startup_script, do_compile_only, excludelist)
elif option[0] == 'liberty' or option[0] == 'lib':
# If there is not a single file with all liberty cells in it,
# then compile one, because one does not want to have to have
# an include line for every single cell used in a design.
create_lib_library(destlibdir, compname, do_compile_only, excludelist)
elif option[0] == 'spice' or option[0] == 'spi':
# If there is not a single file with all SPICE subcircuits in it,
# then compile one, because one does not want to have to have
# an include line for every single cell used in a design.
spiext = '.spice' if not ef_format else '.spi'
create_spice_library(destlibdir, compname, spiext, do_compile_only, do_stub, excludelist)
if do_compile_only == True:
if newname:
if os.path.isfile(newname):
os.remove(newname)
elif option[0] == 'cdl':
# If there is not a single file with all CDL subcircuits in it,
# then compile one, because one does not want to have to have
# an include line for every single cell used in a design.
create_spice_library(destlibdir, compname, '.cdl', do_compile_only, do_stub, excludelist)
if do_compile_only == True:
if newname:
if os.path.isfile(newname):
os.remove(newname)
elif option[0] == 'lef':
# If there is not a single file with all LEF cells in it,
# then compile one, because one does not want to have to have
# an include line for every single cell used in a design.
create_lef_library(destlibdir, compname, do_compile_only, excludelist)
# Find any libraries/options marked as "privileged" (or "private") and
# move the files from libs.tech or libs.ref to libs.priv, leaving a
# symbolic link in the original location. Do this during the initial
# install so that options following in the list can add files to the
# non-privileged equivalent directory path.
if 'priv' in option or 'privileged' in option or 'private' in option:
# Diagnostic
print("Install option: " + str(option[0]))
if ef_format == True:
os.makedirs(targetdir + '/libs.priv', exist_ok=True)
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
srclibdir = targetdir + '/libs.ref/' + option[0] + '/' + destlib
destlibdir = targetdir + '/libs.priv/' + option[0] + '/' + destlib
else:
srclibdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
destlibdir = targetdir + '/libs.priv/' + destlib + '/' + option[0]
if not os.path.exists(destlibdir):
os.makedirs(destlibdir)
print('Moving files in ' + srclibdir + ' to privileged space.')
filelist = os.listdir(srclibdir)
for file in filelist:
srcfile = srclibdir + '/' + file
destfile = destlibdir + '/' + file
if os.path.isfile(destfile):
os.remove(destfile)
elif os.path.isdir(destfile):
shutil.rmtree(destfile)
if os.path.isfile(srcfile):
shutil.copy(srcfile, destfile)
os.remove(srcfile)
else:
shutil.copytree(srcfile, destfile)
shutil.rmtree(srcfile)
print("Completed installation of vendor files.")
#----------------------------------------------------------------
# Installation part 2: Generate derived file formats
#----------------------------------------------------------------
# Now for the harder part. If GDS and/or LEF databases were specified,
# then migrate them to magic (.mag files in layout/ or abstract/).
ignorelist = []
do_cdl_scaleu = False
no_cdl_convert = False
no_gds_convert = False
no_lef_convert = False
cdl_compile_only = False
cdl_exclude = []
lef_exclude = []
gds_exclude = []
spice_exclude = []
verilog_exclude = []
cdl_reflib = '/libs.ref/'
gds_reflib = '/libs.ref/'
lef_reflib = '/libs.ref/'
for option in optionlist[:]:
if option[0] == 'cdl':
# Option 'scaleu' is a standalone keyword
do_cdl_scaleu = 'scaleu' in option
# Option 'ignore' has arguments after '='
for item in option:
if item.split('=')[0] == 'ignore':
ignorelist = item.split('=')[1].split(',')
# Option 'noconvert' is a standalone keyword.
if 'noconvert' in option:
if option[0] == 'cdl':
no_cdl_convert = True
elif option[0] == 'gds':
no_gds_convert = True
elif option[0] == 'lef':
no_lef_convert = True
# Option 'privileged' is a standalone keyword.
if 'priv' in option or 'privileged' in option or 'private' in option:
if option[0] == 'cdl':
cdl_reflib = '/libs.priv/'
elif option[0] == 'gds':
gds_reflib = '/libs.priv/'
elif option[0] == 'lef':
lef_reflib = '/libs.priv/'
# If CDL is marked 'compile-only' then CDL should only convert the
# compiled file to SPICE if conversion is needed.
if 'compile-only' in option:
if option[0] == 'cdl':
cdl_compile_only = True
# Find exclude list for any option
for item in option:
if item.split('=')[0] == 'exclude':
exclude_list = item.split('=')[1].split(',')
if option[0] == 'cdl':
cdl_exclude = exclude_list
elif option[0] == 'lef':
lef_exclude = exclude_list
elif option[0] == 'gds':
gds_exclude = exclude_list
elif option[0] == 'spi' or option[0] == 'spice':
spice_exclude = exclude_list
elif option[0] == 'verilog':
verilog_exclude = exclude_list
devlist = []
pdklibrary = None
if have_gds and not no_gds_convert:
print("Migrating GDS files to layout.")
if ef_format:
destdir = targetdir + gds_reflib + 'mag'
srcdir = targetdir + gds_reflib + 'gds'
vdir = targetdir + '/libs.ref/' + 'verilog'
cdir = targetdir + cdl_reflib + 'cdl'
sdir = targetdir + cdl_reflib + 'spi'
os.makedirs(destdir, exist_ok=True)
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
srclibdir = srcdir + '/' + destlib
vlibdir = vdir + '/' + destlib
clibdir = cdir + '/' + destlib
slibdir = sdir + '/' + destlib
else:
destdir = targetdir + gds_reflib + destlib + '/mag'
srcdir = targetdir + gds_reflib + destlib + '/gds'
vdir = targetdir + '/libs.ref/' + destlib + '/verilog'
cdir = targetdir + cdl_reflib + destlib + '/cdl'
sdir = targetdir + cdl_reflib + destlib + '/spice'
destlibdir = destdir
srclibdir = srcdir
vlibdir = vdir
clibdir = cdir
slibdir = sdir
os.makedirs(destlibdir, exist_ok=True)
# For primitive devices, check the PDK script and find the name
# of the library and get a list of supported devices.
if library[0] == 'primitive':
pdkscript = targetdir + mag_current + pdkname + '.tcl'
print('Searching for supported devices in PDK script ' + pdkscript + '.')
if os.path.isfile(pdkscript):
librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
devlist = []
fixedlist = []
with open(pdkscript, 'r') as ifile:
scripttext = ifile.read().splitlines()
for line in scripttext:
lmatch = librex.match(line)
if lmatch:
pdklibrary = lmatch.group(1)
dmatch = devrex.match(line)
if dmatch:
if dmatch.group(1) == pdklibrary:
devlist.append(dmatch.group(2))
fmatch = fixrex.match(line)
if fmatch:
if fmatch.group(1) == pdklibrary:
fixedlist.append(fmatch.group(2))
# Diagnostic
print("PDK library is " + str(pdklibrary))
# Link to the PDK magic startup file from the target directory
# If there is no -F version then look for one without -F (open source PDK)
startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
if not os.path.isfile(startup_script):
startup_script = targetdir + mag_current + pdkname + '.magicrc'
if have_mag_8_2 and os.path.isfile(startup_script):
# If the symbolic link exists, remove it.
if os.path.isfile(destlibdir + '/.magicrc'):
os.remove(destlibdir + '/.magicrc')
os.symlink(startup_script, destlibdir + '/.magicrc')
# Find GDS file names in the source
print('Getting GDS file list from ' + srclibdir + '.')
gdsfilesraw = os.listdir(srclibdir)
gdsfiles = []
for gdsfile in gdsfilesraw:
gdsext = os.path.splitext(gdsfile)[1].lower()
if gdsext == '.gds' or gdsext == '.gdsii' or gdsext == '.gds2':
gdsfiles.append(gdsfile)
# Create exclude list with glob-style matching using fnmatch
if len(gdsfiles) > 0:
gdsnames = list(os.path.split(item)[1] for item in gdsfiles)
notgdsnames = []
for exclude in gds_exclude:
notgdsnames.extend(fnmatch.filter(gdsnames, exclude))
# Apply exclude list
if len(notgdsnames) > 0:
for file in gdsfiles[:]:
if os.path.split(file)[1] in notgdsnames:
gdsfiles.remove(file)
# Generate a script called "generate_magic.tcl" and leave it in
# the target directory. Use it as input to magic to create the
# .mag files from the database.
print('Creating magic generation script to generate magic database files.')
with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
print('#!/usr/bin/env wish', file=ofile)
print('#--------------------------------------------', file=ofile)
print('# Script to generate .mag files from .gds ', file=ofile)
print('#--------------------------------------------', file=ofile)
print('gds readonly true', file=ofile)
print('gds flatten true', file=ofile)
print('gds rescale false', file=ofile)
print('tech unlock *', file=ofile)
for gdsfile in gdsfiles:
# Note: DO NOT use a relative path here.
print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
# Make sure properties include the Tcl generated cell
# information from the PDK script
if pdklibrary:
tclfixedlist = '{' + ' '.join(fixedlist) + '}'
print('set devlist ' + tclfixedlist, file=ofile)
print('set topcell [lindex [cellname list top] 0]',
file=ofile)
print('foreach cellname $devlist {', file=ofile)
print(' load $cellname', file=ofile)
print(' property gencell $cellname', file=ofile)
print(' property parameter m=1', file=ofile)
print(' property library ' + pdklibrary, file=ofile)
print('}', file=ofile)
print('load $topcell', file=ofile)
print('cellname delete \(UNNAMED\)', file=ofile)
print('writeall force', file=ofile)
leffiles = []
lefmacros = []
if have_lefanno:
# Find LEF file names in the source
if ef_format:
lefsrcdir = targetdir + lef_reflib + 'lefanno'
lefsrclibdir = lefsrcdir + '/' + destlib
else:
lefsrcdir = targetdir + lef_reflib + destlib + '/lefanno'
lefsrclibdir = lefsrcdir
leffiles = os.listdir(lefsrclibdir)
leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
# Get list of abstract views to make from LEF macros
for leffile in leffiles:
with open(leffile, 'r') as ifile:
ltext = ifile.read()
llines = ltext.splitlines()
for lline in llines:
ltok = re.split(' |\t|\(', lline)
if ltok[0] == 'MACRO':
lefmacros.append(ltok[1])
# Create exclude list with glob-style matching using fnmatch
if len(lefmacros) > 0:
lefnames = list(os.path.split(item)[1] for item in lefmacros)
notlefnames = []
for exclude in lef_exclude:
notlefnames.extend(fnmatch.filter(lefnames, exclude))
# Apply exclude list
if len(notlefnames) > 0:
for file in lefmacros[:]:
if os.path.split(file)[1] in notlefnames:
lefmacros.remove(file)
elif have_verilog and os.path.isdir(vlibdir):
# Get list of abstract views to make from verilog modules
vfiles = os.listdir(vlibdir)
vfiles = list(item for item in vfiles if os.path.splitext(item)[1] == '.v')
for vfile in vfiles:
with open(vlibdir + '/' + vfile, 'r') as ifile:
vtext = ifile.read()
vlines = vtext.splitlines()
for vline in vlines:
vtok = re.split(' |\t|\(', vline)
try:
if vtok[0] == 'module':
if vtok[1] not in lefmacros:
lefmacros.append(vtok[1])
except:
pass
# Create exclude list with glob-style matching using fnmatch
if len(lefmacros) > 0:
lefnames = list(os.path.split(item)[1] for item in lefmacros)
notlefnames = []
for exclude in verilog_exclude:
notlefnames.extend(fnmatch.filter(lefnames, exclude))
# Apply exclude list
if len(notlefnames) > 0:
for file in lefmacros[:]:
if os.path.split(file)[1] in notlefnames:
lefmacros.remove(file)
elif have_cdl and os.path.isdir(clibdir):
# Get list of abstract views to make from CDL subcircuits
cfiles = os.listdir(clibdir)
cfiles = list(item for item in cfiles if os.path.splitext(item)[1] == '.cdl')
for cfile in cfiles:
with open(clibdir + '/' + cfile, 'r') as ifile:
ctext = ifile.read()
clines = ctext.splitlines()
for cline in clines:
ctok = cline.split()
try:
if ctok[0].lower() == '.subckt':
if ctok[1] not in lefmacros:
lefmacros.append(ctok[1])
except:
pass
# Create exclude list with glob-style matching using fnmatch
if len(lefmacros) > 0:
lefnames = list(os.path.split(item)[1] for item in lefmacros)
notlefnames = []
for exclude in cdl_exclude:
notlefnames.extend(fnmatch.filter(lefnames, exclude))
# Apply exclude list
if len(notlefnames) > 0:
for file in lefmacros[:]:
if os.path.split(file)[1] in notlefnames:
lefmacros.remove(file)
elif have_spice and os.path.isdir(slibdir):
# Get list of abstract views to make from SPICE subcircuits
sfiles = os.listdir(slibdir)
sfiles = list(item for item in sfiles)
for sfile in sfiles:
with open(slibdir + '/' + sfile, 'r') as ifile:
stext = ifile.read()
slines = stext.splitlines()
for sline in slines:
stok = sline.split()
try:
if stok[0].lower() == '.subckt':
if stok[1] not in lefmacros:
lefmacros.append(stok[1])
except:
pass
# Create exclude list with glob-style matching using fnmatch
if len(lefmacros) > 0:
lefnames = list(os.path.split(item)[1] for item in lefmacros)
notlefnames = []
for exclude in spice_exclude:
notlefnames.extend(fnmatch.filter(lefnames, exclude))
# Apply exclude list
if len(notlefnames) > 0:
for file in lefmacros[:]:
if os.path.split(file)[1] in notlefnames:
lefmacros.remove(file)
if not lefmacros:
print('No source for abstract views: Abstract views not made.')
elif not have_lef:
# This library has a GDS database but no LEF database. Use
# magic to create abstract views of the GDS cells. If
# option "annotate" is given, then read the LEF file after
# loading the database file to annotate the cell with
# information from the LEF file. This usually indicates
# that the LEF file has some weird definition of obstruction
# layers and we want to normalize them by using magic's LEF
# write procedure, but we still need the pin use and class
# information from the LEF file, and maybe the bounding box.
for leffile in leffiles:
if have_lefanno:
print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
for lefmacro in lefmacros:
print('if {[cellname list exists ' + lefmacro + '] != 0} {', file=ofile)
print(' load ' + lefmacro, file=ofile)
print(' lef write ' + lefmacro + ' -hide', file=ofile)
print('}', file=ofile)
print('puts stdout "Done."', file=ofile)
print('quit -noprompt', file=ofile)
print('Running magic to create magic database files.')
sys.stdout.flush()
# Run magic to read in the GDS file and write out magic databases.
with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
stdin = ifile, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = destlibdir,
universal_newlines = True)
if mproc.stdout:
for line in mproc.stdout.splitlines():
print(line)
if mproc.stderr:
print('Error message output from magic:')
for line in mproc.stderr.splitlines():
print(line)
if mproc.returncode != 0:
print('ERROR: Magic exited with status ' + str(mproc.returncode))
if not have_lef:
print('No LEF file install; need to generate LEF.')
# Remove the lefanno/ target and its contents.
if have_lefanno:
if ef_format:
lefannosrcdir = targetdir + lef_reflib + 'lefanno'
else:
lefannosrcdir = targetdir + lef_reflib + destlib + '/lefanno'
if os.path.isdir(lefannosrcdir):
shutil.rmtree(lefannosrcdir)
if ef_format:
destlefdir = targetdir + lef_reflib + 'lef'
destleflibdir = destlefdir + '/' + destlib
else:
destlefdir = targetdir + lef_reflib + destlib + '/lef'
destleflibdir = destlefdir
os.makedirs(destleflibdir, exist_ok=True)
leflist = os.listdir(destlibdir)
leflist = list(item for item in leflist if os.path.splitext(item)[1] == '.lef')
# All macros will go into one file
destleflib = destleflibdir + '/' + destlib + '.lef'
# Remove any existing library file from the target directory
if os.path.isfile(destleflib):
print('Removing existing library ' + destleflib)
os.remove(destleflib)
first = True
with open(destleflib, 'w') as ofile:
for leffile in leflist:
# Remove any existing single file from the target directory
if os.path.isfile(destleflibdir + '/' + leffile):
print('Removing ' + destleflibdir + '/' + leffile)
os.remove(destleflibdir + '/' + leffile)
# Append contents
sourcelef = destlibdir + '/' + leffile
with open(sourcelef, 'r') as ifile:
leflines = ifile.read().splitlines()
if not first:
# Remove header from all but the first file
leflines = leflines[8:]
else:
first = False
for line in leflines:
print(line, file=ofile)
# Remove file from the source directory
print('Removing source file ' + sourcelef)
os.remove(sourcelef)
# Set have_lef now that LEF files were made, so they
# can be used to generate the maglef/ databases.
have_lef = True
elif not have_mag_8_2:
print('The installer is not able to run magic.')
else:
print("Master PDK magic startup file not found. Did you install")
print("PDK tech files before PDK vendor files?")
if have_lef and not no_lef_convert:
print("Migrating LEF files to layout.")
if ef_format:
destdir = targetdir + '/libs.ref/' + 'maglef'
srcdir = targetdir + lef_reflib + 'lef'
magdir = targetdir + gds_reflib + 'mag'
cdldir = targetdir + cdl_reflib + 'cdl'
os.makedirs(destdir, exist_ok=True)
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
srclibdir = srcdir + '/' + destlib
maglibdir = magdir + '/' + destlib
cdllibdir = cdldir + '/' + destlib
else:
destdir = targetdir + '/libs.ref/' + destlib + '/maglef'
srcdir = targetdir + lef_reflib + destlib + '/lef'
magdir = targetdir + gds_reflib + destlib + '/mag'
cdldir = targetdir + cdl_reflib + destlib + '/cdl'
destlibdir = destdir
srclibdir = srcdir
maglibdir = magdir
cdllibdir = cdldir
os.makedirs(destlibdir, exist_ok=True)
# Link to the PDK magic startup file from the target directory
startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
if not os.path.isfile(startup_script):
startup_script = targetdir + mag_current + pdkname + '.magicrc'
if have_mag_8_2 and os.path.isfile(startup_script):
# If the symbolic link exists, remove it.
if os.path.isfile(destlibdir + '/.magicrc'):
os.remove(destlibdir + '/.magicrc')
os.symlink(startup_script, destlibdir + '/.magicrc')
# Find LEF file names in the source
leffiles = os.listdir(srclibdir)
leffiles = list(item for item in leffiles if os.path.splitext(item)[1].lower() == '.lef')
# Get list of abstract views to make from LEF macros
lefmacros = []
err_no_macros = False
for leffile in leffiles:
with open(srclibdir + '/' + leffile, 'r') as ifile:
ltext = ifile.read()
llines = ltext.splitlines()
for lline in llines:
ltok = re.split(' |\t|\(', lline)
if ltok[0] == 'MACRO':
lefmacros.append(ltok[1])
# Create exclude list with glob-style matching using fnmatch
if len(lefmacros) > 0:
lefnames = list(os.path.split(item)[1] for item in lefmacros)
notlefnames = []
for exclude in lef_exclude:
notlefnames.extend(fnmatch.filter(lefnames, exclude))
# Apply exclude list
if len(notlefnames) > 0:
for file in lefmacros[:]:
if os.path.split(file)[1] in notlefnames:
lefmacros.remove(file)
if len(leffiles) == 0:
print('Warning: No LEF files found in ' + srclibdir)
continue
print('Generating conversion script to create magic databases from LEF')
# Generate a script called "generate_magic.tcl" and leave it in
# the target directory. Use it as input to magic to create the
# .mag files from the database.
with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
print('#!/usr/bin/env wish', file=ofile)
print('#--------------------------------------------', file=ofile)
print('# Script to generate .mag files from .lef ', file=ofile)
print('#--------------------------------------------', file=ofile)
print('tech unlock *', file=ofile)
# If there are devices in the LEF file that come from the
# PDK library, then copy this list into the script.
if pdklibrary:
shortdevlist = []
for macro in lefmacros:
if macro in devlist:
shortdevlist.append(macro)
tcldevlist = '{' + ' '.join(shortdevlist) + '}'
print('set devlist ' + tcldevlist, file=ofile)
for leffile in leffiles:
print('lef read ' + srclibdir + '/' + leffile, file=ofile)
for lefmacro in lefmacros:
# To be completed: Parse SPICE file for port order, make
# sure ports are present and ordered.
if pdklibrary and lefmacro in shortdevlist:
print('set cellname ' + lefmacro, file=ofile)
print('if {[lsearch $devlist $cellname] >= 0} {',
file=ofile)
print(' load $cellname', file=ofile)
print(' property gencell $cellname', file=ofile)
print(' property parameter m=1', file=ofile)
print(' property library ' + pdklibrary, file=ofile)
print('}', file=ofile)
# Load one of the LEF files so that the default (UNNAMED) cell
# is not loaded, then delete (UNNAMED) so it doesn't generate
# an error message.
if len(lefmacros) > 0:
print('load ' + lefmacros[0], file=ofile)
print('cellname delete \(UNNAMED\)', file=ofile)
else:
err_no_macros = True
print('writeall force', file=ofile)
print('puts stdout "Done."', file=ofile)
print('quit -noprompt', file=ofile)
if err_no_macros == True:
print('Warning: No LEF macros were defined.')
print('Running magic to create magic databases from LEF')
sys.stdout.flush()
# Run magic to read in the LEF file and write out magic databases.
with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
stdin = ifile, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = destlibdir,
universal_newlines = True)
if mproc.stdout:
for line in mproc.stdout.splitlines():
print(line)
if mproc.stderr:
print('Error message output from magic:')
for line in mproc.stderr.splitlines():
print(line)
if mproc.returncode != 0:
print('ERROR: Magic exited with status ' + str(mproc.returncode))
# Now list all the .mag files generated, and for each, read the
# corresponding file from the mag/ directory, pull the GDS file
# properties, and add those properties to the maglef view. Also
# read the CDL (or SPICE) netlist, read the ports, and rewrite
# the port order in the mag and maglef file accordingly.
# Diagnostic
print('Annotating files in ' + destlibdir)
sys.stdout.flush()
magfiles = os.listdir(destlibdir)
magfiles = list(item for item in magfiles if os.path.splitext(item)[1] == '.mag')
for magroot in magfiles:
magname = os.path.splitext(magroot)[0]
magfile = maglibdir + '/' + magroot
magleffile = destlibdir + '/' + magroot
prop_lines = get_gds_properties(magfile)
# Make sure properties include the Tcl generated cell
# information from the PDK script
prop_gencell = []
if pdklibrary:
if magname in fixedlist:
prop_gencell.append('gencell ' + magname)
prop_gencell.append('library ' + pdklibrary)
prop_gencell.append('parameter m=1')
nprops = len(prop_lines) + len(prop_gencell)
cdlfile = cdllibdir + '/' + magname + '.cdl'
if os.path.exists(cdlfile):
cdlfiles = [cdlfile]
else:
# Assume there is at least one file with all cell subcircuits
# in it.
try:
cdlfiles = glob.glob(cdllibdir + '/*.cdl')
except:
pass
if len(cdlfiles) > 0:
for cdlfile in cdlfiles:
port_dict = get_subckt_ports(cdlfile, magname)
if port_dict != {}:
break
else:
port_dict = {}
if port_dict == {}:
print('No CDL file contains ' + destlib + ' device ' + magname)
cdlfile = None
# To be done: If destlib is 'primitive', then look in
# SPICE models for port order.
if destlib == 'primitive':
print('Fix me: Need to look in SPICE models!')
proprex = re.compile('<< properties >>')
endrex = re.compile('<< end >>')
rlabrex = re.compile('rlabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
flabrex = re.compile('flabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
portrex = re.compile('port[ \t]+([^ \t]+)[ \t]+(.*)')
gcellrex = re.compile('string gencell')
portnum = -1
with open(magleffile, 'r') as ifile:
magtext = ifile.read().splitlines()
with open(magleffile, 'w') as ofile:
has_props = False
is_gencell = False
for line in magtext:
tmatch = portrex.match(line)
if tmatch:
if portnum >= 0:
line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
else:
line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
ematch = endrex.match(line)
if ematch and nprops > 0:
if not has_props:
print('<< properties >>', file=ofile)
if not is_gencell:
for prop in prop_gencell:
print('string ' + prop, file=ofile)
for prop in prop_lines:
print('string ' + prop, file=ofile)
print(line, file=ofile)
pmatch = proprex.match(line)
if pmatch:
has_props = True
gmatch = gcellrex.match(line)
if gmatch:
is_gencell = True
lmatch = flabrex.match(line)
if not lmatch:
lmatch = rlabrex.match(line)
if lmatch:
labname = lmatch.group(1).lower()
try:
portnum = port_dict[labname]
except:
portnum = -1
if os.path.exists(magfile):
with open(magfile, 'r') as ifile:
magtext = ifile.read().splitlines()
with open(magfile, 'w') as ofile:
for line in magtext:
tmatch = portrex.match(line)
if tmatch:
if portnum >= 0:
line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
else:
line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
ematch = endrex.match(line)
print(line, file=ofile)
lmatch = flabrex.match(line)
if not lmatch:
lmatch = rlabrex.match(line)
if lmatch:
labname = lmatch.group(1).lower()
try:
portnum = port_dict[labname]
except:
portnum = -1
elif os.path.splitext(magfile)[1] == '.mag':
# NOTE: Possibly this means the GDS cell has a different name.
print('Error: No file ' + magfile + '. Why is it in maglef???')
elif not have_mag_8_2:
print('The installer is not able to run magic.')
else:
print("Master PDK magic startup file not found. Did you install")
print("PDK tech files before PDK vendor files?")
# If SPICE or CDL databases were specified, then convert them to
# a form that can be used by ngspice, using the cdl2spi.py script
if have_spice:
if ef_format:
if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
elif have_cdl and not no_cdl_convert:
if ef_format:
if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
print("Migrating CDL netlists to SPICE.")
sys.stdout.flush()
if ef_format:
destdir = targetdir + cdl_reflib + 'spi'
srcdir = targetdir + cdl_reflib + 'cdl'
os.makedirs(destdir, exist_ok=True)
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
srclibdir = srcdir + '/' + destlib
else:
destdir = targetdir + cdl_reflib + destlib + '/spice'
srcdir = targetdir + cdl_reflib + destlib + '/cdl'
destlibdir = destdir
srclibdir = srcdir
os.makedirs(destlibdir, exist_ok=True)
# Find CDL file names in the source
# If CDL is marked compile-only then ONLY convert <distdir>.cdl
if cdl_compile_only:
alllibname = destlibdir + '/' + destlib + '.cdl'
if not os.path.exists(alllibname):
cdl_compile_only = False
else:
cdlfiles = [alllibname]
if not cdl_compile_only:
cdlfiles = os.listdir(srclibdir)
cdlfiles = list(item for item in cdlfiles if os.path.splitext(item)[1].lower() == '.cdl')
# The directory with scripts should be in ../common with respect
# to the Makefile that determines the cwd.
scriptdir = os.path.split(os.getcwd())[0] + '/common'
# Run cdl2spi.py script to read in the CDL file and write out SPICE
for cdlfile in cdlfiles:
if ef_format:
spiname = os.path.splitext(cdlfile)[0] + '.spi'
else:
spiname = os.path.splitext(cdlfile)[0] + '.spice'
procopts = [scriptdir + '/cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
if do_cdl_scaleu:
procopts.append('-dscale=u')
for item in ignorelist:
procopts.append('-ignore=' + item)
print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
pproc = subprocess.run(procopts,
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = destlibdir,
universal_newlines = True)
if pproc.stdout:
for line in pproc.stdout.splitlines():
print(line)
if pproc.stderr:
print('Error message output from cdl2spi.py:')
for line in pproc.stderr.splitlines():
print(line)
elif have_gds and not no_gds_convert:
# If neither SPICE nor CDL formats is available in the source, then
# read GDS; if the result has no ports, then read the corresponding
# LEF library to get port information. Then write out a SPICE netlist
# for the whole library. NOTE: If there is no CDL or SPICE source,
# then the port numbering is arbitrary, and becomes whatever the
# output of this script makes it.
if ef_format:
destdir = targetdir + cdl_reflib + 'spi'
srcdir = targetdir + gds_reflib + 'gds'
lefdir = targetdir + lef_reflib + 'lef'
os.makedirs(destdir, exist_ok=True)
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
srclibdir = srcdir + '/' + destlib
leflibdir = lefdir + '/' + destlib
else:
destdir = targetdir + cdl_reflib + destlib + '/spice'
srcdir = targetdir + gds_reflib + destlib + '/gds'
lefdir = targetdir + lef_reflib + destlib + '/lef'
destlibdir = destdir
srclibdir = srcdir
leflibdir = lefdir
os.makedirs(destlibdir, exist_ok=True)
# Link to the PDK magic startup file from the target directory
startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
if not os.path.isfile(startup_script):
startup_script = targetdir + mag_current + pdkname + '.magicrc'
if os.path.isfile(startup_script):
# If the symbolic link exists, remove it.
if os.path.isfile(destlibdir + '/.magicrc'):
os.remove(destlibdir + '/.magicrc')
os.symlink(startup_script, destlibdir + '/.magicrc')
# Get the consolidated GDS library file, or a list of all GDS files
# if there is no single consolidated library
allgdslibname = srclibdir + '/' + destlib + '.gds'
if not os.path.isfile(allgdslibname):
glist = glob.glob(srclibdir + '/*.gds')
glist.extend(glob.glob(srclibdir + '/*.gdsii'))
glist.extend(glob.glob(srclibdir + '/*.gds2'))
allleflibname = leflibdir + '/' + destlib + '.lef'
if not os.path.isfile(allleflibname):
llist = glob.glob(leflibdir + '/*.lef')
print('Creating magic generation script to generate SPICE library.')
with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
print('#!/usr/bin/env wish', file=ofile)
print('#---------------------------------------------', file=ofile)
print('# Script to generate SPICE library from GDS ', file=ofile)
print('#---------------------------------------------', file=ofile)
print('drc off', file=ofile)
print('gds readonly true', file=ofile)
print('gds flatten true', file=ofile)
print('gds rescale false', file=ofile)
print('tech unlock *', file=ofile)
if not os.path.isfile(allgdslibname):
for gdsfile in glist:
print('gds read ' + gdsfile, file=ofile)
else:
print('gds read ' + allgdslibname, file=ofile)
if not os.path.isfile(allleflibname):
# Annotate the cells with information from the LEF files
for leffile in llist:
print('lef read ' + leffile, file=ofile)
else:
print('lef read ' + allleflibname, file=ofile)
# Load first file and remove the (UNNAMED) cell
if not os.path.isfile(allgdslibname):
print('load ' + os.path.splitext(glist[0])[0], file=ofile)
else:
gdslibroot = os.path.split(allgdslibname)[1]
print('load ' + os.path.splitext(gdslibroot)[0], file=ofile)
print('cellname delete \(UNNAMED\)', file=ofile)
print('ext2spice lvs', file=ofile)
# NOTE: Leaving "subcircuit top" as "auto" (default) can cause
# cells like decap that have no I/O to be output without a subcircuit
# wrapper. Also note that if this happens, it is an indication that
# power supplies have not been labeled as ports, which is harder to
# handle and should be fixed in the source.
print('ext2spice subcircuit top on', file=ofile)
print('ext2spice cthresh 0.1', file=ofile)
if os.path.isfile(allgdslibname):
print('select top cell', file=ofile)
print('set glist [cellname list children]', file=ofile)
print('foreach cell $glist {', file=ofile)
else:
print('foreach cell [cellname list top] {', file=ofile)
print(' load $cell', file=ofile)
print(' puts stdout "Extracting cell $cell"', file=ofile)
print(' extract all', file=ofile)
print(' ext2spice', file=ofile)
print('}', file=ofile)
print('puts stdout "Done."', file=ofile)
print('quit -noprompt', file=ofile)
# Run magic to read in the individual GDS files and
# write out the consolidated GDS library
print('Running magic to create GDS library.')
sys.stdout.flush()
mproc = subprocess.run(['magic', '-dnull', '-noconsole',
destlibdir + '/generate_magic.tcl'],
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = destlibdir,
universal_newlines = True)
if mproc.stdout:
for line in mproc.stdout.splitlines():
print(line)
if mproc.stderr:
print('Error message output from magic:')
for line in mproc.stderr.splitlines():
print(line)
if mproc.returncode != 0:
print('ERROR: Magic exited with status ' + str(mproc.returncode))
# Remove intermediate extraction files
extfiles = glob.glob(destlibdir + '/*.ext')
for extfile in extfiles:
os.remove(extfile)
# If the GDS file was a consolidated file of all cells, then
# create a similar SPICE library of all cells.
if os.path.isfile(allgdslibname):
spiext = '.spice' if not ef_format else '.spi'
create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist)
sys.exit(0)