blob: a755074a8adfbfb8f03f82b4a39a5343e1cd15d0 [file] [log] [blame]
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001#!/usr/bin/env python3
2#
3# foundry_install.py
4#
5# This file generates the local directory structure and populates the
6# directories with foundry vendor data. The local directory (target)
7# should be a staging area, not a place where files are kept permanently.
8#
9# Options:
10# -ef_format Use efabless naming (libs.ref/techLEF),
11# otherwise use generic naming (libs.tech/lef)
12# -clean Clear out and remove target directory before starting
13# -source <path> Path to source data top level directory
14# -target <path> Path to target (staging) top level directory
15#
16# All other options represent paths to vendor files. They may all be
17# wildcarded with "*", or with specific escapes like "%l" for library
18# name or "%v" for version number (see below for a complete list of escape
19# sequences).
20#
21# Note only one of "-spice" or "-cdl" need be specified. Since the
22# open source tools use ngspice, CDL files are converted to ngspice
23# syntax when needed.
24#
25# -techlef <path> Path to technology LEF file
26# -doc <path> Path to technology documentation
27# -lef <path> Path to LEF file
28# -spice <path> Path to SPICE netlists
29# -cdl <path> Path to CDL netlists
30# -models <path> Path to SPICE (primitive device) models
31# -liberty <path> Path to Liberty timing files
32# -gds <path> Path to GDS data
33# -verilog <path> Path to verilog models
34#
35# -library <type> <name> [<target>] See below
36#
37# For the "-library" option, any number of libraries may be supported, and
38# one "-library" option should be provided for each supported library.
39# <type> is one of: "digital", "primitive", or "general". Analog and I/O
40# libraries fall under the category "general", as they are all treated the
41# same way. <name> is the vendor name of the library. [<target>] is the
42# (optional) local name of the library. If omitted, then the vendor name
43# is used for the target (there is no particular reason to specify a
44# different local name for a library).
45#
46# In special cases using options (see below), path may be "-", indicating
47# that there are no source files, but only to run compilations or conversions
48# on the files in the target directory.
49#
50# All options "-lef", "-spice", etc., can take the additional arguments
51# up <number>
52#
53# to indicate that the source hierarchy should be copied from <number>
54# levels above the files. For example, if liberty files are kept in
55# multiple directories according to voltage level, then
56#
57# -liberty x/y/z/PVT_*/*.lib
58#
59# would install all .lib files directly into libs.ref/<libname>/liberty/*.lib
60# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/*.lib)
61# while
62#
63# -liberty x/y/z/PVT_*/*.lib up 1
64#
65# would install all .lib files into libs.ref/liberty/<libname>/PVT_*/*.lib
66# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/PVT_*/*.lib)
67#
68# Please note that the INSTALL variable in the Makefile starts with "set -f"
69# to suppress the OS from doing wildcard substitution; otherwise the
70# wildcards in the install options will get expanded by the OS before
71# being passed to the install script.
72#
73# Other library-specific arguments are:
74#
75# nospec : Remove timing specification before installing
76# (used with verilog files; needs to be extended to
77# liberty files)
Tim Edwards4697a172021-11-05 22:47:07 -040078#
Tim Edwards55f4d0e2020-07-05 15:41:02 -040079# compile : Create a single library from all components. Used
80# when a foundry library has inconveniently split
81# an IP library (LEF, CDL, verilog, etc.) into
82# individual files.
Tim Edwards4697a172021-11-05 22:47:07 -040083#
Tim Edwards995c1332020-09-25 15:33:58 -040084# compile-only: Like "compile" except that the individual
85# files are removed after the library file has been
86# created.
87#
Tim Edwards55f4d0e2020-07-05 15:41:02 -040088# stub : Remove contents of subcircuits from CDL or SPICE
89# netlist files.
90#
91# priv : Mark the contents being installed as privleged, and
92# put them in a separate root directory libs.priv
93# where they can be given additional read/write
94# restrictions.
95#
96# exclude : Followed by "=" and a comma-separated list of names.
97# exclude these files/modules/subcircuits. Names may
Tim Edwards4697a172021-11-05 22:47:07 -040098# also be wildcarded in "glob" format. Cell names are
99# excluded from the copy and also from any libraries
100# that are generated. If the pattern contains a directory
101# path, then patterns are added from files in the
102# specified directory instead of the source.
103#
104# no-copy : Followed by "=" and a comma-separated list of names.
105# exclude these files/modules/subcircuits. Names may
106# also be wildcarded in "glob" format. Cell names are
107# excluded from the copy only. If the pattern contains
108# a directory path, then patterns are added from files
109# in the specified directory instead of the source.
110#
111# include : Followed by "=" and a comma-separated list of names.
112# include these files/modules/subcircuits. Names may
113# also be wildcarded in "glob" format. Cell names are
114# included in any libraries that are generated if they
115# exist in the target directory, even if they were not
116# in the source directory being copied. If the pattern
117# contains a directory path, then patterns are added from
118# files in the specified directory instead of the source.
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400119#
120# rename : Followed by "=" and an alternative name. For any
121# file that is a single entry, change the name of
122# the file in the target directory to this (To-do:
123# take regexps for multiple files). When used with
124# "compile" or "compile-only", this refers to the
125# name of the target compiled file.
126#
Tim Edwards995c1332020-09-25 15:33:58 -0400127# filter: Followed by "=" and the name of a script.
128# Each file is passed through the filter script
129# before writing into the staging area.
130#
Tim Edwards5c7924d2020-09-30 22:22:10 -0400131# sort: Followed by "=" and the name of a script.
Tim Edwards995c1332020-09-25 15:33:58 -0400132# The list of files to process (after applying items
133# from "exclude") will be written to a file
134# "filelist.txt", which will be used by the
Tim Edwards5c7924d2020-09-30 22:22:10 -0400135# library compile routines, if present. The sort
136# script will rewrite the file with the order in
137# which entries should appear in the compiled library.
138# Only useful when used with "compile" or "compile-only".
139# If not specified, files are sorted by "natural sort"
140# order.
Tim Edwards995c1332020-09-25 15:33:58 -0400141#
Tim Edwards4697a172021-11-05 22:47:07 -0400142# annotate: When used with "-lef", the LEF files are used only
143# for annotation of pins (use, direction, etc.), but
144# the LEF files should not be used and LEF should be
145# generated from layout.
146#
Tim Edwardsb063d372021-11-12 16:15:18 -0500147# lefopts: Followed by "=" and a comma-separated list of option
148# strings. If LEF views are generated from magic, use the
149# options specified.
150#
Tim Edwards4697a172021-11-05 22:47:07 -0400151# noconvert: Install only; do not attempt to convert to other
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400152# formats (applies only to GDS, CDL, and LEF).
153#
Tim Edwards26ab4962021-01-03 14:22:54 -0500154# options: Followed by "=" and the name of a script. Behavior
155# is dependent on the mode; if applied to "-gds",
156# then the script is inserted before the GDS read
Tim Edwards7dbca1a2021-03-03 12:25:19 -0500157# in the Tcl generate script passed to magic. If
158# what follows the "=" is not a file, then it is
159# Tcl code to be inserted verbatim.
Tim Edwards26ab4962021-01-03 14:22:54 -0500160#
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400161# NOTE: This script can be called once for all libraries if all file
162# types (gds, cdl, lef, etc.) happen to all work with the same wildcards.
163# However, it is more likely that it will be called several times for the
164# same PDK, once to install I/O cells, once to install digital, and so
165# forth, as made possible by the wild-carding.
166
167import re
168import os
169import sys
170import glob
171import stat
172import shutil
173import fnmatch
174import subprocess
175
Tim Edwards51f81422020-07-26 12:49:48 -0400176# Import local routines
177from create_gds_library import create_gds_library
178from create_spice_library import create_spice_library
179from create_lef_library import create_lef_library
180from create_lib_library import create_lib_library
181from create_verilog_library import create_verilog_library
182
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400183def usage():
184 print("foundry_install.py [options...]")
185 print(" -copy Copy files from source to target (default)")
186 print(" -ef_format Use efabless naming conventions for local directories")
187 print("")
188 print(" -source <path> Path to top of source directory tree")
189 print(" -target <path> Path to top of target directory tree")
190 print("")
191 print(" -techlef <path> Path to technology LEF file")
192 print(" -doc <path> Path to technology documentation")
193 print(" -lef <path> Path to LEF file")
194 print(" -spice <path> Path to SPICE netlists")
195 print(" -cdl <path> Path to CDL netlists")
196 print(" -models <path> Path to SPICE (primitive device) models")
197 print(" -lib <path> Path to Liberty timing files")
198 print(" -liberty <path> Path to Liberty timing files")
199 print(" -gds <path> Path to GDS data")
200 print(" -verilog <path> Path to verilog models")
201 print(" -library <type> <name> [<target>] See below")
202 print("")
203 print(" All <path> names may be wild-carded with '*' ('glob'-style wild-cards)")
204 print("")
205 print(" All options with <path> other than source and target may take the additional")
206 print(" arguments 'up <number>', where <number> indicates the number of levels of")
207 print(" hierarchy of the source path to include when copying to the target.")
208 print("")
209 print(" Library <type> may be one of:")
210 print(" digital Digital standard cell library")
211 print(" primitive Primitive device library")
212 print(" general All other library types (I/O, analog, etc.)")
213 print("")
214 print(" If <target> is unspecified then <name> is used for the target.")
215
216# Return a list of files after glob-style substituting into pathname. This
217# mostly relies on glob.glob(), but uses the additional substitutions with
218# escape strings:
219#
220# %v : Match a version number in the form "major[.minor[.rev]]"
221# %l : substitute the library name
222# %% : substitute the percent character verbatim
223
224from distutils.version import LooseVersion
225
226#----------------------------------------------------------------------------
227#----------------------------------------------------------------------------
228
229def makeuserwritable(filepath):
230 if os.path.exists(filepath):
231 st = os.stat(filepath)
232 os.chmod(filepath, st.st_mode | stat.S_IWUSR)
233
234#----------------------------------------------------------------------------
235#----------------------------------------------------------------------------
236
237def substitute(pathname, library):
238 if library:
239 # Do %l substitution
240 newpathname = re.sub('%l', library, pathname)
241 else:
242 newpathname = pathname
243
244 if '%v' in newpathname:
245 vglob = re.sub('%v.*', '*', newpathname)
246 vlibs = glob.glob(vglob)
247 try:
248 vstr = vlibs[0][len(vglob)-1:]
249 except IndexError:
250 pass
251 else:
252 for vlib in vlibs[1:]:
253 vtest = vlib[len(vglob)-1:]
254 if LooseVersion(vtest) > LooseVersion(vstr):
255 vstr = vtest
256 newpathname = re.sub('%v', vstr, newpathname)
257
258 if '%%' in newpathname:
259 newpathname = re.sub('%%', '%', newpathname)
260
261 return newpathname
262
263#----------------------------------------------------------------------------
264#----------------------------------------------------------------------------
265
266def get_gds_properties(magfile):
267 proprex = re.compile('^[ \t]*string[ \t]+(GDS_[^ \t]+)[ \t]+([^ \t]+)$')
268 proplines = []
269 if os.path.isfile(magfile):
270 with open(magfile, 'r') as ifile:
271 magtext = ifile.read().splitlines()
272 for line in magtext:
273 lmatch = proprex.match(line)
274 if lmatch:
275 propline = lmatch.group(1) + ' ' + lmatch.group(2)
276 proplines.append(propline)
277 return proplines
278
279#----------------------------------------------------------------------------
280# Read subcircuit ports from a CDL file, given a subcircuit name that should
281# appear in the file as a subcircuit entry, and return a dictionary of ports
282# and their indexes in the subcircuit line.
283#----------------------------------------------------------------------------
284
285def get_subckt_ports(cdlfile, subname):
286 portdict = {}
287 pidx = 1
288 portrex = re.compile('^\.subckt[ \t]+([^ \t]+)[ \t]+(.*)$', flags=re.IGNORECASE)
289 with open(cdlfile, 'r') as ifile:
290 cdltext = ifile.read()
291 cdllines = cdltext.replace('\n+', ' ').splitlines()
292 for line in cdllines:
293 lmatch = portrex.match(line)
294 if lmatch:
295 if lmatch.group(1).lower() == subname.lower():
296 ports = lmatch.group(2).split()
297 for port in ports:
298 portdict[port.lower()] = pidx
299 pidx += 1
300 break
301 return portdict
302
303#----------------------------------------------------------------------------
304# Filter a verilog file to remove any backslash continuation lines, which
305# iverilog does not parse. If targetroot is a directory, then find and
306# process all files in the path of targetroot. If any file to be processed
307# is unmodified (has no backslash continuation lines), then ignore it. If
308# any file is a symbolic link and gets modified, then remove the symbolic
309# link before overwriting with the modified file.
310#----------------------------------------------------------------------------
311
312def vfilefilter(vfile):
313 modified = False
314 with open(vfile, 'r') as ifile:
315 vtext = ifile.read()
316
317 # Remove backslash-followed-by-newline and absorb initial whitespace. It
318 # is unclear what initial whitespace means in this context, as the use-
319 # case that has been seen seems to work under the assumption that leading
320 # whitespace is ignored up to the amount used by the last indentation.
321
322 vlines = re.sub('\\\\\n[ \t]*', '', vtext)
323
324 if vlines != vtext:
325 # File contents have been modified, so if this file was a symbolic
326 # link, then remove it. Otherwise, overwrite the file with the
327 # modified contents.
328 if os.path.islink(vfile):
329 os.unlink(vfile)
330 with open(vfile, 'w') as ofile:
331 ofile.write(vlines)
332
333#----------------------------------------------------------------------------
334# Run a filter on verilog files that cleans up known syntax issues.
335# This is embedded in the foundry_install script and is not a custom
336# filter largely because the issue is in the tool, not the PDK.
337#----------------------------------------------------------------------------
338
339def vfilter(targetroot):
340 if os.path.isfile(targetroot):
341 vfilefilter(targetroot)
342 else:
343 vlist = glob.glob(targetroot + '/*')
344 for vfile in vlist:
345 if os.path.isfile(vfile):
346 vfilefilter(vfile)
347
348#----------------------------------------------------------------------------
349# For issues that are PDK-specific, a script can be written and put in
350# the PDK's custom/scripts/ directory, and passed to the foundry_install
351# script using the "filter" option.
352#----------------------------------------------------------------------------
353
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400354def tfilter(targetroot, filterscript, ef_format=False, outfile=[]):
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400355 filterroot = os.path.split(filterscript)[1]
356 if os.path.isfile(targetroot):
357 print(' Filtering file ' + targetroot + ' with ' + filterroot)
358 sys.stdout.flush()
359 if not outfile:
360 outfile = targetroot
361 else:
362 # Make sure this file is writable (as the original may not be)
363 makeuserwritable(outfile)
364
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400365 if ef_format:
366 arguments = [filterscript, targetroot, outfile, '-ef_format']
367 else:
368 arguments = [filterscript, targetroot, outfile]
369
370 fproc = subprocess.run(arguments,
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400371 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
372 stderr = subprocess.PIPE, universal_newlines = True)
373 if fproc.stdout:
374 for line in fproc.stdout.splitlines():
375 print(line)
376 if fproc.stderr:
377 print('Error message output from filter script:')
378 for line in fproc.stderr.splitlines():
379 print(line)
380
381 else:
382 tlist = glob.glob(targetroot + '/*')
383 for tfile in tlist:
384 if os.path.isfile(tfile):
385 print(' Filtering file ' + tfile + ' with ' + filterroot)
386 sys.stdout.flush()
387 fproc = subprocess.run([filterscript, tfile, tfile],
388 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
389 stderr = subprocess.PIPE, universal_newlines = True)
390 if fproc.stdout:
391 for line in fproc.stdout.splitlines():
392 print(line)
393 if fproc.stderr:
394 print('Error message output from filter script:')
395 for line in fproc.stderr.splitlines():
396 print(line)
397
398#----------------------------------------------------------------------------
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400399# This is the main entry point for the foundry install script.
400#----------------------------------------------------------------------------
401
402if __name__ == '__main__':
403
404 if len(sys.argv) == 1:
405 print("No options given to foundry_install.py.")
406 usage()
407 sys.exit(0)
408
409 optionlist = []
410 newopt = []
411
412 sourcedir = None
413 targetdir = None
414
415 ef_format = False
416 do_clean = False
417
418 have_lef = False
419 have_techlef = False
420 have_lefanno = False
421 have_gds = False
422 have_spice = False
423 have_cdl = False
424 have_verilog = False
425 have_lib = False
426
427 # Break arguments into groups where the first word begins with "-".
428 # All following words not beginning with "-" are appended to the
429 # same list (optionlist). Then each optionlist is processed.
430 # Note that the first entry in optionlist has the '-' removed.
431
432 for option in sys.argv[1:]:
433 if option.find('-', 0) == 0:
434 if newopt != []:
435 optionlist.append(newopt)
436 newopt = []
437 newopt.append(option[1:])
438 else:
439 newopt.append(option)
440
441 if newopt != []:
442 optionlist.append(newopt)
443
444 # Pull library names from optionlist
445 libraries = []
446 for option in optionlist[:]:
447 if option[0] == 'library':
448 optionlist.remove(option)
449 libraries.append(option[1:])
450
451 # Check for option "ef_format" or "std_format" or "clean"
452 for option in optionlist[:]:
453 if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
454 optionlist.remove(option)
455 ef_format = True
456 elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
457 optionlist.remove(option)
458 ef_format = False
459 elif option[0] == 'clean':
460 do_clean = True
461
462 # Check for options "source" and "target"
463 for option in optionlist[:]:
464 if option[0] == 'source':
465 optionlist.remove(option)
Tim Edwards855295e2021-09-08 10:57:54 -0400466 if len(option) > 1:
467 sourcedir = option[1]
468 else:
469 print('Error: Option "source" used with no value.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400470 elif option[0] == 'target':
471 optionlist.remove(option)
Tim Edwards855295e2021-09-08 10:57:54 -0400472 if len(option) > 1:
473 targetdir = option[1]
474 else:
475 print('Error: Option "target" used with no value.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400476
477 if not targetdir:
478 print("No target directory specified. Exiting.")
479 sys.exit(1)
480
481 # Take the target PDK name from the target path last component
482 pdkname = os.path.split(targetdir)[1]
483
484 # If targetdir (the staging area) exists, make sure it's empty.
485
486 if os.path.isdir(targetdir):
487 # Error if targetdir exists but is not writeable
488 if not os.access(targetdir, os.W_OK):
489 print("Target installation directory " + targetdir + " is not writable.")
490 sys.exit(1)
491
492 # Clear out the staging directory if specified
493 if do_clean:
494 shutil.rmtree(targetdir)
495 elif os.path.exists(targetdir):
496 print("Target installation directory " + targetdir + " is not a directory.")
497 sys.exit(1)
498
499 # Error if no source or dest specified unless "-clean" was specified
500 if not sourcedir:
501 if do_clean:
502 print("Done removing staging area.")
503 sys.exit(0)
504 else:
505 print("No source directory specified. Exiting.")
506 sys.exit(1)
507
508 # Create the target directory
509 os.makedirs(targetdir, exist_ok=True)
510
Tim Edwards5c7924d2020-09-30 22:22:10 -0400511 # Here's where common scripts are found:
512 scriptdir = os.path.split(os.getcwd())[0] + '/common'
513
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400514 #----------------------------------------------------------------
515 # Installation part 1: Install files into the staging directory
516 #----------------------------------------------------------------
517
518 # Diagnostic
519 print("Installing in target (staging) directory " + targetdir)
520
521 # Create the top-level directories
522
523 os.makedirs(targetdir + '/libs.tech', exist_ok=True)
524 os.makedirs(targetdir + '/libs.ref', exist_ok=True)
525
526 # Path to magic techfile depends on ef_format
527
528 if ef_format == True:
529 mag_current = '/libs.tech/magic/current/'
530 else:
531 mag_current = '/libs.tech/magic/'
532
533 # Check for magic version and set flag if it does not exist or if
534 # it has the wrong version.
535 have_mag_8_2 = False
536 try:
537 mproc = subprocess.run(['magic', '--version'],
538 stdout = subprocess.PIPE,
539 stderr = subprocess.PIPE,
540 universal_newlines = True)
541 if mproc.stdout:
542 mag_version = mproc.stdout.splitlines()[0]
543 mag_version_info = mag_version.split('.')
544 try:
545 if int(mag_version_info[0]) > 8:
546 have_mag_8_2 = True
547 elif int(mag_version_info[0]) == 8:
548 if int(mag_version_info[1]) >= 2:
549 have_mag_8_2 = True
Tim Edwardsaac3d1a2021-06-15 16:37:10 -0400550 print('Magic version 8.2 (or better) available on the system.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400551 except ValueError:
552 print('Error: "magic --version" did not return valid version number.')
553 except FileNotFoundError:
554 print('Error: Failed to find executable for magic in standard search path.')
555
556 if not have_mag_8_2:
Tim Edwardsaac3d1a2021-06-15 16:37:10 -0400557 print('WARNING: Magic version 8.2 (or beter) cannot be executed ')
558 print('from the standard executable search path.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400559 print('Please install or correct the search path.')
560 print('Magic database files will not be created, and other missing file formats may not be generated.')
561
562 # Populate any targets that do not specify a library, or where the library is
563 # specified as "primitive".
564
565 # Populate the techLEF and SPICE models, if specified. Also, this section can add
566 # to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
567 # install into libs.tech/ngspice/.
568
569 if libraries == [] or 'primitive' in libraries[0]:
570
571 for option in optionlist[:]:
572
573 # Legacy behavior is to put libs.tech models and techLEF files in
574 # the same grouping as files for the primdev library (which go in
575 # libs.ref). Current behavior is to put all libs.tech files in
576 # a grouping with no library, with unrestricted ability to write
577 # into any subdirectory of libs.tech/. Therefore, need to restrict
578 # legacy use to just 'techlef' and 'models'.
579
580 if len(libraries) > 0 and 'primitive' in libraries[0]:
581 if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
582 continue
583
584 # Normally technology LEF files are associated with IP libraries.
585 # However, if no library is specified or the library is 'primitive'
586 # (legacy behavior), then put in the techLEF directory with no subdirectory.
587
588 filter_scripts = []
589 if option[0] == 'techlef' or option[0] == 'techLEF':
590 for item in option:
591 if item.split('=')[0] == 'filter':
592 filter_scripts.append(item.split('=')[1])
593 break
594
595 if ef_format:
596 techlefdir = targetdir + '/libs.ref/' + 'techLEF'
597 else:
598 techlefdir = targetdir + '/libs.tech/lef'
599
600 os.makedirs(techlefdir, exist_ok=True)
601 # All techlef files should be copied, so use "glob" on the wildcards
602 techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
603
604 for lefname in techlist:
605 leffile = os.path.split(lefname)[1]
606 targname = techlefdir + '/' + leffile
607
608 if os.path.isfile(lefname):
609 shutil.copy(lefname, targname)
610 else:
611 shutil.copytree(lefname, targname)
612
613 for filter_script in filter_scripts:
614 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400615 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400616
617 optionlist.remove(option)
618
619 # All remaining options will refer to specific tools (e.g., -ngspice, -magic)
620 # although generic names (.e.g, -models) are acceptable if the tools know
621 # where to find the files. Currently, most tools have their own formats
622 # and standards for setup, and so generally each install directory will be
623 # unique to one EDA tool.
624
625 else:
626 filter_scripts = []
627 for item in option:
628 if item.split('=')[0] == 'filter':
629 filter_scripts.append(item.split('=')[1])
630 break
631
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400632 print('Diagnostic: installing to ' + option[0] + '.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400633 tooldir = targetdir + '/libs.tech/' + option[0]
634 os.makedirs(tooldir, exist_ok=True)
635
636 # All files should be linked or copied, so use "glob" on
637 # the wildcards. Copy each file and recursively copy each
638 # directory.
639 toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
640
641 for toolname in toollist:
642 toolfile = os.path.split(toolname)[1]
643 targname = tooldir + '/' + toolfile
644
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400645 print(' installing from ' + toolfile + ' to ' + targname)
646
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400647 if os.path.isdir(toolname):
648 # Remove any existing directory, and its contents
649 if os.path.isdir(targname):
650 shutil.rmtree(targname)
651 os.makedirs(targname)
652
653 # Recursively find and copy or link the whole directory
654 # tree from this point.
655
656 alltoollist = glob.glob(toolname + '/**', recursive=True)
657 commonpart = os.path.commonpath(alltoollist)
658 for subtoolname in alltoollist:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400659 # Get the path part that is not common between toollist and
660 # alltoollist.
661 subpart = os.path.relpath(subtoolname, commonpart)
662 subtargname = targname + '/' + subpart
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400663
664 if os.path.isfile(subtoolname):
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400665 os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400666 shutil.copy(subtoolname, subtargname)
667 else:
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400668 print(' copy tree from ' + subtoolname + ' to ' + subtargname)
669 # emulate Python3.8 dirs_exist_ok option
670 try:
671 shutil.copytree(subtoolname, subtargname)
672 except FileExistsError:
673 pass
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400674
675 for filter_script in filter_scripts:
676 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400677 tfilter(subtargname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400678
679 else:
680 # Remove any existing file
681 if os.path.isfile(targname):
682 os.remove(targname)
683 elif os.path.isdir(targname):
684 shutil.rmtree(targname)
685
686 if os.path.isfile(toolname):
687 shutil.copy(toolname, targname)
688 else:
689 shutil.copytree(toolname, targname)
690
691 for filter_script in filter_scripts:
692 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400693 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400694
695 optionlist.remove(option)
696
697 # Do an initial pass through all of the options and determine what is being
698 # installed, so that we know in advance which file formats are missing and
699 # need to be generated.
700
701 for option in optionlist[:]:
702 if option[0] == 'lef':
Tim Edwards91ddc9a2021-06-24 22:50:30 -0400703 have_lefanno = True if 'annotate' in option or 'anno' in option else False
704 have_lef = True if not have_lefanno else False
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400705 if option[0] == 'techlef' or option[0] == 'techLEF':
706 have_techlef = True
707 elif option[0] == 'gds':
708 have_gds = True
709 elif option[0] == 'spice' or option[0] == 'spi':
710 have_spice = True
711 elif option[0] == 'cdl':
712 have_cdl = True
713 elif option[0] == 'verilog':
714 have_verilog = True
715 elif option[0] == 'lib' or option[0] == 'liberty':
716 have_lib = True
717
718 # The remaining options in optionlist should all be types like 'lef' or 'liberty'
719 # and there should be a corresponding library list specified by '-library'
720
721 for option in optionlist[:]:
722
723 # Ignore if no library list---should have been taken care of above.
724 if libraries == []:
725 break
726
727 # Diagnostic
728 print("Install option: " + str(option[0]))
729
Tim Edwards91ddc9a2021-06-24 22:50:30 -0400730 if option[0] == 'lef' and have_lefanno:
731 print("LEF files used for annotation only. Temporary install.")
732
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400733 # For ef_format: always make techlef -> techLEF and spice -> spi
734
735 if ef_format:
736 if option[0] == 'techlef':
737 option[0] = 'techLEF'
738 elif option[0] == 'spice':
739 option[0] = 'spi'
740
741 destdir = targetdir + '/libs.ref/' + option[0]
742 os.makedirs(destdir, exist_ok=True)
743
744 # If the option is followed by the keyword "up" and a number, then
745 # the source should be copied (or linked) from <number> levels up
746 # in the hierarchy (see below).
747
Tim Edwards995c1332020-09-25 15:33:58 -0400748 hier_up = 0
749 for item in option:
750 if item.split('=')[0] == 'up':
751 hier_up = int(item.split('=')[1])
752 break
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400753
754 filter_scripts = []
755 for item in option:
756 if item.split('=')[0] == 'filter':
757 filter_scripts.append(item.split('=')[1])
758 break
759
760 # Option 'stub' applies to netlists ('cdl' or 'spice') and generates
761 # a file with only stub entries.
762 do_stub = 'stub' in option
763
764 # Option 'compile' is a standalone keyword ('comp' may be used).
765 do_compile = 'compile' in option or 'comp' in option
766 do_compile_only = 'compile-only' in option or 'comp-only' in option
767
768 # Option 'nospecify' is a standalone keyword ('nospec' may be used).
769 do_remove_spec = 'nospecify' in option or 'nospec' in option
770
771 # Option 'exclude' has an argument
Tim Edwards4697a172021-11-05 22:47:07 -0400772 excludelist = []
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400773 try:
774 excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
775 except IndexError:
Tim Edwards4697a172021-11-05 22:47:07 -0400776 pass
777
778 if len(excludelist) > 0:
Tim Edwardsf35c6072021-06-15 16:00:27 -0400779 print('Excluding files: ' + (',').join(excludelist))
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400780
Tim Edwards4697a172021-11-05 22:47:07 -0400781 # Option 'no-copy' has an argument
782 nocopylist = []
783 try:
784 nocopylist = list(item.split('=')[1].split(',') for item in option if item.startswith('no-copy'))[0]
785 except IndexError:
786 pass
787
788 if len(nocopylist) > 0:
789 print('Not copying files: ' + (',').join(nocopylist))
790
791 # Option 'include' has an argument
792 includelist = []
793 try:
794 includelist = list(item.split('=')[1].split(',') for item in option if item.startswith('incl'))[0]
795 except IndexError:
796 pass
797
798 if len(includelist) > 0:
799 print('Including files: ' + (',').join(includelist))
800
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400801 # Option 'rename' has an argument
802 try:
803 newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
804 except IndexError:
805 newname = None
806 else:
807 print('Renaming file to: ' + newname)
808
Tim Edwards5c7924d2020-09-30 22:22:10 -0400809 # Option 'sort' has an argument. . .
Tim Edwards995c1332020-09-25 15:33:58 -0400810 try:
811 sortscript = list(item.split('=')[1] for item in option if item.startswith('sort'))[0]
812 except IndexError:
Tim Edwards5c7924d2020-09-30 22:22:10 -0400813 # If option 'sort' is not specified, then use the "natural sort" script
814 sortscript = scriptdir + '/sort_pdkfiles.py'
Tim Edwards995c1332020-09-25 15:33:58 -0400815 else:
816 print('Sorting files with script ' + sortscript)
Tim Edwards995c1332020-09-25 15:33:58 -0400817
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400818 # For each library, create the library subdirectory
819 for library in libraries:
820 if len(library) == 3:
821 destlib = library[2]
822 else:
823 destlib = library[1]
824
825 if ef_format:
826 destlibdir = destdir + '/' + destlib
827 else:
828 destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
829 destlibdir = destdir
830
831 os.makedirs(destlibdir, exist_ok=True)
832
833 # Populate the library subdirectory
834 # Parse the option and replace each '/*/' with the library name,
835 # and check if it is a valid directory name. Then glob the
836 # resulting option name. Warning: This assumes that all
837 # occurences of the text '/*/' match a library name. It should
838 # be possible to wild-card the directory name in such a way that
839 # this is always true.
840
841 testpath = substitute(sourcedir + '/' + option[1], library[1])
842 liblist = glob.glob(testpath)
843
844 # Create a file "sources.txt" (or append to it if it exists)
845 # and add the source directory name so that the staging install
846 # script can know where the files came from.
847
848 with open(destlibdir + '/sources.txt', 'a') as ofile:
849 print(testpath, file=ofile)
850
851 # Create exclude list with glob-style matching using fnmatch
852 if len(liblist) > 0:
853 liblistnames = list(os.path.split(item)[1] for item in liblist)
854 notliblist = []
855 for exclude in excludelist:
Tim Edwards4697a172021-11-05 22:47:07 -0400856 if '/' in exclude:
857 # Names come from files in a path that is not the source
858 excludefiles = os.listdir(os.path.split(exclude)[0])
859 pattern = os.path.split(exclude)[1]
860 notliblist.extend(fnmatch.filter(excludefiles, pattern))
861 else:
862 notliblist.extend(fnmatch.filter(liblistnames, exclude))
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400863
864 # Apply exclude list
865 if len(notliblist) > 0:
866 for file in liblist[:]:
867 if os.path.split(file)[1] in notliblist:
868 liblist.remove(file)
869
870 if len(excludelist) > 0 and len(notliblist) == 0:
871 print('Warning: Nothing from the exclude list found in sources.')
872 print('excludelist = ' + str(excludelist))
873 print('destlibdir = ' + destlibdir)
874
Tim Edwards4697a172021-11-05 22:47:07 -0400875 # Create a list of cell names not to be copied from "nocopylist"
876 nocopynames = []
877 for nocopy in nocopylist:
878 if '/' in nocopy:
879 # Names come from files in a path that is not the source
880 nocopyfiles = os.listdir(os.path.split(nocopy)[0])
881 pattern = os.path.split(nocopy)[1]
882 nocopynames.extend(fnmatch.filter(nocopyfiles, pattern))
883 else:
884 nocopynames.extend(fnmatch.filter(liblistnames, nocopy))
885
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400886 # Diagnostic
887 print('Collecting files from ' + testpath)
888 print('Files to install:')
889 if len(liblist) < 10:
890 for item in liblist:
891 print(' ' + item)
892 else:
893 for item in liblist[0:4]:
894 print(' ' + item)
895 print(' .')
896 print(' .')
897 print(' .')
898 for item in liblist[-6:-1]:
899 print(' ' + item)
900 print('(' + str(len(liblist)) + ' files total)')
901
Tim Edwards995c1332020-09-25 15:33:58 -0400902 destfilelist = []
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400903 for libname in liblist:
Tim Edwards4697a172021-11-05 22:47:07 -0400904
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400905 # Note that there may be a hierarchy to the files in option[1],
906 # say for liberty timing files under different conditions, so
907 # make sure directories have been created as needed.
908
909 libfile = os.path.split(libname)[1]
910 libfilepath = os.path.split(libname)[0]
911 destpathcomp = []
912 for i in range(hier_up):
913 destpathcomp.append('/' + os.path.split(libfilepath)[1])
914 libfilepath = os.path.split(libfilepath)[0]
915 destpathcomp.reverse()
916 destpath = ''.join(destpathcomp)
917
Tim Edwards4697a172021-11-05 22:47:07 -0400918 dontcopy = True if libfile in nocopynames else False
919
Tim Edwards05e66eb2020-09-24 13:11:59 -0400920 if option[0] == 'verilog':
921 fileext = '.v'
Tim Edwards05e66eb2020-09-24 13:11:59 -0400922 elif option[0] == 'liberty' or option[0] == 'lib':
923 fileext = '.lib'
924 elif option[0] == 'spice' or option[0] == 'spi':
925 fileext = '.spice' if not ef_format else '.spi'
Tim Edwardsbbab9f62020-11-02 10:12:20 -0500926 elif option[0] == 'techlef':
Tim Edwards05e66eb2020-09-24 13:11:59 -0400927 fileext = '.lef'
Tim Edwardsbbab9f62020-11-02 10:12:20 -0500928 else:
929 fileext = '.' + option[0]
Tim Edwards05e66eb2020-09-24 13:11:59 -0400930
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400931 if newname:
Tim Edwards05e66eb2020-09-24 13:11:59 -0400932 if os.path.splitext(newname)[1] == '':
933 newname = newname + fileext
934
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400935 if len(liblist) == 1:
936 destfile = newname
937 else:
938 if not do_compile and not do_compile_only:
939 print('Error: rename specified but more than one file found!')
940 destfile = libfile
941 else:
942 destfile = libfile
943
944 targname = destlibdir + destpath + '/' + destfile
945
946 # NOTE: When using "up" with link_from, could just make
947 # destpath itself a symbolic link; this way is more flexible
948 # but adds one symbolic link per file.
949
950 if destpath != '':
951 if not os.path.isdir(destlibdir + destpath):
952 os.makedirs(destlibdir + destpath, exist_ok=True)
953
954 # Remove any existing file
955 if os.path.isfile(targname):
Tim Edwards4697a172021-11-05 22:47:07 -0400956 if not dontcopy:
957 os.remove(targname)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400958 elif os.path.isdir(targname):
Tim Edwards4697a172021-11-05 22:47:07 -0400959 if not dontcopy:
960 shutil.rmtree(targname)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400961
962 # NOTE: Diagnostic, probably much too much output.
963 print(' Install:' + libname + ' to ' + targname)
964 if os.path.isfile(libname):
Tim Edwards4697a172021-11-05 22:47:07 -0400965 if not dontcopy:
966 shutil.copy(libname, targname)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400967 else:
Tim Edwards4697a172021-11-05 22:47:07 -0400968 if not dontcopy:
969 shutil.copytree(libname, targname)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400970
971 # File filtering options: Two options 'stub' and 'nospec' are
972 # handled by scripts in ../common/. Custom filters can also be
973 # specified.
974
975 local_filter_scripts = filter_scripts[:]
976
977 if option[0] == 'verilog':
978 # Internally handle syntactical issues with verilog and iverilog
979 vfilter(targname)
980
981 if do_remove_spec:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400982 local_filter_scripts.append(scriptdir + '/remove_specify.py')
983
984 elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
985 if do_stub:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400986 local_filter_scripts.append(scriptdir + '/makestub.py')
987
988 for filter_script in local_filter_scripts:
989 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400990 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400991
Tim Edwards995c1332020-09-25 15:33:58 -0400992 destfilelist.append(os.path.split(targname)[1])
993
Tim Edwards4697a172021-11-05 22:47:07 -0400994 # Add names from "include" list to destfilelist before writing
995 # filelist.txt for library file compiling.
996
997 includenames = []
998 for incname in includelist:
999 if '/' in incname:
1000 # Names come from files in a path that is not the source
1001 incfiles = os.listdir(os.path.split(incname)[0])
1002 pattern = os.path.split(incname)[1]
1003 destfilelist.extend(fnmatch.filter(incfiles, pattern))
1004 else:
1005 destfilelist.extend(fnmatch.filter(liblistnames, incname))
1006
Tim Edwards995c1332020-09-25 15:33:58 -04001007 if sortscript:
Tim Edwardsf35c6072021-06-15 16:00:27 -04001008 with open(destlibdir + '/filelist.txt', 'w') as ofile:
1009 for destfile in destfilelist:
1010 print(destfile, file=ofile)
Tim Edwards995c1332020-09-25 15:33:58 -04001011 if os.path.isfile(sortscript):
1012 print('Diagnostic: Sorting files with ' + sortscript)
1013 subprocess.run([sortscript, destlibdir],
1014 stdout = subprocess.DEVNULL,
1015 stderr = subprocess.DEVNULL)
1016
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001017 if do_compile == True or do_compile_only == True:
1018 # NOTE: The purpose of "rename" is to put a destlib-named
1019 # library elsewhere so that it can be merged with another
Tim Edwards05e66eb2020-09-24 13:11:59 -04001020 # library into a compiled <destlib>.<ext> on another pass.
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001021
1022 compname = destlib
1023
1024 # To do: Make this compatible with linking from another PDK.
1025
1026 if option[0] == 'verilog':
1027 # If there is not a single file with all verilog cells in it,
1028 # then compile one, because one does not want to have to have
1029 # an include line for every single cell used in a design.
1030
1031 create_verilog_library(destlibdir, compname, do_compile_only, do_stub, excludelist)
1032
1033 elif option[0] == 'gds' and have_mag_8_2:
1034 # If there is not a single file with all GDS cells in it,
1035 # then compile one.
1036
1037 # Link to the PDK magic startup file from the target directory
1038 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1039 if not os.path.isfile(startup_script):
1040 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1041 create_gds_library(destlibdir, compname, startup_script, do_compile_only, excludelist)
1042
1043 elif option[0] == 'liberty' or option[0] == 'lib':
1044 # If there is not a single file with all liberty cells in it,
1045 # then compile one, because one does not want to have to have
1046 # an include line for every single cell used in a design.
1047
1048 create_lib_library(destlibdir, compname, do_compile_only, excludelist)
1049
1050 elif option[0] == 'spice' or option[0] == 'spi':
1051 # If there is not a single file with all SPICE subcircuits in it,
1052 # then compile one, because one does not want to have to have
1053 # an include line for every single cell used in a design.
1054
1055 spiext = '.spice' if not ef_format else '.spi'
1056 create_spice_library(destlibdir, compname, spiext, do_compile_only, do_stub, excludelist)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001057
1058 elif option[0] == 'cdl':
1059 # If there is not a single file with all CDL subcircuits in it,
1060 # then compile one, because one does not want to have to have
1061 # an include line for every single cell used in a design.
1062
1063 create_spice_library(destlibdir, compname, '.cdl', do_compile_only, do_stub, excludelist)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001064
1065 elif option[0] == 'lef':
1066 # If there is not a single file with all LEF cells in it,
1067 # then compile one, because one does not want to have to have
1068 # an include line for every single cell used in a design.
1069
Tim Edwardsb063d372021-11-12 16:15:18 -05001070 if not have_lefanno:
1071 create_lef_library(destlibdir, compname, do_compile_only, excludelist)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001072
Tim Edwards05e66eb2020-09-24 13:11:59 -04001073 if do_compile_only == True:
Tim Edwards4db522f2021-05-04 13:34:40 -04001074 if newname and targname:
Tim Edwards05e66eb2020-09-24 13:11:59 -04001075 if os.path.isfile(targname):
1076 os.remove(targname)
1077
1078 # "rename" with "compile" or "compile-only": Change the name
1079 # of the compiled file.
1080
1081 if newname:
1082 print(' Renaming ' + compname + fileext + ' to ' + newname)
1083 origname = destlibdir + '/' + compname + fileext
1084 targrename = destlibdir + destpath + '/' + newname
1085 if os.path.isfile(origname):
1086 os.rename(origname, targrename)
Tim Edwards995c1332020-09-25 15:33:58 -04001087
1088 # If "filelist.txt" was created, remove it
Tim Edwardsf35c6072021-06-15 16:00:27 -04001089 if sortscript:
1090 if os.path.isfile(destlibdir + '/filelist.txt'):
1091 os.remove(destlibdir + '/filelist.txt')
Tim Edwards9be4ac22020-07-26 12:59:30 -04001092
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001093 # Find any libraries/options marked as "privileged" (or "private") and
1094 # move the files from libs.tech or libs.ref to libs.priv, leaving a
1095 # symbolic link in the original location. Do this during the initial
1096 # install so that options following in the list can add files to the
1097 # non-privileged equivalent directory path.
1098
1099 if 'priv' in option or 'privileged' in option or 'private' in option:
1100
1101 # Diagnostic
1102 print("Install option: " + str(option[0]))
1103
1104 if ef_format == True:
1105 os.makedirs(targetdir + '/libs.priv', exist_ok=True)
1106
1107 for library in libraries:
1108 if len(library) == 3:
1109 destlib = library[2]
1110 else:
1111 destlib = library[1]
1112
1113 if ef_format:
1114 srclibdir = targetdir + '/libs.ref/' + option[0] + '/' + destlib
1115 destlibdir = targetdir + '/libs.priv/' + option[0] + '/' + destlib
1116 else:
1117 srclibdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
1118 destlibdir = targetdir + '/libs.priv/' + destlib + '/' + option[0]
1119
1120 if not os.path.exists(destlibdir):
1121 os.makedirs(destlibdir)
1122
1123 print('Moving files in ' + srclibdir + ' to privileged space.')
1124 filelist = os.listdir(srclibdir)
1125 for file in filelist:
1126 srcfile = srclibdir + '/' + file
1127 destfile = destlibdir + '/' + file
1128 if os.path.isfile(destfile):
1129 os.remove(destfile)
1130 elif os.path.isdir(destfile):
1131 shutil.rmtree(destfile)
1132
1133 if os.path.isfile(srcfile):
1134 shutil.copy(srcfile, destfile)
1135 os.remove(srcfile)
1136 else:
1137 shutil.copytree(srcfile, destfile)
1138 shutil.rmtree(srcfile)
1139
1140 print("Completed installation of vendor files.")
1141
1142 #----------------------------------------------------------------
1143 # Installation part 2: Generate derived file formats
1144 #----------------------------------------------------------------
1145
1146 # Now for the harder part. If GDS and/or LEF databases were specified,
1147 # then migrate them to magic (.mag files in layout/ or abstract/).
1148
1149 ignorelist = []
Tim Edwardsc02ef362021-01-04 10:06:56 -05001150 tclscript = None
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001151 do_cdl_scaleu = False
1152 no_cdl_convert = False
1153 no_gds_convert = False
1154 no_lef_convert = False
1155 cdl_compile_only = False
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001156 lef_compile = False
1157 lef_compile_only = False
Tim Edwardsb063d372021-11-12 16:15:18 -05001158 lefopts = None
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001159
1160 cdl_exclude = []
1161 lef_exclude = []
1162 gds_exclude = []
1163 spice_exclude = []
1164 verilog_exclude = []
1165
1166 cdl_reflib = '/libs.ref/'
1167 gds_reflib = '/libs.ref/'
1168 lef_reflib = '/libs.ref/'
1169
1170 for option in optionlist[:]:
1171 if option[0] == 'cdl':
1172 # Option 'scaleu' is a standalone keyword
1173 do_cdl_scaleu = 'scaleu' in option
1174
1175 # Option 'ignore' has arguments after '='
1176 for item in option:
1177 if item.split('=')[0] == 'ignore':
1178 ignorelist = item.split('=')[1].split(',')
1179
Tim Edwards26ab4962021-01-03 14:22:54 -05001180 elif option[0] == 'gds':
1181 for item in option:
1182 if item.split('=')[0] == 'options':
1183 tclscript = item.split('=')[1]
Tim Edwards4db522f2021-05-04 13:34:40 -04001184 tcllines = []
Tim Edwards26ab4962021-01-03 14:22:54 -05001185 print('Adding Tcl script options from file ' + tclscript)
1186
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001187 # Option 'noconvert' is a standalone keyword.
1188 if 'noconvert' in option:
1189 if option[0] == 'cdl':
1190 no_cdl_convert = True
1191 elif option[0] == 'gds':
1192 no_gds_convert = True
1193 elif option[0] == 'lef':
1194 no_lef_convert = True
1195
1196 # Option 'privileged' is a standalone keyword.
1197 if 'priv' in option or 'privileged' in option or 'private' in option:
1198 if option[0] == 'cdl':
1199 cdl_reflib = '/libs.priv/'
1200 elif option[0] == 'gds':
1201 gds_reflib = '/libs.priv/'
1202 elif option[0] == 'lef':
1203 lef_reflib = '/libs.priv/'
1204
1205 # If CDL is marked 'compile-only' then CDL should only convert the
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001206 # compiled file to SPICE if conversion is needed. If LEF is marked
1207 # 'compile' or 'compile-only' in annotate mode, then create a LEF
1208 # library from magic LEF output.
1209
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001210 if 'compile-only' in option:
1211 if option[0] == 'cdl':
1212 cdl_compile_only = True
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001213 elif option[0] == 'lef':
1214 lef_compile_only = True
1215 elif 'compile' in option:
1216 if option[0] == 'lef':
1217 lef_compile = True
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001218
1219 # Find exclude list for any option
1220 for item in option:
1221 if item.split('=')[0] == 'exclude':
1222 exclude_list = item.split('=')[1].split(',')
1223 if option[0] == 'cdl':
1224 cdl_exclude = exclude_list
1225 elif option[0] == 'lef':
1226 lef_exclude = exclude_list
1227 elif option[0] == 'gds':
1228 gds_exclude = exclude_list
1229 elif option[0] == 'spi' or option[0] == 'spice':
1230 spice_exclude = exclude_list
1231 elif option[0] == 'verilog':
1232 verilog_exclude = exclude_list
Tim Edwardsb063d372021-11-12 16:15:18 -05001233
1234 # Find options list for "lef write"
1235 for item in option:
1236 if item.split('=')[0] == 'lefopts':
1237 if option[0] == 'lef':
1238 lefopts = item.split('=')[1].strip('"')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001239
1240 devlist = []
1241 pdklibrary = None
1242
Tim Edwards4db522f2021-05-04 13:34:40 -04001243 if tclscript:
1244 # If tclscript is a file, then read it. Otherwise, assume
1245 # that the option contents should be inserted verbatim.
1246 if os.path.isfile(tclscript):
1247 with open(tclscript, 'r') as ifile:
1248 tcllines = ifile.read().splitlines()
1249 else:
1250 tcllines = list(tclscript)
1251
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001252 if have_gds and not no_gds_convert:
1253 print("Migrating GDS files to layout.")
1254
1255 if ef_format:
1256 destdir = targetdir + gds_reflib + 'mag'
1257 srcdir = targetdir + gds_reflib + 'gds'
1258 vdir = targetdir + '/libs.ref/' + 'verilog'
1259 cdir = targetdir + cdl_reflib + 'cdl'
1260 sdir = targetdir + cdl_reflib + 'spi'
1261
1262 os.makedirs(destdir, exist_ok=True)
1263
1264 # For each library, create the library subdirectory
1265 for library in libraries:
1266 if len(library) == 3:
1267 destlib = library[2]
1268 else:
1269 destlib = library[1]
1270
1271 if ef_format:
1272 destlibdir = destdir + '/' + destlib
1273 srclibdir = srcdir + '/' + destlib
1274 vlibdir = vdir + '/' + destlib
1275 clibdir = cdir + '/' + destlib
1276 slibdir = sdir + '/' + destlib
1277 else:
1278 destdir = targetdir + gds_reflib + destlib + '/mag'
1279 srcdir = targetdir + gds_reflib + destlib + '/gds'
1280 vdir = targetdir + '/libs.ref/' + destlib + '/verilog'
1281 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1282 sdir = targetdir + cdl_reflib + destlib + '/spice'
1283 destlibdir = destdir
1284 srclibdir = srcdir
1285 vlibdir = vdir
1286 clibdir = cdir
1287 slibdir = sdir
1288
1289 os.makedirs(destlibdir, exist_ok=True)
1290
1291 # For primitive devices, check the PDK script and find the name
1292 # of the library and get a list of supported devices.
1293
1294 if library[0] == 'primitive':
1295 pdkscript = targetdir + mag_current + pdkname + '.tcl'
1296 print('Searching for supported devices in PDK script ' + pdkscript + '.')
1297
1298 if os.path.isfile(pdkscript):
1299 librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
1300 devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
1301 fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
1302 devlist = []
1303 fixedlist = []
1304 with open(pdkscript, 'r') as ifile:
1305 scripttext = ifile.read().splitlines()
1306 for line in scripttext:
1307 lmatch = librex.match(line)
1308 if lmatch:
1309 pdklibrary = lmatch.group(1)
1310 dmatch = devrex.match(line)
1311 if dmatch:
1312 if dmatch.group(1) == pdklibrary:
1313 devlist.append(dmatch.group(2))
1314 fmatch = fixrex.match(line)
1315 if fmatch:
1316 if fmatch.group(1) == pdklibrary:
1317 fixedlist.append(fmatch.group(2))
1318
1319 # Diagnostic
1320 print("PDK library is " + str(pdklibrary))
1321
1322 # Link to the PDK magic startup file from the target directory
1323 # If there is no -F version then look for one without -F (open source PDK)
1324 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1325 if not os.path.isfile(startup_script):
1326 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1327
1328 if have_mag_8_2 and os.path.isfile(startup_script):
1329 # If the symbolic link exists, remove it.
1330 if os.path.isfile(destlibdir + '/.magicrc'):
1331 os.remove(destlibdir + '/.magicrc')
1332 os.symlink(startup_script, destlibdir + '/.magicrc')
1333
1334 # Find GDS file names in the source
1335 print('Getting GDS file list from ' + srclibdir + '.')
1336 gdsfilesraw = os.listdir(srclibdir)
1337 gdsfiles = []
1338 for gdsfile in gdsfilesraw:
1339 gdsext = os.path.splitext(gdsfile)[1].lower()
1340 if gdsext == '.gds' or gdsext == '.gdsii' or gdsext == '.gds2':
1341 gdsfiles.append(gdsfile)
1342
1343 # Create exclude list with glob-style matching using fnmatch
1344 if len(gdsfiles) > 0:
1345 gdsnames = list(os.path.split(item)[1] for item in gdsfiles)
1346 notgdsnames = []
1347 for exclude in gds_exclude:
1348 notgdsnames.extend(fnmatch.filter(gdsnames, exclude))
1349
1350 # Apply exclude list
1351 if len(notgdsnames) > 0:
1352 for file in gdsfiles[:]:
1353 if os.path.split(file)[1] in notgdsnames:
1354 gdsfiles.remove(file)
1355
1356 # Generate a script called "generate_magic.tcl" and leave it in
1357 # the target directory. Use it as input to magic to create the
1358 # .mag files from the database.
1359
1360 print('Creating magic generation script to generate magic database files.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001361 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1362 print('#!/usr/bin/env wish', file=ofile)
1363 print('#--------------------------------------------', file=ofile)
1364 print('# Script to generate .mag files from .gds ', file=ofile)
1365 print('#--------------------------------------------', file=ofile)
Tim Edwardsb6e8c7e2021-03-30 12:20:15 -04001366 print('crashbackups stop', file=ofile)
1367 print('drc off', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001368 print('gds readonly true', file=ofile)
1369 print('gds flatten true', file=ofile)
1370 print('gds rescale false', file=ofile)
1371 print('tech unlock *', file=ofile)
1372
Tim Edwards26ab4962021-01-03 14:22:54 -05001373 # Add custom Tcl script lines before "gds read".
1374 if tclscript:
1375 for line in tcllines:
1376 print(line, file=ofile)
1377
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001378 for gdsfile in gdsfiles:
1379 # Note: DO NOT use a relative path here.
1380 print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
1381
1382 # Make sure properties include the Tcl generated cell
1383 # information from the PDK script
1384
1385 if pdklibrary:
1386 tclfixedlist = '{' + ' '.join(fixedlist) + '}'
1387 print('set devlist ' + tclfixedlist, file=ofile)
1388 print('set topcell [lindex [cellname list top] 0]',
1389 file=ofile)
1390
1391 print('foreach cellname $devlist {', file=ofile)
1392 print(' load $cellname', file=ofile)
1393 print(' property gencell $cellname', file=ofile)
1394 print(' property parameter m=1', file=ofile)
1395 print(' property library ' + pdklibrary, file=ofile)
1396 print('}', file=ofile)
1397 print('load $topcell', file=ofile)
1398
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001399 else:
1400 # Use LEF files to set the port properties
1401 if have_lefanno or have_lef:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001402 lefdirname = 'lef'
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001403
1404 # Find LEF file names in the source
1405 if ef_format:
1406 lefsrcdir = targetdir + lef_reflib + lefdirname
1407 lefsrclibdir = lefsrcdir + '/' + destlib
1408 else:
1409 lefsrcdir = targetdir + lef_reflib + destlib + '/' + lefdirname
1410 lefsrclibdir = lefsrcdir
1411
1412 leffiles = os.listdir(lefsrclibdir)
1413 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001414 if len(leffiles) > 0:
1415 lefnames = list(os.path.split(item)[1] for item in leffiles)
1416 notlefnames = []
1417 for exclude in lef_exclude:
1418 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1419
1420 # Apply exclude list
1421 if len(notlefnames) > 0:
1422 for file in leffiles[:]:
1423 if os.path.split(file)[1] in notlefnames:
1424 leffiles.remove(file)
1425
1426 if len(leffiles) > 0:
1427 print('puts stdout "Annotating cells from LEF"', file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001428 for leffile in leffiles:
1429 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1430
1431 # Use CDL or SPICE netlists to set the port order
1432 if have_cdl or have_spice:
1433 if have_cdl:
1434 netdir = clibdir
1435 else:
1436 netdir = slibdir
1437
1438 # Find CDL/SPICE file names in the source
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001439 # Ignore "sources.txt" if it is in the list.
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001440 netfiles = os.listdir(netdir)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001441 print('puts stdout "Annotating cells from CDL/SPICE"',
1442 file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001443 for netfile in netfiles:
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001444 if os.path.split(netfile)[1] != 'sources.txt':
1445 print('catch {readspice ' + netdir + '/' + netfile
1446 + '}', file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001447
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001448 # print('cellname delete \(UNNAMED\)', file=ofile)
1449 print('puts stdout "Writing all magic database files"', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001450 print('writeall force', file=ofile)
1451
1452 leffiles = []
1453 lefmacros = []
Tim Edwardsb063d372021-11-12 16:15:18 -05001454 if have_lefanno:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001455 # Find LEF file names in the source
1456 if ef_format:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001457 lefsrcdir = targetdir + lef_reflib + 'lef'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001458 lefsrclibdir = lefsrcdir + '/' + destlib
1459 else:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001460 lefsrcdir = targetdir + lef_reflib + destlib + '/lef'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001461 lefsrclibdir = lefsrcdir
1462
1463 leffiles = os.listdir(lefsrclibdir)
1464 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
Tim Edwards41c19302021-06-25 15:45:16 -04001465 # Create exclude list with glob-style matching using fnmatch
1466 if len(leffiles) > 0:
1467 lefnames = list(os.path.split(item)[1] for item in leffiles)
1468 notlefnames = []
1469 for exclude in lef_exclude:
1470 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1471
1472 # Apply exclude list
1473 if len(notlefnames) > 0:
1474 for file in leffiles[:]:
1475 if os.path.split(file)[1] in notlefnames:
1476 leffiles.remove(file)
1477
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001478 # Get list of abstract views to make from LEF macros
Tim Edwards41c19302021-06-25 15:45:16 -04001479 # (Note: exclude list can only contain the file being
1480 # read, not individual macro names in the file; might
1481 # need some additional feature to accommodate this.)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001482 for leffile in leffiles:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001483 with open(lefsrclibdir + '/' + leffile, 'r') as ifile:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001484 ltext = ifile.read()
1485 llines = ltext.splitlines()
1486 for lline in llines:
1487 ltok = re.split(' |\t|\(', lline)
1488 if ltok[0] == 'MACRO':
1489 lefmacros.append(ltok[1])
Tim Edwardsb063d372021-11-12 16:15:18 -05001490 elif have_lef:
1491 # Nothing to do; LEF macros were already installed.
1492 pass
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001493 elif have_verilog and os.path.isdir(vlibdir):
1494 # Get list of abstract views to make from verilog modules
Tim Edwards41c19302021-06-25 15:45:16 -04001495 # (NOTE: no way to apply exclude list here!)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001496 vfiles = os.listdir(vlibdir)
1497 vfiles = list(item for item in vfiles if os.path.splitext(item)[1] == '.v')
Tim Edwards41c19302021-06-25 15:45:16 -04001498 # Create exclude list with glob-style matching using fnmatch
1499 if len(vfiles) > 0:
1500 vnames = list(os.path.split(item)[1] for item in vfiles)
1501 notvnames = []
1502 for exclude in verilog_exclude:
1503 notvnames.extend(fnmatch.filter(vnames, exclude))
1504
1505 # Apply exclude list
1506 if len(notvnames) > 0:
1507 for file in vfiles[:]:
1508 if os.path.split(file)[1] in notvnames:
1509 vfiles.remove(file)
1510
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001511 for vfile in vfiles:
1512 with open(vlibdir + '/' + vfile, 'r') as ifile:
1513 vtext = ifile.read()
1514 vlines = vtext.splitlines()
1515 for vline in vlines:
1516 vtok = re.split(' |\t|\(', vline)
1517 try:
1518 if vtok[0] == 'module':
1519 if vtok[1] not in lefmacros:
1520 lefmacros.append(vtok[1])
1521 except:
1522 pass
1523
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001524 elif have_cdl and os.path.isdir(clibdir):
1525 # Get list of abstract views to make from CDL subcircuits
1526 cfiles = os.listdir(clibdir)
1527 cfiles = list(item for item in cfiles if os.path.splitext(item)[1] == '.cdl')
Tim Edwards41c19302021-06-25 15:45:16 -04001528 # Create exclude list with glob-style matching using fnmatch
1529 if len(cfiles) > 0:
1530 cnames = list(os.path.split(item)[1] for item in cfiles)
1531 notcnames = []
1532 for exclude in cdl_exclude:
1533 notcnames.extend(fnmatch.filter(cnames, exclude))
1534
1535 # Apply exclude list
1536 if len(notcnames) > 0:
1537 for file in cfiles[:]:
1538 if os.path.split(file)[1] in notcnames:
1539 cfiles.remove(file)
1540
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001541 for cfile in cfiles:
1542 with open(clibdir + '/' + cfile, 'r') as ifile:
1543 ctext = ifile.read()
1544 clines = ctext.splitlines()
1545 for cline in clines:
1546 ctok = cline.split()
1547 try:
1548 if ctok[0].lower() == '.subckt':
1549 if ctok[1] not in lefmacros:
1550 lefmacros.append(ctok[1])
1551 except:
1552 pass
1553
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001554 elif have_spice and os.path.isdir(slibdir):
1555 # Get list of abstract views to make from SPICE subcircuits
1556 sfiles = os.listdir(slibdir)
1557 sfiles = list(item for item in sfiles)
Tim Edwards41c19302021-06-25 15:45:16 -04001558
1559 # Create exclude list with glob-style matching using fnmatch
1560 if len(sfiles) > 0:
1561 snames = list(os.path.split(item)[1] for item in sfiles)
1562 notsnames = []
1563 for exclude in spice_exclude:
1564 notsnames.extend(fnmatch.filter(snames, exclude))
1565
1566 # Apply exclude list
1567 if len(notsnames) > 0:
1568 for file in sfiles[:]:
1569 if os.path.split(file)[1] in notsnames:
1570 sfiles.remove(file)
1571
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001572 for sfile in sfiles:
1573 with open(slibdir + '/' + sfile, 'r') as ifile:
1574 stext = ifile.read()
1575 slines = stext.splitlines()
1576 for sline in slines:
1577 stok = sline.split()
1578 try:
1579 if stok[0].lower() == '.subckt':
1580 if stok[1] not in lefmacros:
1581 lefmacros.append(stok[1])
1582 except:
1583 pass
1584
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001585 if not lefmacros:
1586 print('No source for abstract views: Abstract views not made.')
Tim Edwardsb063d372021-11-12 16:15:18 -05001587 elif have_lefanno or not have_lef:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001588 # This library has a GDS database but no LEF database. Use
1589 # magic to create abstract views of the GDS cells. If
1590 # option "annotate" is given, then read the LEF file after
1591 # loading the database file to annotate the cell with
1592 # information from the LEF file. This usually indicates
1593 # that the LEF file has some weird definition of obstruction
1594 # layers and we want to normalize them by using magic's LEF
1595 # write procedure, but we still need the pin use and class
1596 # information from the LEF file, and maybe the bounding box.
1597
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001598
1599 # For annotation, the LEF file output will overwrite the
1600 # original source LEF file.
1601 lefdest = lefsrclibdir + '/' if have_lefanno else ''
1602
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001603 for leffile in leffiles:
1604 if have_lefanno:
1605 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1606 for lefmacro in lefmacros:
1607 print('if {[cellname list exists ' + lefmacro + '] != 0} {', file=ofile)
1608 print(' load ' + lefmacro, file=ofile)
Tim Edwardsb063d372021-11-12 16:15:18 -05001609 if lefopts:
1610 print(' lef write ' + lefdest + lefmacro + ' ' + lefopts, file=ofile)
1611 else:
1612 print(' lef write ' + lefdest + lefmacro, file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001613 print('}', file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001614
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001615 print('puts stdout "Done."', file=ofile)
1616 print('quit -noprompt', file=ofile)
1617
1618 print('Running magic to create magic database files.')
1619 sys.stdout.flush()
1620
1621 # Run magic to read in the GDS file and write out magic databases.
1622 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1623 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1624 stdin = ifile, stdout = subprocess.PIPE,
1625 stderr = subprocess.PIPE, cwd = destlibdir,
1626 universal_newlines = True)
1627 if mproc.stdout:
1628 for line in mproc.stdout.splitlines():
1629 print(line)
1630 if mproc.stderr:
1631 print('Error message output from magic:')
1632 for line in mproc.stderr.splitlines():
1633 print(line)
1634 if mproc.returncode != 0:
1635 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1636
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001637 # Set have_lef now that LEF files were made, so they
1638 # can be used to generate the maglef/ databases.
1639 have_lef = True
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001640
1641 elif not have_mag_8_2:
1642 print('The installer is not able to run magic.')
1643 else:
1644 print("Master PDK magic startup file not found. Did you install")
1645 print("PDK tech files before PDK vendor files?")
1646
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001647 if have_lefanno:
1648 # LEF files were used for annotation. If "compile" or "compile-only"
1649 # was also passed as an option, then build the LEF library now from
1650 # the LEF output from magic.
1651 print("Compiling LEF library from magic output.")
1652 if lef_compile or lef_compile_only:
1653 create_lef_library(lefsrclibdir, destlib, lef_compile_only, lef_exclude)
1654
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001655 if have_lef and not no_lef_convert:
1656 print("Migrating LEF files to layout.")
1657 if ef_format:
1658 destdir = targetdir + '/libs.ref/' + 'maglef'
1659 srcdir = targetdir + lef_reflib + 'lef'
1660 magdir = targetdir + gds_reflib + 'mag'
1661 cdldir = targetdir + cdl_reflib + 'cdl'
1662 os.makedirs(destdir, exist_ok=True)
1663
1664 # For each library, create the library subdirectory
1665 for library in libraries:
1666 if len(library) == 3:
1667 destlib = library[2]
1668 else:
1669 destlib = library[1]
1670
1671 if ef_format:
1672 destlibdir = destdir + '/' + destlib
1673 srclibdir = srcdir + '/' + destlib
1674 maglibdir = magdir + '/' + destlib
1675 cdllibdir = cdldir + '/' + destlib
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001676 clibdir = cdir + '/' + destlib
1677 slibdir = sdir + '/' + destlib
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001678 else:
1679 destdir = targetdir + '/libs.ref/' + destlib + '/maglef'
1680 srcdir = targetdir + lef_reflib + destlib + '/lef'
1681 magdir = targetdir + gds_reflib + destlib + '/mag'
1682 cdldir = targetdir + cdl_reflib + destlib + '/cdl'
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001683 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1684 sdir = targetdir + cdl_reflib + destlib + '/spice'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001685
1686 destlibdir = destdir
1687 srclibdir = srcdir
1688 maglibdir = magdir
1689 cdllibdir = cdldir
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001690 clibdir = cdir
1691 slibdir = sdir
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001692
1693 os.makedirs(destlibdir, exist_ok=True)
1694
1695 # Link to the PDK magic startup file from the target directory
1696 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1697 if not os.path.isfile(startup_script):
1698 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1699
1700 if have_mag_8_2 and os.path.isfile(startup_script):
1701 # If the symbolic link exists, remove it.
1702 if os.path.isfile(destlibdir + '/.magicrc'):
1703 os.remove(destlibdir + '/.magicrc')
1704 os.symlink(startup_script, destlibdir + '/.magicrc')
1705
1706 # Find LEF file names in the source
Tim Edwards855295e2021-09-08 10:57:54 -04001707 leffiles = []
1708 if os.path.isdir(srclibdir):
1709 leffiles = os.listdir(srclibdir)
1710 leffiles = list(item for item in leffiles if os.path.splitext(item)[1].lower() == '.lef')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001711
1712 # Get list of abstract views to make from LEF macros
1713 lefmacros = []
1714 err_no_macros = False
1715 for leffile in leffiles:
1716 with open(srclibdir + '/' + leffile, 'r') as ifile:
1717 ltext = ifile.read()
1718 llines = ltext.splitlines()
1719 for lline in llines:
1720 ltok = re.split(' |\t|\(', lline)
1721 if ltok[0] == 'MACRO':
1722 lefmacros.append(ltok[1])
1723
1724 # Create exclude list with glob-style matching using fnmatch
1725 if len(lefmacros) > 0:
1726 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1727 notlefnames = []
1728 for exclude in lef_exclude:
1729 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1730
1731 # Apply exclude list
1732 if len(notlefnames) > 0:
1733 for file in lefmacros[:]:
1734 if os.path.split(file)[1] in notlefnames:
1735 lefmacros.remove(file)
1736
1737 if len(leffiles) == 0:
1738 print('Warning: No LEF files found in ' + srclibdir)
1739 continue
1740
1741 print('Generating conversion script to create magic databases from LEF')
1742
1743 # Generate a script called "generate_magic.tcl" and leave it in
1744 # the target directory. Use it as input to magic to create the
1745 # .mag files from the database.
1746
1747 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1748 print('#!/usr/bin/env wish', file=ofile)
1749 print('#--------------------------------------------', file=ofile)
1750 print('# Script to generate .mag files from .lef ', file=ofile)
1751 print('#--------------------------------------------', file=ofile)
1752 print('tech unlock *', file=ofile)
1753
1754 # If there are devices in the LEF file that come from the
1755 # PDK library, then copy this list into the script.
1756
1757 if pdklibrary:
1758 shortdevlist = []
1759 for macro in lefmacros:
1760 if macro in devlist:
1761 shortdevlist.append(macro)
1762
1763 tcldevlist = '{' + ' '.join(shortdevlist) + '}'
1764 print('set devlist ' + tcldevlist, file=ofile)
1765
1766 for leffile in leffiles:
1767 print('lef read ' + srclibdir + '/' + leffile, file=ofile)
1768
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001769 # Use CDL or SPICE netlists to make sure that ports are
1770 # present, and to set the port order
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001771
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001772 if have_cdl or have_spice:
1773 if have_cdl:
1774 netdir = clibdir
1775 else:
1776 netdir = slibdir
1777
1778 # Find CDL/SPICE file names in the source
1779 # Ignore "sources.txt" if it is in the list.
1780 netfiles = os.listdir(netdir)
1781 print('puts stdout "Annotating cells from CDL/SPICE"',
1782 file=ofile)
1783 for netfile in netfiles:
1784 if os.path.split(netfile)[1] != 'sources.txt':
1785 print('catch {readspice ' + netdir + '/' + netfile
1786 + '}', file=ofile)
1787
1788 for lefmacro in lefmacros:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001789
1790 if pdklibrary and lefmacro in shortdevlist:
1791 print('set cellname ' + lefmacro, file=ofile)
1792 print('if {[lsearch $devlist $cellname] >= 0} {',
1793 file=ofile)
1794 print(' load $cellname', file=ofile)
1795 print(' property gencell $cellname', file=ofile)
1796 print(' property parameter m=1', file=ofile)
1797 print(' property library ' + pdklibrary, file=ofile)
1798 print('}', file=ofile)
1799
1800 # Load one of the LEF files so that the default (UNNAMED) cell
1801 # is not loaded, then delete (UNNAMED) so it doesn't generate
1802 # an error message.
1803 if len(lefmacros) > 0:
1804 print('load ' + lefmacros[0], file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001805 # print('cellname delete \(UNNAMED\)', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001806 else:
1807 err_no_macros = True
1808 print('writeall force', file=ofile)
1809 print('puts stdout "Done."', file=ofile)
1810 print('quit -noprompt', file=ofile)
1811
1812 if err_no_macros == True:
1813 print('Warning: No LEF macros were defined.')
1814
1815 print('Running magic to create magic databases from LEF')
1816 sys.stdout.flush()
1817
1818 # Run magic to read in the LEF file and write out magic databases.
1819 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1820 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1821 stdin = ifile, stdout = subprocess.PIPE,
1822 stderr = subprocess.PIPE, cwd = destlibdir,
1823 universal_newlines = True)
1824 if mproc.stdout:
1825 for line in mproc.stdout.splitlines():
1826 print(line)
1827 if mproc.stderr:
1828 print('Error message output from magic:')
1829 for line in mproc.stderr.splitlines():
1830 print(line)
1831 if mproc.returncode != 0:
1832 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1833
1834
1835 # Now list all the .mag files generated, and for each, read the
1836 # corresponding file from the mag/ directory, pull the GDS file
1837 # properties, and add those properties to the maglef view. Also
1838 # read the CDL (or SPICE) netlist, read the ports, and rewrite
1839 # the port order in the mag and maglef file accordingly.
1840
1841 # Diagnostic
1842 print('Annotating files in ' + destlibdir)
1843 sys.stdout.flush()
1844 magfiles = os.listdir(destlibdir)
1845 magfiles = list(item for item in magfiles if os.path.splitext(item)[1] == '.mag')
1846 for magroot in magfiles:
1847 magname = os.path.splitext(magroot)[0]
1848 magfile = maglibdir + '/' + magroot
1849 magleffile = destlibdir + '/' + magroot
1850 prop_lines = get_gds_properties(magfile)
1851
1852 # Make sure properties include the Tcl generated cell
1853 # information from the PDK script
1854
1855 prop_gencell = []
1856 if pdklibrary:
1857 if magname in fixedlist:
1858 prop_gencell.append('gencell ' + magname)
1859 prop_gencell.append('library ' + pdklibrary)
1860 prop_gencell.append('parameter m=1')
1861
1862 nprops = len(prop_lines) + len(prop_gencell)
1863
1864 cdlfile = cdllibdir + '/' + magname + '.cdl'
1865 if os.path.exists(cdlfile):
1866 cdlfiles = [cdlfile]
1867 else:
1868 # Assume there is at least one file with all cell subcircuits
1869 # in it.
1870 try:
1871 cdlfiles = glob.glob(cdllibdir + '/*.cdl')
1872 except:
1873 pass
1874 if len(cdlfiles) > 0:
1875 for cdlfile in cdlfiles:
1876 port_dict = get_subckt_ports(cdlfile, magname)
1877 if port_dict != {}:
1878 break
1879 else:
1880 port_dict = {}
1881
1882 if port_dict == {}:
1883 print('No CDL file contains ' + destlib + ' device ' + magname)
1884 cdlfile = None
1885 # To be done: If destlib is 'primitive', then look in
1886 # SPICE models for port order.
1887 if destlib == 'primitive':
1888 print('Fix me: Need to look in SPICE models!')
1889
1890 proprex = re.compile('<< properties >>')
1891 endrex = re.compile('<< end >>')
1892 rlabrex = re.compile('rlabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001893 flabrex = re.compile('flabel[ \t]+.*[ \t]+([^ \t]+)[ \t]*')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001894 portrex = re.compile('port[ \t]+([^ \t]+)[ \t]+(.*)')
1895 gcellrex = re.compile('string gencell')
1896 portnum = -1
1897
1898 with open(magleffile, 'r') as ifile:
1899 magtext = ifile.read().splitlines()
1900
1901 with open(magleffile, 'w') as ofile:
1902 has_props = False
1903 is_gencell = False
1904 for line in magtext:
1905 tmatch = portrex.match(line)
1906 if tmatch:
1907 if portnum >= 0:
1908 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1909 else:
1910 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1911 ematch = endrex.match(line)
1912 if ematch and nprops > 0:
1913 if not has_props:
1914 print('<< properties >>', file=ofile)
1915 if not is_gencell:
1916 for prop in prop_gencell:
1917 print('string ' + prop, file=ofile)
1918 for prop in prop_lines:
1919 print('string ' + prop, file=ofile)
1920
1921 print(line, file=ofile)
1922 pmatch = proprex.match(line)
1923 if pmatch:
1924 has_props = True
1925
1926 gmatch = gcellrex.match(line)
1927 if gmatch:
1928 is_gencell = True
1929
1930 lmatch = flabrex.match(line)
1931 if not lmatch:
1932 lmatch = rlabrex.match(line)
1933 if lmatch:
1934 labname = lmatch.group(1).lower()
1935 try:
1936 portnum = port_dict[labname]
1937 except:
1938 portnum = -1
1939
1940 if os.path.exists(magfile):
1941 with open(magfile, 'r') as ifile:
1942 magtext = ifile.read().splitlines()
1943
1944 with open(magfile, 'w') as ofile:
1945 for line in magtext:
1946 tmatch = portrex.match(line)
1947 if tmatch:
1948 if portnum >= 0:
1949 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1950 else:
1951 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1952 ematch = endrex.match(line)
1953 print(line, file=ofile)
1954 lmatch = flabrex.match(line)
1955 if not lmatch:
1956 lmatch = rlabrex.match(line)
1957 if lmatch:
1958 labname = lmatch.group(1).lower()
1959 try:
1960 portnum = port_dict[labname]
1961 except:
1962 portnum = -1
1963 elif os.path.splitext(magfile)[1] == '.mag':
1964 # NOTE: Possibly this means the GDS cell has a different name.
1965 print('Error: No file ' + magfile + '. Why is it in maglef???')
1966
1967 elif not have_mag_8_2:
1968 print('The installer is not able to run magic.')
1969 else:
1970 print("Master PDK magic startup file not found. Did you install")
1971 print("PDK tech files before PDK vendor files?")
1972
1973 # If SPICE or CDL databases were specified, then convert them to
1974 # a form that can be used by ngspice, using the cdl2spi.py script
1975
1976 if have_spice:
1977 if ef_format:
1978 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1979 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1980
1981 elif have_cdl and not no_cdl_convert:
1982 if ef_format:
1983 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1984 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1985
1986 print("Migrating CDL netlists to SPICE.")
1987 sys.stdout.flush()
1988
1989 if ef_format:
1990 destdir = targetdir + cdl_reflib + 'spi'
1991 srcdir = targetdir + cdl_reflib + 'cdl'
1992 os.makedirs(destdir, exist_ok=True)
1993
1994 # For each library, create the library subdirectory
1995 for library in libraries:
1996 if len(library) == 3:
1997 destlib = library[2]
1998 else:
1999 destlib = library[1]
2000
2001 if ef_format:
2002 destlibdir = destdir + '/' + destlib
2003 srclibdir = srcdir + '/' + destlib
2004 else:
2005 destdir = targetdir + cdl_reflib + destlib + '/spice'
2006 srcdir = targetdir + cdl_reflib + destlib + '/cdl'
2007
2008 destlibdir = destdir
2009 srclibdir = srcdir
2010
2011 os.makedirs(destlibdir, exist_ok=True)
2012
2013 # Find CDL file names in the source
2014 # If CDL is marked compile-only then ONLY convert <distdir>.cdl
2015 if cdl_compile_only:
2016 alllibname = destlibdir + '/' + destlib + '.cdl'
2017 if not os.path.exists(alllibname):
2018 cdl_compile_only = False
2019 else:
2020 cdlfiles = [alllibname]
2021
2022 if not cdl_compile_only:
2023 cdlfiles = os.listdir(srclibdir)
2024 cdlfiles = list(item for item in cdlfiles if os.path.splitext(item)[1].lower() == '.cdl')
2025
2026 # The directory with scripts should be in ../common with respect
2027 # to the Makefile that determines the cwd.
Tim Edwards55f4d0e2020-07-05 15:41:02 -04002028
2029 # Run cdl2spi.py script to read in the CDL file and write out SPICE
2030 for cdlfile in cdlfiles:
2031 if ef_format:
2032 spiname = os.path.splitext(cdlfile)[0] + '.spi'
2033 else:
2034 spiname = os.path.splitext(cdlfile)[0] + '.spice'
2035 procopts = [scriptdir + '/cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
2036 if do_cdl_scaleu:
2037 procopts.append('-dscale=u')
2038 for item in ignorelist:
2039 procopts.append('-ignore=' + item)
2040
2041 print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
2042 pproc = subprocess.run(procopts,
2043 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
2044 stderr = subprocess.PIPE, cwd = destlibdir,
2045 universal_newlines = True)
2046 if pproc.stdout:
2047 for line in pproc.stdout.splitlines():
2048 print(line)
2049 if pproc.stderr:
2050 print('Error message output from cdl2spi.py:')
2051 for line in pproc.stderr.splitlines():
2052 print(line)
2053
2054 elif have_gds and not no_gds_convert:
2055 # If neither SPICE nor CDL formats is available in the source, then
2056 # read GDS; if the result has no ports, then read the corresponding
2057 # LEF library to get port information. Then write out a SPICE netlist
2058 # for the whole library. NOTE: If there is no CDL or SPICE source,
2059 # then the port numbering is arbitrary, and becomes whatever the
2060 # output of this script makes it.
2061
2062 if ef_format:
2063 destdir = targetdir + cdl_reflib + 'spi'
2064 srcdir = targetdir + gds_reflib + 'gds'
2065 lefdir = targetdir + lef_reflib + 'lef'
2066 os.makedirs(destdir, exist_ok=True)
2067
2068 # For each library, create the library subdirectory
2069 for library in libraries:
2070 if len(library) == 3:
2071 destlib = library[2]
2072 else:
2073 destlib = library[1]
2074
2075 if ef_format:
2076 destlibdir = destdir + '/' + destlib
2077 srclibdir = srcdir + '/' + destlib
2078 leflibdir = lefdir + '/' + destlib
2079 else:
2080 destdir = targetdir + cdl_reflib + destlib + '/spice'
2081 srcdir = targetdir + gds_reflib + destlib + '/gds'
2082 lefdir = targetdir + lef_reflib + destlib + '/lef'
2083
2084 destlibdir = destdir
2085 srclibdir = srcdir
2086 leflibdir = lefdir
2087
2088 os.makedirs(destlibdir, exist_ok=True)
2089
2090 # Link to the PDK magic startup file from the target directory
2091 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
2092 if not os.path.isfile(startup_script):
2093 startup_script = targetdir + mag_current + pdkname + '.magicrc'
2094 if os.path.isfile(startup_script):
2095 # If the symbolic link exists, remove it.
2096 if os.path.isfile(destlibdir + '/.magicrc'):
2097 os.remove(destlibdir + '/.magicrc')
2098 os.symlink(startup_script, destlibdir + '/.magicrc')
2099
2100 # Get the consolidated GDS library file, or a list of all GDS files
2101 # if there is no single consolidated library
2102
2103 allgdslibname = srclibdir + '/' + destlib + '.gds'
2104 if not os.path.isfile(allgdslibname):
2105 glist = glob.glob(srclibdir + '/*.gds')
2106 glist.extend(glob.glob(srclibdir + '/*.gdsii'))
2107 glist.extend(glob.glob(srclibdir + '/*.gds2'))
2108
2109 allleflibname = leflibdir + '/' + destlib + '.lef'
2110 if not os.path.isfile(allleflibname):
2111 llist = glob.glob(leflibdir + '/*.lef')
2112
2113 print('Creating magic generation script to generate SPICE library.')
2114 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
2115 print('#!/usr/bin/env wish', file=ofile)
2116 print('#---------------------------------------------', file=ofile)
2117 print('# Script to generate SPICE library from GDS ', file=ofile)
2118 print('#---------------------------------------------', file=ofile)
2119 print('drc off', file=ofile)
2120 print('gds readonly true', file=ofile)
2121 print('gds flatten true', file=ofile)
2122 print('gds rescale false', file=ofile)
2123 print('tech unlock *', file=ofile)
2124
Tim Edwards8b96a1b2021-04-27 15:16:26 -04002125 # Add custom Tcl script lines before "gds read".
2126 if tclscript:
2127 for line in tcllines:
2128 print(line, file=ofile)
2129
Tim Edwards55f4d0e2020-07-05 15:41:02 -04002130 if not os.path.isfile(allgdslibname):
2131 for gdsfile in glist:
2132 print('gds read ' + gdsfile, file=ofile)
2133 else:
2134 print('gds read ' + allgdslibname, file=ofile)
2135
2136 if not os.path.isfile(allleflibname):
2137 # Annotate the cells with information from the LEF files
2138 for leffile in llist:
2139 print('lef read ' + leffile, file=ofile)
2140 else:
2141 print('lef read ' + allleflibname, file=ofile)
2142
2143 # Load first file and remove the (UNNAMED) cell
2144 if not os.path.isfile(allgdslibname):
2145 print('load ' + os.path.splitext(glist[0])[0], file=ofile)
2146 else:
2147 gdslibroot = os.path.split(allgdslibname)[1]
2148 print('load ' + os.path.splitext(gdslibroot)[0], file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05002149 # print('cellname delete \(UNNAMED\)', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04002150
2151 print('ext2spice lvs', file=ofile)
2152
2153 # NOTE: Leaving "subcircuit top" as "auto" (default) can cause
2154 # cells like decap that have no I/O to be output without a subcircuit
2155 # wrapper. Also note that if this happens, it is an indication that
2156 # power supplies have not been labeled as ports, which is harder to
2157 # handle and should be fixed in the source.
2158 print('ext2spice subcircuit top on', file=ofile)
2159
2160 print('ext2spice cthresh 0.1', file=ofile)
2161
2162 if os.path.isfile(allgdslibname):
2163 print('select top cell', file=ofile)
2164 print('set glist [cellname list children]', file=ofile)
2165 print('foreach cell $glist {', file=ofile)
2166 else:
2167 print('foreach cell [cellname list top] {', file=ofile)
2168
2169 print(' load $cell', file=ofile)
2170 print(' puts stdout "Extracting cell $cell"', file=ofile)
2171 print(' extract all', file=ofile)
2172 print(' ext2spice', file=ofile)
2173 print('}', file=ofile)
2174 print('puts stdout "Done."', file=ofile)
2175 print('quit -noprompt', file=ofile)
2176
2177 # Run magic to read in the individual GDS files and
2178 # write out the consolidated GDS library
2179
2180 print('Running magic to create GDS library.')
2181 sys.stdout.flush()
2182
2183 mproc = subprocess.run(['magic', '-dnull', '-noconsole',
2184 destlibdir + '/generate_magic.tcl'],
2185 stdin = subprocess.DEVNULL,
2186 stdout = subprocess.PIPE,
2187 stderr = subprocess.PIPE, cwd = destlibdir,
2188 universal_newlines = True)
2189 if mproc.stdout:
2190 for line in mproc.stdout.splitlines():
2191 print(line)
2192 if mproc.stderr:
2193 print('Error message output from magic:')
2194 for line in mproc.stderr.splitlines():
2195 print(line)
2196 if mproc.returncode != 0:
2197 print('ERROR: Magic exited with status ' + str(mproc.returncode))
2198
2199 # Remove intermediate extraction files
2200 extfiles = glob.glob(destlibdir + '/*.ext')
2201 for extfile in extfiles:
2202 os.remove(extfile)
2203
2204 # If the GDS file was a consolidated file of all cells, then
2205 # create a similar SPICE library of all cells.
2206
2207 if os.path.isfile(allgdslibname):
2208 spiext = '.spice' if not ef_format else '.spi'
2209 create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist)
2210
2211 sys.exit(0)