blob: 6e337f56b11ec6158456c8b363fc916aedfc8022 [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)
78# compile : Create a single library from all components. Used
79# when a foundry library has inconveniently split
80# an IP library (LEF, CDL, verilog, etc.) into
81# individual files.
82# stub : Remove contents of subcircuits from CDL or SPICE
83# netlist files.
84#
85# priv : Mark the contents being installed as privleged, and
86# put them in a separate root directory libs.priv
87# where they can be given additional read/write
88# restrictions.
89#
90# exclude : Followed by "=" and a comma-separated list of names.
91# exclude these files/modules/subcircuits. Names may
92# also be wildcarded in "glob" format.
93#
94# rename : Followed by "=" and an alternative name. For any
95# file that is a single entry, change the name of
96# the file in the target directory to this (To-do:
97# take regexps for multiple files). When used with
98# "compile" or "compile-only", this refers to the
99# name of the target compiled file.
100#
101# noconvert : Install only; do not attempt to convert to other
102# formats (applies only to GDS, CDL, and LEF).
103#
104# NOTE: This script can be called once for all libraries if all file
105# types (gds, cdl, lef, etc.) happen to all work with the same wildcards.
106# However, it is more likely that it will be called several times for the
107# same PDK, once to install I/O cells, once to install digital, and so
108# forth, as made possible by the wild-carding.
109
110import re
111import os
112import sys
113import glob
114import stat
115import shutil
116import fnmatch
117import subprocess
118
Tim Edwards51f81422020-07-26 12:49:48 -0400119# Import local routines
120from create_gds_library import create_gds_library
121from create_spice_library import create_spice_library
122from create_lef_library import create_lef_library
123from create_lib_library import create_lib_library
124from create_verilog_library import create_verilog_library
125
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400126def usage():
127 print("foundry_install.py [options...]")
128 print(" -copy Copy files from source to target (default)")
129 print(" -ef_format Use efabless naming conventions for local directories")
130 print("")
131 print(" -source <path> Path to top of source directory tree")
132 print(" -target <path> Path to top of target directory tree")
133 print("")
134 print(" -techlef <path> Path to technology LEF file")
135 print(" -doc <path> Path to technology documentation")
136 print(" -lef <path> Path to LEF file")
137 print(" -spice <path> Path to SPICE netlists")
138 print(" -cdl <path> Path to CDL netlists")
139 print(" -models <path> Path to SPICE (primitive device) models")
140 print(" -lib <path> Path to Liberty timing files")
141 print(" -liberty <path> Path to Liberty timing files")
142 print(" -gds <path> Path to GDS data")
143 print(" -verilog <path> Path to verilog models")
144 print(" -library <type> <name> [<target>] See below")
145 print("")
146 print(" All <path> names may be wild-carded with '*' ('glob'-style wild-cards)")
147 print("")
148 print(" All options with <path> other than source and target may take the additional")
149 print(" arguments 'up <number>', where <number> indicates the number of levels of")
150 print(" hierarchy of the source path to include when copying to the target.")
151 print("")
152 print(" Library <type> may be one of:")
153 print(" digital Digital standard cell library")
154 print(" primitive Primitive device library")
155 print(" general All other library types (I/O, analog, etc.)")
156 print("")
157 print(" If <target> is unspecified then <name> is used for the target.")
158
159# Return a list of files after glob-style substituting into pathname. This
160# mostly relies on glob.glob(), but uses the additional substitutions with
161# escape strings:
162#
163# %v : Match a version number in the form "major[.minor[.rev]]"
164# %l : substitute the library name
165# %% : substitute the percent character verbatim
166
167from distutils.version import LooseVersion
168
169#----------------------------------------------------------------------------
170#----------------------------------------------------------------------------
171
172def makeuserwritable(filepath):
173 if os.path.exists(filepath):
174 st = os.stat(filepath)
175 os.chmod(filepath, st.st_mode | stat.S_IWUSR)
176
177#----------------------------------------------------------------------------
178#----------------------------------------------------------------------------
179
180def substitute(pathname, library):
181 if library:
182 # Do %l substitution
183 newpathname = re.sub('%l', library, pathname)
184 else:
185 newpathname = pathname
186
187 if '%v' in newpathname:
188 vglob = re.sub('%v.*', '*', newpathname)
189 vlibs = glob.glob(vglob)
190 try:
191 vstr = vlibs[0][len(vglob)-1:]
192 except IndexError:
193 pass
194 else:
195 for vlib in vlibs[1:]:
196 vtest = vlib[len(vglob)-1:]
197 if LooseVersion(vtest) > LooseVersion(vstr):
198 vstr = vtest
199 newpathname = re.sub('%v', vstr, newpathname)
200
201 if '%%' in newpathname:
202 newpathname = re.sub('%%', '%', newpathname)
203
204 return newpathname
205
206#----------------------------------------------------------------------------
207#----------------------------------------------------------------------------
208
209def get_gds_properties(magfile):
210 proprex = re.compile('^[ \t]*string[ \t]+(GDS_[^ \t]+)[ \t]+([^ \t]+)$')
211 proplines = []
212 if os.path.isfile(magfile):
213 with open(magfile, 'r') as ifile:
214 magtext = ifile.read().splitlines()
215 for line in magtext:
216 lmatch = proprex.match(line)
217 if lmatch:
218 propline = lmatch.group(1) + ' ' + lmatch.group(2)
219 proplines.append(propline)
220 return proplines
221
222#----------------------------------------------------------------------------
223# Read subcircuit ports from a CDL file, given a subcircuit name that should
224# appear in the file as a subcircuit entry, and return a dictionary of ports
225# and their indexes in the subcircuit line.
226#----------------------------------------------------------------------------
227
228def get_subckt_ports(cdlfile, subname):
229 portdict = {}
230 pidx = 1
231 portrex = re.compile('^\.subckt[ \t]+([^ \t]+)[ \t]+(.*)$', flags=re.IGNORECASE)
232 with open(cdlfile, 'r') as ifile:
233 cdltext = ifile.read()
234 cdllines = cdltext.replace('\n+', ' ').splitlines()
235 for line in cdllines:
236 lmatch = portrex.match(line)
237 if lmatch:
238 if lmatch.group(1).lower() == subname.lower():
239 ports = lmatch.group(2).split()
240 for port in ports:
241 portdict[port.lower()] = pidx
242 pidx += 1
243 break
244 return portdict
245
246#----------------------------------------------------------------------------
247# Filter a verilog file to remove any backslash continuation lines, which
248# iverilog does not parse. If targetroot is a directory, then find and
249# process all files in the path of targetroot. If any file to be processed
250# is unmodified (has no backslash continuation lines), then ignore it. If
251# any file is a symbolic link and gets modified, then remove the symbolic
252# link before overwriting with the modified file.
253#----------------------------------------------------------------------------
254
255def vfilefilter(vfile):
256 modified = False
257 with open(vfile, 'r') as ifile:
258 vtext = ifile.read()
259
260 # Remove backslash-followed-by-newline and absorb initial whitespace. It
261 # is unclear what initial whitespace means in this context, as the use-
262 # case that has been seen seems to work under the assumption that leading
263 # whitespace is ignored up to the amount used by the last indentation.
264
265 vlines = re.sub('\\\\\n[ \t]*', '', vtext)
266
267 if vlines != vtext:
268 # File contents have been modified, so if this file was a symbolic
269 # link, then remove it. Otherwise, overwrite the file with the
270 # modified contents.
271 if os.path.islink(vfile):
272 os.unlink(vfile)
273 with open(vfile, 'w') as ofile:
274 ofile.write(vlines)
275
276#----------------------------------------------------------------------------
277# Run a filter on verilog files that cleans up known syntax issues.
278# This is embedded in the foundry_install script and is not a custom
279# filter largely because the issue is in the tool, not the PDK.
280#----------------------------------------------------------------------------
281
282def vfilter(targetroot):
283 if os.path.isfile(targetroot):
284 vfilefilter(targetroot)
285 else:
286 vlist = glob.glob(targetroot + '/*')
287 for vfile in vlist:
288 if os.path.isfile(vfile):
289 vfilefilter(vfile)
290
291#----------------------------------------------------------------------------
292# For issues that are PDK-specific, a script can be written and put in
293# the PDK's custom/scripts/ directory, and passed to the foundry_install
294# script using the "filter" option.
295#----------------------------------------------------------------------------
296
297def tfilter(targetroot, filterscript, outfile=[]):
298 filterroot = os.path.split(filterscript)[1]
299 if os.path.isfile(targetroot):
300 print(' Filtering file ' + targetroot + ' with ' + filterroot)
301 sys.stdout.flush()
302 if not outfile:
303 outfile = targetroot
304 else:
305 # Make sure this file is writable (as the original may not be)
306 makeuserwritable(outfile)
307
308 fproc = subprocess.run([filterscript, targetroot, outfile],
309 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
310 stderr = subprocess.PIPE, universal_newlines = True)
311 if fproc.stdout:
312 for line in fproc.stdout.splitlines():
313 print(line)
314 if fproc.stderr:
315 print('Error message output from filter script:')
316 for line in fproc.stderr.splitlines():
317 print(line)
318
319 else:
320 tlist = glob.glob(targetroot + '/*')
321 for tfile in tlist:
322 if os.path.isfile(tfile):
323 print(' Filtering file ' + tfile + ' with ' + filterroot)
324 sys.stdout.flush()
325 fproc = subprocess.run([filterscript, tfile, tfile],
326 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
327 stderr = subprocess.PIPE, universal_newlines = True)
328 if fproc.stdout:
329 for line in fproc.stdout.splitlines():
330 print(line)
331 if fproc.stderr:
332 print('Error message output from filter script:')
333 for line in fproc.stderr.splitlines():
334 print(line)
335
336#----------------------------------------------------------------------------
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400337# This is the main entry point for the foundry install script.
338#----------------------------------------------------------------------------
339
340if __name__ == '__main__':
341
342 if len(sys.argv) == 1:
343 print("No options given to foundry_install.py.")
344 usage()
345 sys.exit(0)
346
347 optionlist = []
348 newopt = []
349
350 sourcedir = None
351 targetdir = None
352
353 ef_format = False
354 do_clean = False
355
356 have_lef = False
357 have_techlef = False
358 have_lefanno = False
359 have_gds = False
360 have_spice = False
361 have_cdl = False
362 have_verilog = False
363 have_lib = False
364
365 # Break arguments into groups where the first word begins with "-".
366 # All following words not beginning with "-" are appended to the
367 # same list (optionlist). Then each optionlist is processed.
368 # Note that the first entry in optionlist has the '-' removed.
369
370 for option in sys.argv[1:]:
371 if option.find('-', 0) == 0:
372 if newopt != []:
373 optionlist.append(newopt)
374 newopt = []
375 newopt.append(option[1:])
376 else:
377 newopt.append(option)
378
379 if newopt != []:
380 optionlist.append(newopt)
381
382 # Pull library names from optionlist
383 libraries = []
384 for option in optionlist[:]:
385 if option[0] == 'library':
386 optionlist.remove(option)
387 libraries.append(option[1:])
388
389 # Check for option "ef_format" or "std_format" or "clean"
390 for option in optionlist[:]:
391 if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
392 optionlist.remove(option)
393 ef_format = True
394 elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
395 optionlist.remove(option)
396 ef_format = False
397 elif option[0] == 'clean':
398 do_clean = True
399
400 # Check for options "source" and "target"
401 for option in optionlist[:]:
402 if option[0] == 'source':
403 optionlist.remove(option)
404 sourcedir = option[1]
405 elif option[0] == 'target':
406 optionlist.remove(option)
407 targetdir = option[1]
408
409 if not targetdir:
410 print("No target directory specified. Exiting.")
411 sys.exit(1)
412
413 # Take the target PDK name from the target path last component
414 pdkname = os.path.split(targetdir)[1]
415
416 # If targetdir (the staging area) exists, make sure it's empty.
417
418 if os.path.isdir(targetdir):
419 # Error if targetdir exists but is not writeable
420 if not os.access(targetdir, os.W_OK):
421 print("Target installation directory " + targetdir + " is not writable.")
422 sys.exit(1)
423
424 # Clear out the staging directory if specified
425 if do_clean:
426 shutil.rmtree(targetdir)
427 elif os.path.exists(targetdir):
428 print("Target installation directory " + targetdir + " is not a directory.")
429 sys.exit(1)
430
431 # Error if no source or dest specified unless "-clean" was specified
432 if not sourcedir:
433 if do_clean:
434 print("Done removing staging area.")
435 sys.exit(0)
436 else:
437 print("No source directory specified. Exiting.")
438 sys.exit(1)
439
440 # Create the target directory
441 os.makedirs(targetdir, exist_ok=True)
442
443 #----------------------------------------------------------------
444 # Installation part 1: Install files into the staging directory
445 #----------------------------------------------------------------
446
447 # Diagnostic
448 print("Installing in target (staging) directory " + targetdir)
449
450 # Create the top-level directories
451
452 os.makedirs(targetdir + '/libs.tech', exist_ok=True)
453 os.makedirs(targetdir + '/libs.ref', exist_ok=True)
454
455 # Path to magic techfile depends on ef_format
456
457 if ef_format == True:
458 mag_current = '/libs.tech/magic/current/'
459 else:
460 mag_current = '/libs.tech/magic/'
461
462 # Check for magic version and set flag if it does not exist or if
463 # it has the wrong version.
464 have_mag_8_2 = False
465 try:
466 mproc = subprocess.run(['magic', '--version'],
467 stdout = subprocess.PIPE,
468 stderr = subprocess.PIPE,
469 universal_newlines = True)
470 if mproc.stdout:
471 mag_version = mproc.stdout.splitlines()[0]
472 mag_version_info = mag_version.split('.')
473 try:
474 if int(mag_version_info[0]) > 8:
475 have_mag_8_2 = True
476 elif int(mag_version_info[0]) == 8:
477 if int(mag_version_info[1]) >= 2:
478 have_mag_8_2 = True
479 print('Magic version 8.2 available on the system.')
480 except ValueError:
481 print('Error: "magic --version" did not return valid version number.')
482 except FileNotFoundError:
483 print('Error: Failed to find executable for magic in standard search path.')
484
485 if not have_mag_8_2:
486 print('WARNING: Magic version 8.2 cannot be executed from the standard executable search path.')
487 print('Please install or correct the search path.')
488 print('Magic database files will not be created, and other missing file formats may not be generated.')
489
490 # Populate any targets that do not specify a library, or where the library is
491 # specified as "primitive".
492
493 # Populate the techLEF and SPICE models, if specified. Also, this section can add
494 # to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
495 # install into libs.tech/ngspice/.
496
497 if libraries == [] or 'primitive' in libraries[0]:
498
499 for option in optionlist[:]:
500
501 # Legacy behavior is to put libs.tech models and techLEF files in
502 # the same grouping as files for the primdev library (which go in
503 # libs.ref). Current behavior is to put all libs.tech files in
504 # a grouping with no library, with unrestricted ability to write
505 # into any subdirectory of libs.tech/. Therefore, need to restrict
506 # legacy use to just 'techlef' and 'models'.
507
508 if len(libraries) > 0 and 'primitive' in libraries[0]:
509 if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
510 continue
511
512 # Normally technology LEF files are associated with IP libraries.
513 # However, if no library is specified or the library is 'primitive'
514 # (legacy behavior), then put in the techLEF directory with no subdirectory.
515
516 filter_scripts = []
517 if option[0] == 'techlef' or option[0] == 'techLEF':
518 for item in option:
519 if item.split('=')[0] == 'filter':
520 filter_scripts.append(item.split('=')[1])
521 break
522
523 if ef_format:
524 techlefdir = targetdir + '/libs.ref/' + 'techLEF'
525 else:
526 techlefdir = targetdir + '/libs.tech/lef'
527
528 os.makedirs(techlefdir, exist_ok=True)
529 # All techlef files should be copied, so use "glob" on the wildcards
530 techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
531
532 for lefname in techlist:
533 leffile = os.path.split(lefname)[1]
534 targname = techlefdir + '/' + leffile
535
536 if os.path.isfile(lefname):
537 shutil.copy(lefname, targname)
538 else:
539 shutil.copytree(lefname, targname)
540
541 for filter_script in filter_scripts:
542 # Apply filter script to all files in the target directory
543 tfilter(targname, filter_script)
544
545 optionlist.remove(option)
546
547 # All remaining options will refer to specific tools (e.g., -ngspice, -magic)
548 # although generic names (.e.g, -models) are acceptable if the tools know
549 # where to find the files. Currently, most tools have their own formats
550 # and standards for setup, and so generally each install directory will be
551 # unique to one EDA tool.
552
553 else:
554 filter_scripts = []
555 for item in option:
556 if item.split('=')[0] == 'filter':
557 filter_scripts.append(item.split('=')[1])
558 break
559
560 print('Diagnostic: installing ' + option[0] + '.')
561 tooldir = targetdir + '/libs.tech/' + option[0]
562 os.makedirs(tooldir, exist_ok=True)
563
564 # All files should be linked or copied, so use "glob" on
565 # the wildcards. Copy each file and recursively copy each
566 # directory.
567 toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
568
569 for toolname in toollist:
570 toolfile = os.path.split(toolname)[1]
571 targname = tooldir + '/' + toolfile
572
573 if os.path.isdir(toolname):
574 # Remove any existing directory, and its contents
575 if os.path.isdir(targname):
576 shutil.rmtree(targname)
577 os.makedirs(targname)
578
579 # Recursively find and copy or link the whole directory
580 # tree from this point.
581
582 alltoollist = glob.glob(toolname + '/**', recursive=True)
583 commonpart = os.path.commonpath(alltoollist)
584 for subtoolname in alltoollist:
585 if os.path.isdir(subtoolname):
586 continue
587 # Get the path part that is not common between toollist and
588 # alltoollist.
589 subpart = os.path.relpath(subtoolname, commonpart)
590 subtargname = targname + '/' + subpart
591 os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
592
593 if os.path.isfile(subtoolname):
594 shutil.copy(subtoolname, subtargname)
595 else:
596 shutil.copytree(subtoolname, subtargname)
597
598 for filter_script in filter_scripts:
599 # Apply filter script to all files in the target directory
600 tfilter(subtargname, filter_script)
601
602 else:
603 # Remove any existing file
604 if os.path.isfile(targname):
605 os.remove(targname)
606 elif os.path.isdir(targname):
607 shutil.rmtree(targname)
608
609 if os.path.isfile(toolname):
610 shutil.copy(toolname, targname)
611 else:
612 shutil.copytree(toolname, targname)
613
614 for filter_script in filter_scripts:
615 # Apply filter script to all files in the target directory
616 tfilter(targname, filter_script)
617
618 optionlist.remove(option)
619
620 # Do an initial pass through all of the options and determine what is being
621 # installed, so that we know in advance which file formats are missing and
622 # need to be generated.
623
624 for option in optionlist[:]:
625 if option[0] == 'lef':
626 have_lef = True
627 if option[0] == 'techlef' or option[0] == 'techLEF':
628 have_techlef = True
629 elif option[0] == 'gds':
630 have_gds = True
631 elif option[0] == 'spice' or option[0] == 'spi':
632 have_spice = True
633 elif option[0] == 'cdl':
634 have_cdl = True
635 elif option[0] == 'verilog':
636 have_verilog = True
637 elif option[0] == 'lib' or option[0] == 'liberty':
638 have_lib = True
639
640 # The remaining options in optionlist should all be types like 'lef' or 'liberty'
641 # and there should be a corresponding library list specified by '-library'
642
643 for option in optionlist[:]:
644
645 # Ignore if no library list---should have been taken care of above.
646 if libraries == []:
647 break
648
649 # Diagnostic
650 print("Install option: " + str(option[0]))
651
652 # For ef_format: always make techlef -> techLEF and spice -> spi
653
654 if ef_format:
655 if option[0] == 'techlef':
656 option[0] = 'techLEF'
657 elif option[0] == 'spice':
658 option[0] = 'spi'
659
660 destdir = targetdir + '/libs.ref/' + option[0]
661 os.makedirs(destdir, exist_ok=True)
662
663 # If the option is followed by the keyword "up" and a number, then
664 # the source should be copied (or linked) from <number> levels up
665 # in the hierarchy (see below).
666
667 if 'up' in option:
668 uparg = option.index('up')
669 try:
670 hier_up = int(option[uparg + 1])
671 except:
672 print("Non-numeric option to 'up': " + option[uparg + 1])
673 print("Ignoring 'up' option.")
674 hier_up = 0
675 else:
676 hier_up = 0
677
678 filter_scripts = []
679 for item in option:
680 if item.split('=')[0] == 'filter':
681 filter_scripts.append(item.split('=')[1])
682 break
683
684 # Option 'stub' applies to netlists ('cdl' or 'spice') and generates
685 # a file with only stub entries.
686 do_stub = 'stub' in option
687
688 # Option 'compile' is a standalone keyword ('comp' may be used).
689 do_compile = 'compile' in option or 'comp' in option
690 do_compile_only = 'compile-only' in option or 'comp-only' in option
691
692 # Option 'nospecify' is a standalone keyword ('nospec' may be used).
693 do_remove_spec = 'nospecify' in option or 'nospec' in option
694
695 # Option 'exclude' has an argument
696 try:
697 excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
698 except IndexError:
699 excludelist = []
700 else:
701 print('Excluding files: ' + (',').join(excludelist))
702
703 # Option 'rename' has an argument
704 try:
705 newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
706 except IndexError:
707 newname = None
708 else:
709 print('Renaming file to: ' + newname)
710
711 # 'anno' may be specified for LEF, in which case the LEF is used only
712 # to annotate GDS and is not itself installed; this allows LEF to
713 # be generated from Magic and avoids quirky use of obstruction layers.
714 have_lefanno = True if 'annotate' in option or 'anno' in option else False
715 if have_lefanno:
716 if option[0] != 'lef':
717 print("Warning: 'annotate' option specified outside of -lef. Ignoring.")
718 else:
719 # Mark as NOT having LEF since we want to use it only for annotation.
720 have_lef = False
721
722 # For each library, create the library subdirectory
723 for library in libraries:
724 if len(library) == 3:
725 destlib = library[2]
726 else:
727 destlib = library[1]
728
729 if ef_format:
730 destlibdir = destdir + '/' + destlib
731 else:
732 destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
733 destlibdir = destdir
734
735 os.makedirs(destlibdir, exist_ok=True)
736
737 # Populate the library subdirectory
738 # Parse the option and replace each '/*/' with the library name,
739 # and check if it is a valid directory name. Then glob the
740 # resulting option name. Warning: This assumes that all
741 # occurences of the text '/*/' match a library name. It should
742 # be possible to wild-card the directory name in such a way that
743 # this is always true.
744
745 testpath = substitute(sourcedir + '/' + option[1], library[1])
746 liblist = glob.glob(testpath)
747
748 # Create a file "sources.txt" (or append to it if it exists)
749 # and add the source directory name so that the staging install
750 # script can know where the files came from.
751
752 with open(destlibdir + '/sources.txt', 'a') as ofile:
753 print(testpath, file=ofile)
754
755 # Create exclude list with glob-style matching using fnmatch
756 if len(liblist) > 0:
757 liblistnames = list(os.path.split(item)[1] for item in liblist)
758 notliblist = []
759 for exclude in excludelist:
760 notliblist.extend(fnmatch.filter(liblistnames, exclude))
761
762 # Apply exclude list
763 if len(notliblist) > 0:
764 for file in liblist[:]:
765 if os.path.split(file)[1] in notliblist:
766 liblist.remove(file)
767
768 if len(excludelist) > 0 and len(notliblist) == 0:
769 print('Warning: Nothing from the exclude list found in sources.')
770 print('excludelist = ' + str(excludelist))
771 print('destlibdir = ' + destlibdir)
772
773 # Diagnostic
774 print('Collecting files from ' + testpath)
775 print('Files to install:')
776 if len(liblist) < 10:
777 for item in liblist:
778 print(' ' + item)
779 else:
780 for item in liblist[0:4]:
781 print(' ' + item)
782 print(' .')
783 print(' .')
784 print(' .')
785 for item in liblist[-6:-1]:
786 print(' ' + item)
787 print('(' + str(len(liblist)) + ' files total)')
788
789 for libname in liblist:
790 # Note that there may be a hierarchy to the files in option[1],
791 # say for liberty timing files under different conditions, so
792 # make sure directories have been created as needed.
793
794 libfile = os.path.split(libname)[1]
795 libfilepath = os.path.split(libname)[0]
796 destpathcomp = []
797 for i in range(hier_up):
798 destpathcomp.append('/' + os.path.split(libfilepath)[1])
799 libfilepath = os.path.split(libfilepath)[0]
800 destpathcomp.reverse()
801 destpath = ''.join(destpathcomp)
802
803 if newname:
804 if len(liblist) == 1:
805 destfile = newname
806 else:
807 if not do_compile and not do_compile_only:
808 print('Error: rename specified but more than one file found!')
809 destfile = libfile
810 else:
811 destfile = libfile
812
813 targname = destlibdir + destpath + '/' + destfile
814
815 # NOTE: When using "up" with link_from, could just make
816 # destpath itself a symbolic link; this way is more flexible
817 # but adds one symbolic link per file.
818
819 if destpath != '':
820 if not os.path.isdir(destlibdir + destpath):
821 os.makedirs(destlibdir + destpath, exist_ok=True)
822
823 # Remove any existing file
824 if os.path.isfile(targname):
825 os.remove(targname)
826 elif os.path.isdir(targname):
827 shutil.rmtree(targname)
828
829 # NOTE: Diagnostic, probably much too much output.
830 print(' Install:' + libname + ' to ' + targname)
831 if os.path.isfile(libname):
832 shutil.copy(libname, targname)
833 else:
834 shutil.copytree(libname, targname)
835
836 # File filtering options: Two options 'stub' and 'nospec' are
837 # handled by scripts in ../common/. Custom filters can also be
838 # specified.
839
840 local_filter_scripts = filter_scripts[:]
841
842 if option[0] == 'verilog':
843 # Internally handle syntactical issues with verilog and iverilog
844 vfilter(targname)
845
846 if do_remove_spec:
847 scriptdir = os.path.split(os.getcwd())[0] + '/common'
848 local_filter_scripts.append(scriptdir + '/remove_specify.py')
849
850 elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
851 if do_stub:
852 scriptdir = os.path.split(os.getcwd())[0] + '/common'
853 local_filter_scripts.append(scriptdir + '/makestub.py')
854
855 for filter_script in local_filter_scripts:
856 # Apply filter script to all files in the target directory
857 tfilter(targname, filter_script)
858
859 if do_compile == True or do_compile_only == True:
860 # NOTE: The purpose of "rename" is to put a destlib-named
861 # library elsewhere so that it can be merged with another
862 # library into a compiled <destlib>.<ext>
863
864 compname = destlib
865
866 # To do: Make this compatible with linking from another PDK.
867
868 if option[0] == 'verilog':
869 # If there is not a single file with all verilog cells in it,
870 # then compile one, because one does not want to have to have
871 # an include line for every single cell used in a design.
872
873 create_verilog_library(destlibdir, compname, do_compile_only, do_stub, excludelist)
874
Tim Edwards9be4ac22020-07-26 12:59:30 -0400875 if do_compile_only == True:
876 if newname:
877 if os.path.isfile(newname):
878 os.remove(newname)
879
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400880 elif option[0] == 'gds' and have_mag_8_2:
881 # If there is not a single file with all GDS cells in it,
882 # then compile one.
883
884 # Link to the PDK magic startup file from the target directory
885 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
886 if not os.path.isfile(startup_script):
887 startup_script = targetdir + mag_current + pdkname + '.magicrc'
888 create_gds_library(destlibdir, compname, startup_script, do_compile_only, excludelist)
889
Tim Edwards9be4ac22020-07-26 12:59:30 -0400890 if do_compile_only == True:
891 if newname:
892 if os.path.isfile(newname):
893 os.remove(newname)
894
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400895 elif option[0] == 'liberty' or option[0] == 'lib':
896 # If there is not a single file with all liberty cells in it,
897 # then compile one, because one does not want to have to have
898 # an include line for every single cell used in a design.
899
900 create_lib_library(destlibdir, compname, do_compile_only, excludelist)
901
Tim Edwards9be4ac22020-07-26 12:59:30 -0400902 if do_compile_only == True:
903 if newname:
904 if os.path.isfile(newname):
905 os.remove(newname)
906
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400907 elif option[0] == 'spice' or option[0] == 'spi':
908 # If there is not a single file with all SPICE subcircuits in it,
909 # then compile one, because one does not want to have to have
910 # an include line for every single cell used in a design.
911
912 spiext = '.spice' if not ef_format else '.spi'
913 create_spice_library(destlibdir, compname, spiext, do_compile_only, do_stub, excludelist)
914 if do_compile_only == True:
915 if newname:
916 if os.path.isfile(newname):
917 os.remove(newname)
918
919 elif option[0] == 'cdl':
920 # If there is not a single file with all CDL subcircuits in it,
921 # then compile one, because one does not want to have to have
922 # an include line for every single cell used in a design.
923
924 create_spice_library(destlibdir, compname, '.cdl', do_compile_only, do_stub, excludelist)
925 if do_compile_only == True:
926 if newname:
927 if os.path.isfile(newname):
928 os.remove(newname)
929
930 elif option[0] == 'lef':
931 # If there is not a single file with all LEF cells in it,
932 # then compile one, because one does not want to have to have
933 # an include line for every single cell used in a design.
934
935 create_lef_library(destlibdir, compname, do_compile_only, excludelist)
936
Tim Edwards9be4ac22020-07-26 12:59:30 -0400937 if do_compile_only == True:
938 if newname:
939 if os.path.isfile(newname):
940 os.remove(newname)
941
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400942 # Find any libraries/options marked as "privileged" (or "private") and
943 # move the files from libs.tech or libs.ref to libs.priv, leaving a
944 # symbolic link in the original location. Do this during the initial
945 # install so that options following in the list can add files to the
946 # non-privileged equivalent directory path.
947
948 if 'priv' in option or 'privileged' in option or 'private' in option:
949
950 # Diagnostic
951 print("Install option: " + str(option[0]))
952
953 if ef_format == True:
954 os.makedirs(targetdir + '/libs.priv', exist_ok=True)
955
956 for library in libraries:
957 if len(library) == 3:
958 destlib = library[2]
959 else:
960 destlib = library[1]
961
962 if ef_format:
963 srclibdir = targetdir + '/libs.ref/' + option[0] + '/' + destlib
964 destlibdir = targetdir + '/libs.priv/' + option[0] + '/' + destlib
965 else:
966 srclibdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
967 destlibdir = targetdir + '/libs.priv/' + destlib + '/' + option[0]
968
969 if not os.path.exists(destlibdir):
970 os.makedirs(destlibdir)
971
972 print('Moving files in ' + srclibdir + ' to privileged space.')
973 filelist = os.listdir(srclibdir)
974 for file in filelist:
975 srcfile = srclibdir + '/' + file
976 destfile = destlibdir + '/' + file
977 if os.path.isfile(destfile):
978 os.remove(destfile)
979 elif os.path.isdir(destfile):
980 shutil.rmtree(destfile)
981
982 if os.path.isfile(srcfile):
983 shutil.copy(srcfile, destfile)
984 os.remove(srcfile)
985 else:
986 shutil.copytree(srcfile, destfile)
987 shutil.rmtree(srcfile)
988
989 print("Completed installation of vendor files.")
990
991 #----------------------------------------------------------------
992 # Installation part 2: Generate derived file formats
993 #----------------------------------------------------------------
994
995 # Now for the harder part. If GDS and/or LEF databases were specified,
996 # then migrate them to magic (.mag files in layout/ or abstract/).
997
998 ignorelist = []
999 do_cdl_scaleu = False
1000 no_cdl_convert = False
1001 no_gds_convert = False
1002 no_lef_convert = False
1003 cdl_compile_only = False
1004
1005 cdl_exclude = []
1006 lef_exclude = []
1007 gds_exclude = []
1008 spice_exclude = []
1009 verilog_exclude = []
1010
1011 cdl_reflib = '/libs.ref/'
1012 gds_reflib = '/libs.ref/'
1013 lef_reflib = '/libs.ref/'
1014
1015 for option in optionlist[:]:
1016 if option[0] == 'cdl':
1017 # Option 'scaleu' is a standalone keyword
1018 do_cdl_scaleu = 'scaleu' in option
1019
1020 # Option 'ignore' has arguments after '='
1021 for item in option:
1022 if item.split('=')[0] == 'ignore':
1023 ignorelist = item.split('=')[1].split(',')
1024
1025 # Option 'noconvert' is a standalone keyword.
1026 if 'noconvert' in option:
1027 if option[0] == 'cdl':
1028 no_cdl_convert = True
1029 elif option[0] == 'gds':
1030 no_gds_convert = True
1031 elif option[0] == 'lef':
1032 no_lef_convert = True
1033
1034 # Option 'privileged' is a standalone keyword.
1035 if 'priv' in option or 'privileged' in option or 'private' in option:
1036 if option[0] == 'cdl':
1037 cdl_reflib = '/libs.priv/'
1038 elif option[0] == 'gds':
1039 gds_reflib = '/libs.priv/'
1040 elif option[0] == 'lef':
1041 lef_reflib = '/libs.priv/'
1042
1043 # If CDL is marked 'compile-only' then CDL should only convert the
1044 # compiled file to SPICE if conversion is needed.
1045 if 'compile-only' in option:
1046 if option[0] == 'cdl':
1047 cdl_compile_only = True
1048
1049 # Find exclude list for any option
1050 for item in option:
1051 if item.split('=')[0] == 'exclude':
1052 exclude_list = item.split('=')[1].split(',')
1053 if option[0] == 'cdl':
1054 cdl_exclude = exclude_list
1055 elif option[0] == 'lef':
1056 lef_exclude = exclude_list
1057 elif option[0] == 'gds':
1058 gds_exclude = exclude_list
1059 elif option[0] == 'spi' or option[0] == 'spice':
1060 spice_exclude = exclude_list
1061 elif option[0] == 'verilog':
1062 verilog_exclude = exclude_list
1063
1064 devlist = []
1065 pdklibrary = None
1066
1067 if have_gds and not no_gds_convert:
1068 print("Migrating GDS files to layout.")
1069
1070 if ef_format:
1071 destdir = targetdir + gds_reflib + 'mag'
1072 srcdir = targetdir + gds_reflib + 'gds'
1073 vdir = targetdir + '/libs.ref/' + 'verilog'
1074 cdir = targetdir + cdl_reflib + 'cdl'
1075 sdir = targetdir + cdl_reflib + 'spi'
1076
1077 os.makedirs(destdir, exist_ok=True)
1078
1079 # For each library, create the library subdirectory
1080 for library in libraries:
1081 if len(library) == 3:
1082 destlib = library[2]
1083 else:
1084 destlib = library[1]
1085
1086 if ef_format:
1087 destlibdir = destdir + '/' + destlib
1088 srclibdir = srcdir + '/' + destlib
1089 vlibdir = vdir + '/' + destlib
1090 clibdir = cdir + '/' + destlib
1091 slibdir = sdir + '/' + destlib
1092 else:
1093 destdir = targetdir + gds_reflib + destlib + '/mag'
1094 srcdir = targetdir + gds_reflib + destlib + '/gds'
1095 vdir = targetdir + '/libs.ref/' + destlib + '/verilog'
1096 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1097 sdir = targetdir + cdl_reflib + destlib + '/spice'
1098 destlibdir = destdir
1099 srclibdir = srcdir
1100 vlibdir = vdir
1101 clibdir = cdir
1102 slibdir = sdir
1103
1104 os.makedirs(destlibdir, exist_ok=True)
1105
1106 # For primitive devices, check the PDK script and find the name
1107 # of the library and get a list of supported devices.
1108
1109 if library[0] == 'primitive':
1110 pdkscript = targetdir + mag_current + pdkname + '.tcl'
1111 print('Searching for supported devices in PDK script ' + pdkscript + '.')
1112
1113 if os.path.isfile(pdkscript):
1114 librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
1115 devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
1116 fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
1117 devlist = []
1118 fixedlist = []
1119 with open(pdkscript, 'r') as ifile:
1120 scripttext = ifile.read().splitlines()
1121 for line in scripttext:
1122 lmatch = librex.match(line)
1123 if lmatch:
1124 pdklibrary = lmatch.group(1)
1125 dmatch = devrex.match(line)
1126 if dmatch:
1127 if dmatch.group(1) == pdklibrary:
1128 devlist.append(dmatch.group(2))
1129 fmatch = fixrex.match(line)
1130 if fmatch:
1131 if fmatch.group(1) == pdklibrary:
1132 fixedlist.append(fmatch.group(2))
1133
1134 # Diagnostic
1135 print("PDK library is " + str(pdklibrary))
1136
1137 # Link to the PDK magic startup file from the target directory
1138 # If there is no -F version then look for one without -F (open source PDK)
1139 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1140 if not os.path.isfile(startup_script):
1141 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1142
1143 if have_mag_8_2 and os.path.isfile(startup_script):
1144 # If the symbolic link exists, remove it.
1145 if os.path.isfile(destlibdir + '/.magicrc'):
1146 os.remove(destlibdir + '/.magicrc')
1147 os.symlink(startup_script, destlibdir + '/.magicrc')
1148
1149 # Find GDS file names in the source
1150 print('Getting GDS file list from ' + srclibdir + '.')
1151 gdsfilesraw = os.listdir(srclibdir)
1152 gdsfiles = []
1153 for gdsfile in gdsfilesraw:
1154 gdsext = os.path.splitext(gdsfile)[1].lower()
1155 if gdsext == '.gds' or gdsext == '.gdsii' or gdsext == '.gds2':
1156 gdsfiles.append(gdsfile)
1157
1158 # Create exclude list with glob-style matching using fnmatch
1159 if len(gdsfiles) > 0:
1160 gdsnames = list(os.path.split(item)[1] for item in gdsfiles)
1161 notgdsnames = []
1162 for exclude in gds_exclude:
1163 notgdsnames.extend(fnmatch.filter(gdsnames, exclude))
1164
1165 # Apply exclude list
1166 if len(notgdsnames) > 0:
1167 for file in gdsfiles[:]:
1168 if os.path.split(file)[1] in notgdsnames:
1169 gdsfiles.remove(file)
1170
1171 # Generate a script called "generate_magic.tcl" and leave it in
1172 # the target directory. Use it as input to magic to create the
1173 # .mag files from the database.
1174
1175 print('Creating magic generation script to generate magic database files.')
1176
1177 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1178 print('#!/usr/bin/env wish', file=ofile)
1179 print('#--------------------------------------------', file=ofile)
1180 print('# Script to generate .mag files from .gds ', file=ofile)
1181 print('#--------------------------------------------', file=ofile)
1182 print('gds readonly true', file=ofile)
1183 print('gds flatten true', file=ofile)
1184 print('gds rescale false', file=ofile)
1185 print('tech unlock *', file=ofile)
1186
1187 for gdsfile in gdsfiles:
1188 # Note: DO NOT use a relative path here.
1189 print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
1190
1191 # Make sure properties include the Tcl generated cell
1192 # information from the PDK script
1193
1194 if pdklibrary:
1195 tclfixedlist = '{' + ' '.join(fixedlist) + '}'
1196 print('set devlist ' + tclfixedlist, file=ofile)
1197 print('set topcell [lindex [cellname list top] 0]',
1198 file=ofile)
1199
1200 print('foreach cellname $devlist {', file=ofile)
1201 print(' load $cellname', file=ofile)
1202 print(' property gencell $cellname', file=ofile)
1203 print(' property parameter m=1', file=ofile)
1204 print(' property library ' + pdklibrary, file=ofile)
1205 print('}', file=ofile)
1206 print('load $topcell', file=ofile)
1207
1208 print('cellname delete \(UNNAMED\)', file=ofile)
1209 print('writeall force', file=ofile)
1210
1211 leffiles = []
1212 lefmacros = []
1213 if have_lefanno:
1214 # Find LEF file names in the source
1215 if ef_format:
1216 lefsrcdir = targetdir + lef_reflib + 'lefanno'
1217 lefsrclibdir = lefsrcdir + '/' + destlib
1218 else:
1219 lefsrcdir = targetdir + lef_reflib + destlib + '/lefanno'
1220 lefsrclibdir = lefsrcdir
1221
1222 leffiles = os.listdir(lefsrclibdir)
1223 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
1224 # Get list of abstract views to make from LEF macros
1225 for leffile in leffiles:
1226 with open(leffile, 'r') as ifile:
1227 ltext = ifile.read()
1228 llines = ltext.splitlines()
1229 for lline in llines:
1230 ltok = re.split(' |\t|\(', lline)
1231 if ltok[0] == 'MACRO':
1232 lefmacros.append(ltok[1])
1233
1234 # Create exclude list with glob-style matching using fnmatch
1235 if len(lefmacros) > 0:
1236 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1237 notlefnames = []
1238 for exclude in lef_exclude:
1239 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1240
1241 # Apply exclude list
1242 if len(notlefnames) > 0:
1243 for file in lefmacros[:]:
1244 if os.path.split(file)[1] in notlefnames:
1245 lefmacros.remove(file)
1246
1247 elif have_verilog and os.path.isdir(vlibdir):
1248 # Get list of abstract views to make from verilog modules
1249 vfiles = os.listdir(vlibdir)
1250 vfiles = list(item for item in vfiles if os.path.splitext(item)[1] == '.v')
1251 for vfile in vfiles:
1252 with open(vlibdir + '/' + vfile, 'r') as ifile:
1253 vtext = ifile.read()
1254 vlines = vtext.splitlines()
1255 for vline in vlines:
1256 vtok = re.split(' |\t|\(', vline)
1257 try:
1258 if vtok[0] == 'module':
1259 if vtok[1] not in lefmacros:
1260 lefmacros.append(vtok[1])
1261 except:
1262 pass
1263
1264 # Create exclude list with glob-style matching using fnmatch
1265 if len(lefmacros) > 0:
1266 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1267 notlefnames = []
1268 for exclude in verilog_exclude:
1269 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1270
1271 # Apply exclude list
1272 if len(notlefnames) > 0:
1273 for file in lefmacros[:]:
1274 if os.path.split(file)[1] in notlefnames:
1275 lefmacros.remove(file)
1276
1277 elif have_cdl and os.path.isdir(clibdir):
1278 # Get list of abstract views to make from CDL subcircuits
1279 cfiles = os.listdir(clibdir)
1280 cfiles = list(item for item in cfiles if os.path.splitext(item)[1] == '.cdl')
1281 for cfile in cfiles:
1282 with open(clibdir + '/' + cfile, 'r') as ifile:
1283 ctext = ifile.read()
1284 clines = ctext.splitlines()
1285 for cline in clines:
1286 ctok = cline.split()
1287 try:
1288 if ctok[0].lower() == '.subckt':
1289 if ctok[1] not in lefmacros:
1290 lefmacros.append(ctok[1])
1291 except:
1292 pass
1293
1294 # Create exclude list with glob-style matching using fnmatch
1295 if len(lefmacros) > 0:
1296 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1297 notlefnames = []
1298 for exclude in cdl_exclude:
1299 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1300
1301 # Apply exclude list
1302 if len(notlefnames) > 0:
1303 for file in lefmacros[:]:
1304 if os.path.split(file)[1] in notlefnames:
1305 lefmacros.remove(file)
1306
1307 elif have_spice and os.path.isdir(slibdir):
1308 # Get list of abstract views to make from SPICE subcircuits
1309 sfiles = os.listdir(slibdir)
1310 sfiles = list(item for item in sfiles)
1311 for sfile in sfiles:
1312 with open(slibdir + '/' + sfile, 'r') as ifile:
1313 stext = ifile.read()
1314 slines = stext.splitlines()
1315 for sline in slines:
1316 stok = sline.split()
1317 try:
1318 if stok[0].lower() == '.subckt':
1319 if stok[1] not in lefmacros:
1320 lefmacros.append(stok[1])
1321 except:
1322 pass
1323
1324 # Create exclude list with glob-style matching using fnmatch
1325 if len(lefmacros) > 0:
1326 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1327 notlefnames = []
1328 for exclude in spice_exclude:
1329 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1330
1331 # Apply exclude list
1332 if len(notlefnames) > 0:
1333 for file in lefmacros[:]:
1334 if os.path.split(file)[1] in notlefnames:
1335 lefmacros.remove(file)
1336
1337 if not lefmacros:
1338 print('No source for abstract views: Abstract views not made.')
1339 elif not have_lef:
1340 # This library has a GDS database but no LEF database. Use
1341 # magic to create abstract views of the GDS cells. If
1342 # option "annotate" is given, then read the LEF file after
1343 # loading the database file to annotate the cell with
1344 # information from the LEF file. This usually indicates
1345 # that the LEF file has some weird definition of obstruction
1346 # layers and we want to normalize them by using magic's LEF
1347 # write procedure, but we still need the pin use and class
1348 # information from the LEF file, and maybe the bounding box.
1349
1350 for leffile in leffiles:
1351 if have_lefanno:
1352 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1353 for lefmacro in lefmacros:
1354 print('if {[cellname list exists ' + lefmacro + '] != 0} {', file=ofile)
1355 print(' load ' + lefmacro, file=ofile)
1356 print(' lef write ' + lefmacro + ' -hide', file=ofile)
1357 print('}', file=ofile)
1358 print('puts stdout "Done."', file=ofile)
1359 print('quit -noprompt', file=ofile)
1360
1361 print('Running magic to create magic database files.')
1362 sys.stdout.flush()
1363
1364 # Run magic to read in the GDS file and write out magic databases.
1365 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1366 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1367 stdin = ifile, stdout = subprocess.PIPE,
1368 stderr = subprocess.PIPE, cwd = destlibdir,
1369 universal_newlines = True)
1370 if mproc.stdout:
1371 for line in mproc.stdout.splitlines():
1372 print(line)
1373 if mproc.stderr:
1374 print('Error message output from magic:')
1375 for line in mproc.stderr.splitlines():
1376 print(line)
1377 if mproc.returncode != 0:
1378 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1379
1380 if not have_lef:
1381 print('No LEF file install; need to generate LEF.')
1382 # Remove the lefanno/ target and its contents.
1383 if have_lefanno:
1384 if ef_format:
1385 lefannosrcdir = targetdir + lef_reflib + 'lefanno'
1386 else:
1387 lefannosrcdir = targetdir + lef_reflib + destlib + '/lefanno'
1388 if os.path.isdir(lefannosrcdir):
1389 shutil.rmtree(lefannosrcdir)
1390
1391 if ef_format:
1392 destlefdir = targetdir + lef_reflib + 'lef'
1393 destleflibdir = destlefdir + '/' + destlib
1394 else:
1395 destlefdir = targetdir + lef_reflib + destlib + '/lef'
1396 destleflibdir = destlefdir
1397
1398 os.makedirs(destleflibdir, exist_ok=True)
1399 leflist = os.listdir(destlibdir)
1400 leflist = list(item for item in leflist if os.path.splitext(item)[1] == '.lef')
1401
1402 # All macros will go into one file
1403 destleflib = destleflibdir + '/' + destlib + '.lef'
1404 # Remove any existing library file from the target directory
1405 if os.path.isfile(destleflib):
1406 print('Removing existing library ' + destleflib)
1407 os.remove(destleflib)
1408
1409 first = True
1410 with open(destleflib, 'w') as ofile:
1411 for leffile in leflist:
1412 # Remove any existing single file from the target directory
1413 if os.path.isfile(destleflibdir + '/' + leffile):
1414 print('Removing ' + destleflibdir + '/' + leffile)
1415 os.remove(destleflibdir + '/' + leffile)
1416
1417 # Append contents
1418 sourcelef = destlibdir + '/' + leffile
1419 with open(sourcelef, 'r') as ifile:
1420 leflines = ifile.read().splitlines()
1421 if not first:
1422 # Remove header from all but the first file
1423 leflines = leflines[8:]
1424 else:
1425 first = False
1426
1427 for line in leflines:
1428 print(line, file=ofile)
1429
1430 # Remove file from the source directory
1431 print('Removing source file ' + sourcelef)
1432 os.remove(sourcelef)
1433
1434 # Set have_lef now that LEF files were made, so they
1435 # can be used to generate the maglef/ databases.
1436 have_lef = True
1437
1438 elif not have_mag_8_2:
1439 print('The installer is not able to run magic.')
1440 else:
1441 print("Master PDK magic startup file not found. Did you install")
1442 print("PDK tech files before PDK vendor files?")
1443
1444 if have_lef and not no_lef_convert:
1445 print("Migrating LEF files to layout.")
1446 if ef_format:
1447 destdir = targetdir + '/libs.ref/' + 'maglef'
1448 srcdir = targetdir + lef_reflib + 'lef'
1449 magdir = targetdir + gds_reflib + 'mag'
1450 cdldir = targetdir + cdl_reflib + 'cdl'
1451 os.makedirs(destdir, exist_ok=True)
1452
1453 # For each library, create the library subdirectory
1454 for library in libraries:
1455 if len(library) == 3:
1456 destlib = library[2]
1457 else:
1458 destlib = library[1]
1459
1460 if ef_format:
1461 destlibdir = destdir + '/' + destlib
1462 srclibdir = srcdir + '/' + destlib
1463 maglibdir = magdir + '/' + destlib
1464 cdllibdir = cdldir + '/' + destlib
1465 else:
1466 destdir = targetdir + '/libs.ref/' + destlib + '/maglef'
1467 srcdir = targetdir + lef_reflib + destlib + '/lef'
1468 magdir = targetdir + gds_reflib + destlib + '/mag'
1469 cdldir = targetdir + cdl_reflib + destlib + '/cdl'
1470
1471 destlibdir = destdir
1472 srclibdir = srcdir
1473 maglibdir = magdir
1474 cdllibdir = cdldir
1475
1476 os.makedirs(destlibdir, exist_ok=True)
1477
1478 # Link to the PDK magic startup file from the target directory
1479 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1480 if not os.path.isfile(startup_script):
1481 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1482
1483 if have_mag_8_2 and os.path.isfile(startup_script):
1484 # If the symbolic link exists, remove it.
1485 if os.path.isfile(destlibdir + '/.magicrc'):
1486 os.remove(destlibdir + '/.magicrc')
1487 os.symlink(startup_script, destlibdir + '/.magicrc')
1488
1489 # Find LEF file names in the source
1490 leffiles = os.listdir(srclibdir)
1491 leffiles = list(item for item in leffiles if os.path.splitext(item)[1].lower() == '.lef')
1492
1493 # Get list of abstract views to make from LEF macros
1494 lefmacros = []
1495 err_no_macros = False
1496 for leffile in leffiles:
1497 with open(srclibdir + '/' + leffile, 'r') as ifile:
1498 ltext = ifile.read()
1499 llines = ltext.splitlines()
1500 for lline in llines:
1501 ltok = re.split(' |\t|\(', lline)
1502 if ltok[0] == 'MACRO':
1503 lefmacros.append(ltok[1])
1504
1505 # Create exclude list with glob-style matching using fnmatch
1506 if len(lefmacros) > 0:
1507 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1508 notlefnames = []
1509 for exclude in lef_exclude:
1510 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1511
1512 # Apply exclude list
1513 if len(notlefnames) > 0:
1514 for file in lefmacros[:]:
1515 if os.path.split(file)[1] in notlefnames:
1516 lefmacros.remove(file)
1517
1518 if len(leffiles) == 0:
1519 print('Warning: No LEF files found in ' + srclibdir)
1520 continue
1521
1522 print('Generating conversion script to create magic databases from LEF')
1523
1524 # Generate a script called "generate_magic.tcl" and leave it in
1525 # the target directory. Use it as input to magic to create the
1526 # .mag files from the database.
1527
1528 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1529 print('#!/usr/bin/env wish', file=ofile)
1530 print('#--------------------------------------------', file=ofile)
1531 print('# Script to generate .mag files from .lef ', file=ofile)
1532 print('#--------------------------------------------', file=ofile)
1533 print('tech unlock *', file=ofile)
1534
1535 # If there are devices in the LEF file that come from the
1536 # PDK library, then copy this list into the script.
1537
1538 if pdklibrary:
1539 shortdevlist = []
1540 for macro in lefmacros:
1541 if macro in devlist:
1542 shortdevlist.append(macro)
1543
1544 tcldevlist = '{' + ' '.join(shortdevlist) + '}'
1545 print('set devlist ' + tcldevlist, file=ofile)
1546
1547 for leffile in leffiles:
1548 print('lef read ' + srclibdir + '/' + leffile, file=ofile)
1549
1550 for lefmacro in lefmacros:
1551
1552 # To be completed: Parse SPICE file for port order, make
1553 # sure ports are present and ordered.
1554
1555 if pdklibrary and lefmacro in shortdevlist:
1556 print('set cellname ' + lefmacro, file=ofile)
1557 print('if {[lsearch $devlist $cellname] >= 0} {',
1558 file=ofile)
1559 print(' load $cellname', file=ofile)
1560 print(' property gencell $cellname', file=ofile)
1561 print(' property parameter m=1', file=ofile)
1562 print(' property library ' + pdklibrary, file=ofile)
1563 print('}', file=ofile)
1564
1565 # Load one of the LEF files so that the default (UNNAMED) cell
1566 # is not loaded, then delete (UNNAMED) so it doesn't generate
1567 # an error message.
1568 if len(lefmacros) > 0:
1569 print('load ' + lefmacros[0], file=ofile)
1570 print('cellname delete \(UNNAMED\)', file=ofile)
1571 else:
1572 err_no_macros = True
1573 print('writeall force', file=ofile)
1574 print('puts stdout "Done."', file=ofile)
1575 print('quit -noprompt', file=ofile)
1576
1577 if err_no_macros == True:
1578 print('Warning: No LEF macros were defined.')
1579
1580 print('Running magic to create magic databases from LEF')
1581 sys.stdout.flush()
1582
1583 # Run magic to read in the LEF file and write out magic databases.
1584 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1585 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1586 stdin = ifile, stdout = subprocess.PIPE,
1587 stderr = subprocess.PIPE, cwd = destlibdir,
1588 universal_newlines = True)
1589 if mproc.stdout:
1590 for line in mproc.stdout.splitlines():
1591 print(line)
1592 if mproc.stderr:
1593 print('Error message output from magic:')
1594 for line in mproc.stderr.splitlines():
1595 print(line)
1596 if mproc.returncode != 0:
1597 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1598
1599
1600 # Now list all the .mag files generated, and for each, read the
1601 # corresponding file from the mag/ directory, pull the GDS file
1602 # properties, and add those properties to the maglef view. Also
1603 # read the CDL (or SPICE) netlist, read the ports, and rewrite
1604 # the port order in the mag and maglef file accordingly.
1605
1606 # Diagnostic
1607 print('Annotating files in ' + destlibdir)
1608 sys.stdout.flush()
1609 magfiles = os.listdir(destlibdir)
1610 magfiles = list(item for item in magfiles if os.path.splitext(item)[1] == '.mag')
1611 for magroot in magfiles:
1612 magname = os.path.splitext(magroot)[0]
1613 magfile = maglibdir + '/' + magroot
1614 magleffile = destlibdir + '/' + magroot
1615 prop_lines = get_gds_properties(magfile)
1616
1617 # Make sure properties include the Tcl generated cell
1618 # information from the PDK script
1619
1620 prop_gencell = []
1621 if pdklibrary:
1622 if magname in fixedlist:
1623 prop_gencell.append('gencell ' + magname)
1624 prop_gencell.append('library ' + pdklibrary)
1625 prop_gencell.append('parameter m=1')
1626
1627 nprops = len(prop_lines) + len(prop_gencell)
1628
1629 cdlfile = cdllibdir + '/' + magname + '.cdl'
1630 if os.path.exists(cdlfile):
1631 cdlfiles = [cdlfile]
1632 else:
1633 # Assume there is at least one file with all cell subcircuits
1634 # in it.
1635 try:
1636 cdlfiles = glob.glob(cdllibdir + '/*.cdl')
1637 except:
1638 pass
1639 if len(cdlfiles) > 0:
1640 for cdlfile in cdlfiles:
1641 port_dict = get_subckt_ports(cdlfile, magname)
1642 if port_dict != {}:
1643 break
1644 else:
1645 port_dict = {}
1646
1647 if port_dict == {}:
1648 print('No CDL file contains ' + destlib + ' device ' + magname)
1649 cdlfile = None
1650 # To be done: If destlib is 'primitive', then look in
1651 # SPICE models for port order.
1652 if destlib == 'primitive':
1653 print('Fix me: Need to look in SPICE models!')
1654
1655 proprex = re.compile('<< properties >>')
1656 endrex = re.compile('<< end >>')
1657 rlabrex = re.compile('rlabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
1658 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]+)')
1659 portrex = re.compile('port[ \t]+([^ \t]+)[ \t]+(.*)')
1660 gcellrex = re.compile('string gencell')
1661 portnum = -1
1662
1663 with open(magleffile, 'r') as ifile:
1664 magtext = ifile.read().splitlines()
1665
1666 with open(magleffile, 'w') as ofile:
1667 has_props = False
1668 is_gencell = False
1669 for line in magtext:
1670 tmatch = portrex.match(line)
1671 if tmatch:
1672 if portnum >= 0:
1673 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1674 else:
1675 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1676 ematch = endrex.match(line)
1677 if ematch and nprops > 0:
1678 if not has_props:
1679 print('<< properties >>', file=ofile)
1680 if not is_gencell:
1681 for prop in prop_gencell:
1682 print('string ' + prop, file=ofile)
1683 for prop in prop_lines:
1684 print('string ' + prop, file=ofile)
1685
1686 print(line, file=ofile)
1687 pmatch = proprex.match(line)
1688 if pmatch:
1689 has_props = True
1690
1691 gmatch = gcellrex.match(line)
1692 if gmatch:
1693 is_gencell = True
1694
1695 lmatch = flabrex.match(line)
1696 if not lmatch:
1697 lmatch = rlabrex.match(line)
1698 if lmatch:
1699 labname = lmatch.group(1).lower()
1700 try:
1701 portnum = port_dict[labname]
1702 except:
1703 portnum = -1
1704
1705 if os.path.exists(magfile):
1706 with open(magfile, 'r') as ifile:
1707 magtext = ifile.read().splitlines()
1708
1709 with open(magfile, 'w') as ofile:
1710 for line in magtext:
1711 tmatch = portrex.match(line)
1712 if tmatch:
1713 if portnum >= 0:
1714 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1715 else:
1716 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1717 ematch = endrex.match(line)
1718 print(line, file=ofile)
1719 lmatch = flabrex.match(line)
1720 if not lmatch:
1721 lmatch = rlabrex.match(line)
1722 if lmatch:
1723 labname = lmatch.group(1).lower()
1724 try:
1725 portnum = port_dict[labname]
1726 except:
1727 portnum = -1
1728 elif os.path.splitext(magfile)[1] == '.mag':
1729 # NOTE: Possibly this means the GDS cell has a different name.
1730 print('Error: No file ' + magfile + '. Why is it in maglef???')
1731
1732 elif not have_mag_8_2:
1733 print('The installer is not able to run magic.')
1734 else:
1735 print("Master PDK magic startup file not found. Did you install")
1736 print("PDK tech files before PDK vendor files?")
1737
1738 # If SPICE or CDL databases were specified, then convert them to
1739 # a form that can be used by ngspice, using the cdl2spi.py script
1740
1741 if have_spice:
1742 if ef_format:
1743 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1744 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1745
1746 elif have_cdl and not no_cdl_convert:
1747 if ef_format:
1748 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1749 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1750
1751 print("Migrating CDL netlists to SPICE.")
1752 sys.stdout.flush()
1753
1754 if ef_format:
1755 destdir = targetdir + cdl_reflib + 'spi'
1756 srcdir = targetdir + cdl_reflib + 'cdl'
1757 os.makedirs(destdir, exist_ok=True)
1758
1759 # For each library, create the library subdirectory
1760 for library in libraries:
1761 if len(library) == 3:
1762 destlib = library[2]
1763 else:
1764 destlib = library[1]
1765
1766 if ef_format:
1767 destlibdir = destdir + '/' + destlib
1768 srclibdir = srcdir + '/' + destlib
1769 else:
1770 destdir = targetdir + cdl_reflib + destlib + '/spice'
1771 srcdir = targetdir + cdl_reflib + destlib + '/cdl'
1772
1773 destlibdir = destdir
1774 srclibdir = srcdir
1775
1776 os.makedirs(destlibdir, exist_ok=True)
1777
1778 # Find CDL file names in the source
1779 # If CDL is marked compile-only then ONLY convert <distdir>.cdl
1780 if cdl_compile_only:
1781 alllibname = destlibdir + '/' + destlib + '.cdl'
1782 if not os.path.exists(alllibname):
1783 cdl_compile_only = False
1784 else:
1785 cdlfiles = [alllibname]
1786
1787 if not cdl_compile_only:
1788 cdlfiles = os.listdir(srclibdir)
1789 cdlfiles = list(item for item in cdlfiles if os.path.splitext(item)[1].lower() == '.cdl')
1790
1791 # The directory with scripts should be in ../common with respect
1792 # to the Makefile that determines the cwd.
1793 scriptdir = os.path.split(os.getcwd())[0] + '/common'
1794
1795 # Run cdl2spi.py script to read in the CDL file and write out SPICE
1796 for cdlfile in cdlfiles:
1797 if ef_format:
1798 spiname = os.path.splitext(cdlfile)[0] + '.spi'
1799 else:
1800 spiname = os.path.splitext(cdlfile)[0] + '.spice'
1801 procopts = [scriptdir + '/cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
1802 if do_cdl_scaleu:
1803 procopts.append('-dscale=u')
1804 for item in ignorelist:
1805 procopts.append('-ignore=' + item)
1806
1807 print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
1808 pproc = subprocess.run(procopts,
1809 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
1810 stderr = subprocess.PIPE, cwd = destlibdir,
1811 universal_newlines = True)
1812 if pproc.stdout:
1813 for line in pproc.stdout.splitlines():
1814 print(line)
1815 if pproc.stderr:
1816 print('Error message output from cdl2spi.py:')
1817 for line in pproc.stderr.splitlines():
1818 print(line)
1819
1820 elif have_gds and not no_gds_convert:
1821 # If neither SPICE nor CDL formats is available in the source, then
1822 # read GDS; if the result has no ports, then read the corresponding
1823 # LEF library to get port information. Then write out a SPICE netlist
1824 # for the whole library. NOTE: If there is no CDL or SPICE source,
1825 # then the port numbering is arbitrary, and becomes whatever the
1826 # output of this script makes it.
1827
1828 if ef_format:
1829 destdir = targetdir + cdl_reflib + 'spi'
1830 srcdir = targetdir + gds_reflib + 'gds'
1831 lefdir = targetdir + lef_reflib + 'lef'
1832 os.makedirs(destdir, exist_ok=True)
1833
1834 # For each library, create the library subdirectory
1835 for library in libraries:
1836 if len(library) == 3:
1837 destlib = library[2]
1838 else:
1839 destlib = library[1]
1840
1841 if ef_format:
1842 destlibdir = destdir + '/' + destlib
1843 srclibdir = srcdir + '/' + destlib
1844 leflibdir = lefdir + '/' + destlib
1845 else:
1846 destdir = targetdir + cdl_reflib + destlib + '/spice'
1847 srcdir = targetdir + gds_reflib + destlib + '/gds'
1848 lefdir = targetdir + lef_reflib + destlib + '/lef'
1849
1850 destlibdir = destdir
1851 srclibdir = srcdir
1852 leflibdir = lefdir
1853
1854 os.makedirs(destlibdir, exist_ok=True)
1855
1856 # Link to the PDK magic startup file from the target directory
1857 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1858 if not os.path.isfile(startup_script):
1859 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1860 if os.path.isfile(startup_script):
1861 # If the symbolic link exists, remove it.
1862 if os.path.isfile(destlibdir + '/.magicrc'):
1863 os.remove(destlibdir + '/.magicrc')
1864 os.symlink(startup_script, destlibdir + '/.magicrc')
1865
1866 # Get the consolidated GDS library file, or a list of all GDS files
1867 # if there is no single consolidated library
1868
1869 allgdslibname = srclibdir + '/' + destlib + '.gds'
1870 if not os.path.isfile(allgdslibname):
1871 glist = glob.glob(srclibdir + '/*.gds')
1872 glist.extend(glob.glob(srclibdir + '/*.gdsii'))
1873 glist.extend(glob.glob(srclibdir + '/*.gds2'))
1874
1875 allleflibname = leflibdir + '/' + destlib + '.lef'
1876 if not os.path.isfile(allleflibname):
1877 llist = glob.glob(leflibdir + '/*.lef')
1878
1879 print('Creating magic generation script to generate SPICE library.')
1880 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1881 print('#!/usr/bin/env wish', file=ofile)
1882 print('#---------------------------------------------', file=ofile)
1883 print('# Script to generate SPICE library from GDS ', file=ofile)
1884 print('#---------------------------------------------', file=ofile)
1885 print('drc off', file=ofile)
1886 print('gds readonly true', file=ofile)
1887 print('gds flatten true', file=ofile)
1888 print('gds rescale false', file=ofile)
1889 print('tech unlock *', file=ofile)
1890
1891 if not os.path.isfile(allgdslibname):
1892 for gdsfile in glist:
1893 print('gds read ' + gdsfile, file=ofile)
1894 else:
1895 print('gds read ' + allgdslibname, file=ofile)
1896
1897 if not os.path.isfile(allleflibname):
1898 # Annotate the cells with information from the LEF files
1899 for leffile in llist:
1900 print('lef read ' + leffile, file=ofile)
1901 else:
1902 print('lef read ' + allleflibname, file=ofile)
1903
1904 # Load first file and remove the (UNNAMED) cell
1905 if not os.path.isfile(allgdslibname):
1906 print('load ' + os.path.splitext(glist[0])[0], file=ofile)
1907 else:
1908 gdslibroot = os.path.split(allgdslibname)[1]
1909 print('load ' + os.path.splitext(gdslibroot)[0], file=ofile)
1910 print('cellname delete \(UNNAMED\)', file=ofile)
1911
1912 print('ext2spice lvs', file=ofile)
1913
1914 # NOTE: Leaving "subcircuit top" as "auto" (default) can cause
1915 # cells like decap that have no I/O to be output without a subcircuit
1916 # wrapper. Also note that if this happens, it is an indication that
1917 # power supplies have not been labeled as ports, which is harder to
1918 # handle and should be fixed in the source.
1919 print('ext2spice subcircuit top on', file=ofile)
1920
1921 print('ext2spice cthresh 0.1', file=ofile)
1922
1923 if os.path.isfile(allgdslibname):
1924 print('select top cell', file=ofile)
1925 print('set glist [cellname list children]', file=ofile)
1926 print('foreach cell $glist {', file=ofile)
1927 else:
1928 print('foreach cell [cellname list top] {', file=ofile)
1929
1930 print(' load $cell', file=ofile)
1931 print(' puts stdout "Extracting cell $cell"', file=ofile)
1932 print(' extract all', file=ofile)
1933 print(' ext2spice', file=ofile)
1934 print('}', file=ofile)
1935 print('puts stdout "Done."', file=ofile)
1936 print('quit -noprompt', file=ofile)
1937
1938 # Run magic to read in the individual GDS files and
1939 # write out the consolidated GDS library
1940
1941 print('Running magic to create GDS library.')
1942 sys.stdout.flush()
1943
1944 mproc = subprocess.run(['magic', '-dnull', '-noconsole',
1945 destlibdir + '/generate_magic.tcl'],
1946 stdin = subprocess.DEVNULL,
1947 stdout = subprocess.PIPE,
1948 stderr = subprocess.PIPE, cwd = destlibdir,
1949 universal_newlines = True)
1950 if mproc.stdout:
1951 for line in mproc.stdout.splitlines():
1952 print(line)
1953 if mproc.stderr:
1954 print('Error message output from magic:')
1955 for line in mproc.stderr.splitlines():
1956 print(line)
1957 if mproc.returncode != 0:
1958 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1959
1960 # Remove intermediate extraction files
1961 extfiles = glob.glob(destlibdir + '/*.ext')
1962 for extfile in extfiles:
1963 os.remove(extfile)
1964
1965 # If the GDS file was a consolidated file of all cells, then
1966 # create a similar SPICE library of all cells.
1967
1968 if os.path.isfile(allgdslibname):
1969 spiext = '.spice' if not ef_format else '.spi'
1970 create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist)
1971
1972 sys.exit(0)