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