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