blob: fcb8970a826c2237a3097bef1fb4e9c8aef97442 [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
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400323def tfilter(targetroot, filterscript, ef_format=False, outfile=[]):
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400324 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
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400334 if ef_format:
335 arguments = [filterscript, targetroot, outfile, '-ef_format']
336 else:
337 arguments = [filterscript, targetroot, outfile]
338
339 fproc = subprocess.run(arguments,
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400340 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
341 stderr = subprocess.PIPE, universal_newlines = True)
342 if fproc.stdout:
343 for line in fproc.stdout.splitlines():
344 print(line)
345 if fproc.stderr:
346 print('Error message output from filter script:')
347 for line in fproc.stderr.splitlines():
348 print(line)
349
350 else:
351 tlist = glob.glob(targetroot + '/*')
352 for tfile in tlist:
353 if os.path.isfile(tfile):
354 print(' Filtering file ' + tfile + ' with ' + filterroot)
355 sys.stdout.flush()
356 fproc = subprocess.run([filterscript, tfile, tfile],
357 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
358 stderr = subprocess.PIPE, universal_newlines = True)
359 if fproc.stdout:
360 for line in fproc.stdout.splitlines():
361 print(line)
362 if fproc.stderr:
363 print('Error message output from filter script:')
364 for line in fproc.stderr.splitlines():
365 print(line)
366
367#----------------------------------------------------------------------------
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400368# This is the main entry point for the foundry install script.
369#----------------------------------------------------------------------------
370
371if __name__ == '__main__':
372
373 if len(sys.argv) == 1:
374 print("No options given to foundry_install.py.")
375 usage()
376 sys.exit(0)
377
378 optionlist = []
379 newopt = []
380
381 sourcedir = None
382 targetdir = None
383
384 ef_format = False
385 do_clean = False
386
387 have_lef = False
388 have_techlef = False
389 have_lefanno = False
390 have_gds = False
391 have_spice = False
392 have_cdl = False
393 have_verilog = False
394 have_lib = False
395
396 # Break arguments into groups where the first word begins with "-".
397 # All following words not beginning with "-" are appended to the
398 # same list (optionlist). Then each optionlist is processed.
399 # Note that the first entry in optionlist has the '-' removed.
400
401 for option in sys.argv[1:]:
402 if option.find('-', 0) == 0:
403 if newopt != []:
404 optionlist.append(newopt)
405 newopt = []
406 newopt.append(option[1:])
407 else:
408 newopt.append(option)
409
410 if newopt != []:
411 optionlist.append(newopt)
412
413 # Pull library names from optionlist
414 libraries = []
415 for option in optionlist[:]:
416 if option[0] == 'library':
417 optionlist.remove(option)
418 libraries.append(option[1:])
419
420 # Check for option "ef_format" or "std_format" or "clean"
421 for option in optionlist[:]:
422 if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
423 optionlist.remove(option)
424 ef_format = True
425 elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
426 optionlist.remove(option)
427 ef_format = False
428 elif option[0] == 'clean':
429 do_clean = True
430
431 # Check for options "source" and "target"
432 for option in optionlist[:]:
433 if option[0] == 'source':
434 optionlist.remove(option)
Tim Edwards855295e2021-09-08 10:57:54 -0400435 if len(option) > 1:
436 sourcedir = option[1]
437 else:
438 print('Error: Option "source" used with no value.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400439 elif option[0] == 'target':
440 optionlist.remove(option)
Tim Edwards855295e2021-09-08 10:57:54 -0400441 if len(option) > 1:
442 targetdir = option[1]
443 else:
444 print('Error: Option "target" used with no value.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400445
446 if not targetdir:
447 print("No target directory specified. Exiting.")
448 sys.exit(1)
449
450 # Take the target PDK name from the target path last component
451 pdkname = os.path.split(targetdir)[1]
452
453 # If targetdir (the staging area) exists, make sure it's empty.
454
455 if os.path.isdir(targetdir):
456 # Error if targetdir exists but is not writeable
457 if not os.access(targetdir, os.W_OK):
458 print("Target installation directory " + targetdir + " is not writable.")
459 sys.exit(1)
460
461 # Clear out the staging directory if specified
462 if do_clean:
463 shutil.rmtree(targetdir)
464 elif os.path.exists(targetdir):
465 print("Target installation directory " + targetdir + " is not a directory.")
466 sys.exit(1)
467
468 # Error if no source or dest specified unless "-clean" was specified
469 if not sourcedir:
470 if do_clean:
471 print("Done removing staging area.")
472 sys.exit(0)
473 else:
474 print("No source directory specified. Exiting.")
475 sys.exit(1)
476
477 # Create the target directory
478 os.makedirs(targetdir, exist_ok=True)
479
Tim Edwards5c7924d2020-09-30 22:22:10 -0400480 # Here's where common scripts are found:
481 scriptdir = os.path.split(os.getcwd())[0] + '/common'
482
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400483 #----------------------------------------------------------------
484 # Installation part 1: Install files into the staging directory
485 #----------------------------------------------------------------
486
487 # Diagnostic
488 print("Installing in target (staging) directory " + targetdir)
489
490 # Create the top-level directories
491
492 os.makedirs(targetdir + '/libs.tech', exist_ok=True)
493 os.makedirs(targetdir + '/libs.ref', exist_ok=True)
494
495 # Path to magic techfile depends on ef_format
496
497 if ef_format == True:
498 mag_current = '/libs.tech/magic/current/'
499 else:
500 mag_current = '/libs.tech/magic/'
501
502 # Check for magic version and set flag if it does not exist or if
503 # it has the wrong version.
504 have_mag_8_2 = False
505 try:
506 mproc = subprocess.run(['magic', '--version'],
507 stdout = subprocess.PIPE,
508 stderr = subprocess.PIPE,
509 universal_newlines = True)
510 if mproc.stdout:
511 mag_version = mproc.stdout.splitlines()[0]
512 mag_version_info = mag_version.split('.')
513 try:
514 if int(mag_version_info[0]) > 8:
515 have_mag_8_2 = True
516 elif int(mag_version_info[0]) == 8:
517 if int(mag_version_info[1]) >= 2:
518 have_mag_8_2 = True
Tim Edwardsaac3d1a2021-06-15 16:37:10 -0400519 print('Magic version 8.2 (or better) available on the system.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400520 except ValueError:
521 print('Error: "magic --version" did not return valid version number.')
522 except FileNotFoundError:
523 print('Error: Failed to find executable for magic in standard search path.')
524
525 if not have_mag_8_2:
Tim Edwardsaac3d1a2021-06-15 16:37:10 -0400526 print('WARNING: Magic version 8.2 (or beter) cannot be executed ')
527 print('from the standard executable search path.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400528 print('Please install or correct the search path.')
529 print('Magic database files will not be created, and other missing file formats may not be generated.')
530
531 # Populate any targets that do not specify a library, or where the library is
532 # specified as "primitive".
533
534 # Populate the techLEF and SPICE models, if specified. Also, this section can add
535 # to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
536 # install into libs.tech/ngspice/.
537
538 if libraries == [] or 'primitive' in libraries[0]:
539
540 for option in optionlist[:]:
541
542 # Legacy behavior is to put libs.tech models and techLEF files in
543 # the same grouping as files for the primdev library (which go in
544 # libs.ref). Current behavior is to put all libs.tech files in
545 # a grouping with no library, with unrestricted ability to write
546 # into any subdirectory of libs.tech/. Therefore, need to restrict
547 # legacy use to just 'techlef' and 'models'.
548
549 if len(libraries) > 0 and 'primitive' in libraries[0]:
550 if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
551 continue
552
553 # Normally technology LEF files are associated with IP libraries.
554 # However, if no library is specified or the library is 'primitive'
555 # (legacy behavior), then put in the techLEF directory with no subdirectory.
556
557 filter_scripts = []
558 if option[0] == 'techlef' or option[0] == 'techLEF':
559 for item in option:
560 if item.split('=')[0] == 'filter':
561 filter_scripts.append(item.split('=')[1])
562 break
563
564 if ef_format:
565 techlefdir = targetdir + '/libs.ref/' + 'techLEF'
566 else:
567 techlefdir = targetdir + '/libs.tech/lef'
568
569 os.makedirs(techlefdir, exist_ok=True)
570 # All techlef files should be copied, so use "glob" on the wildcards
571 techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
572
573 for lefname in techlist:
574 leffile = os.path.split(lefname)[1]
575 targname = techlefdir + '/' + leffile
576
577 if os.path.isfile(lefname):
578 shutil.copy(lefname, targname)
579 else:
580 shutil.copytree(lefname, targname)
581
582 for filter_script in filter_scripts:
583 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400584 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400585
586 optionlist.remove(option)
587
588 # All remaining options will refer to specific tools (e.g., -ngspice, -magic)
589 # although generic names (.e.g, -models) are acceptable if the tools know
590 # where to find the files. Currently, most tools have their own formats
591 # and standards for setup, and so generally each install directory will be
592 # unique to one EDA tool.
593
594 else:
595 filter_scripts = []
596 for item in option:
597 if item.split('=')[0] == 'filter':
598 filter_scripts.append(item.split('=')[1])
599 break
600
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400601 print('Diagnostic: installing to ' + option[0] + '.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400602 tooldir = targetdir + '/libs.tech/' + option[0]
603 os.makedirs(tooldir, exist_ok=True)
604
605 # All files should be linked or copied, so use "glob" on
606 # the wildcards. Copy each file and recursively copy each
607 # directory.
608 toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
609
610 for toolname in toollist:
611 toolfile = os.path.split(toolname)[1]
612 targname = tooldir + '/' + toolfile
613
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400614 print(' installing from ' + toolfile + ' to ' + targname)
615
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400616 if os.path.isdir(toolname):
617 # Remove any existing directory, and its contents
618 if os.path.isdir(targname):
619 shutil.rmtree(targname)
620 os.makedirs(targname)
621
622 # Recursively find and copy or link the whole directory
623 # tree from this point.
624
625 alltoollist = glob.glob(toolname + '/**', recursive=True)
626 commonpart = os.path.commonpath(alltoollist)
627 for subtoolname in alltoollist:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400628 # Get the path part that is not common between toollist and
629 # alltoollist.
630 subpart = os.path.relpath(subtoolname, commonpart)
631 subtargname = targname + '/' + subpart
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400632
633 if os.path.isfile(subtoolname):
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400634 os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400635 shutil.copy(subtoolname, subtargname)
636 else:
Tim Edwardsc6202ef2020-09-20 17:16:33 -0400637 print(' copy tree from ' + subtoolname + ' to ' + subtargname)
638 # emulate Python3.8 dirs_exist_ok option
639 try:
640 shutil.copytree(subtoolname, subtargname)
641 except FileExistsError:
642 pass
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400643
644 for filter_script in filter_scripts:
645 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400646 tfilter(subtargname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400647
648 else:
649 # Remove any existing file
650 if os.path.isfile(targname):
651 os.remove(targname)
652 elif os.path.isdir(targname):
653 shutil.rmtree(targname)
654
655 if os.path.isfile(toolname):
656 shutil.copy(toolname, targname)
657 else:
658 shutil.copytree(toolname, targname)
659
660 for filter_script in filter_scripts:
661 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400662 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400663
664 optionlist.remove(option)
665
666 # Do an initial pass through all of the options and determine what is being
667 # installed, so that we know in advance which file formats are missing and
668 # need to be generated.
669
670 for option in optionlist[:]:
671 if option[0] == 'lef':
Tim Edwards91ddc9a2021-06-24 22:50:30 -0400672 have_lefanno = True if 'annotate' in option or 'anno' in option else False
673 have_lef = True if not have_lefanno else False
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400674 if option[0] == 'techlef' or option[0] == 'techLEF':
675 have_techlef = True
676 elif option[0] == 'gds':
677 have_gds = True
678 elif option[0] == 'spice' or option[0] == 'spi':
679 have_spice = True
680 elif option[0] == 'cdl':
681 have_cdl = True
682 elif option[0] == 'verilog':
683 have_verilog = True
684 elif option[0] == 'lib' or option[0] == 'liberty':
685 have_lib = True
686
687 # The remaining options in optionlist should all be types like 'lef' or 'liberty'
688 # and there should be a corresponding library list specified by '-library'
689
690 for option in optionlist[:]:
691
692 # Ignore if no library list---should have been taken care of above.
693 if libraries == []:
694 break
695
696 # Diagnostic
697 print("Install option: " + str(option[0]))
698
Tim Edwards91ddc9a2021-06-24 22:50:30 -0400699 if option[0] == 'lef' and have_lefanno:
700 print("LEF files used for annotation only. Temporary install.")
701
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400702 # For ef_format: always make techlef -> techLEF and spice -> spi
703
704 if ef_format:
705 if option[0] == 'techlef':
706 option[0] = 'techLEF'
707 elif option[0] == 'spice':
708 option[0] = 'spi'
709
710 destdir = targetdir + '/libs.ref/' + option[0]
711 os.makedirs(destdir, exist_ok=True)
712
713 # If the option is followed by the keyword "up" and a number, then
714 # the source should be copied (or linked) from <number> levels up
715 # in the hierarchy (see below).
716
Tim Edwards995c1332020-09-25 15:33:58 -0400717 hier_up = 0
718 for item in option:
719 if item.split('=')[0] == 'up':
720 hier_up = int(item.split('=')[1])
721 break
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400722
723 filter_scripts = []
724 for item in option:
725 if item.split('=')[0] == 'filter':
726 filter_scripts.append(item.split('=')[1])
727 break
728
729 # Option 'stub' applies to netlists ('cdl' or 'spice') and generates
730 # a file with only stub entries.
731 do_stub = 'stub' in option
732
733 # Option 'compile' is a standalone keyword ('comp' may be used).
734 do_compile = 'compile' in option or 'comp' in option
735 do_compile_only = 'compile-only' in option or 'comp-only' in option
736
737 # Option 'nospecify' is a standalone keyword ('nospec' may be used).
738 do_remove_spec = 'nospecify' in option or 'nospec' in option
739
740 # Option 'exclude' has an argument
741 try:
742 excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
743 except IndexError:
744 excludelist = []
745 else:
Tim Edwardsf35c6072021-06-15 16:00:27 -0400746 print('Excluding files: ' + (',').join(excludelist))
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400747
748 # Option 'rename' has an argument
749 try:
750 newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
751 except IndexError:
752 newname = None
753 else:
754 print('Renaming file to: ' + newname)
755
Tim Edwards5c7924d2020-09-30 22:22:10 -0400756 # Option 'sort' has an argument. . .
Tim Edwards995c1332020-09-25 15:33:58 -0400757 try:
758 sortscript = list(item.split('=')[1] for item in option if item.startswith('sort'))[0]
759 except IndexError:
Tim Edwards5c7924d2020-09-30 22:22:10 -0400760 # If option 'sort' is not specified, then use the "natural sort" script
761 sortscript = scriptdir + '/sort_pdkfiles.py'
Tim Edwards995c1332020-09-25 15:33:58 -0400762 else:
763 print('Sorting files with script ' + sortscript)
Tim Edwards995c1332020-09-25 15:33:58 -0400764
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400765 # For each library, create the library subdirectory
766 for library in libraries:
767 if len(library) == 3:
768 destlib = library[2]
769 else:
770 destlib = library[1]
771
772 if ef_format:
773 destlibdir = destdir + '/' + destlib
774 else:
775 destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
776 destlibdir = destdir
777
778 os.makedirs(destlibdir, exist_ok=True)
779
780 # Populate the library subdirectory
781 # Parse the option and replace each '/*/' with the library name,
782 # and check if it is a valid directory name. Then glob the
783 # resulting option name. Warning: This assumes that all
784 # occurences of the text '/*/' match a library name. It should
785 # be possible to wild-card the directory name in such a way that
786 # this is always true.
787
788 testpath = substitute(sourcedir + '/' + option[1], library[1])
789 liblist = glob.glob(testpath)
790
791 # Create a file "sources.txt" (or append to it if it exists)
792 # and add the source directory name so that the staging install
793 # script can know where the files came from.
794
795 with open(destlibdir + '/sources.txt', 'a') as ofile:
796 print(testpath, file=ofile)
797
798 # Create exclude list with glob-style matching using fnmatch
799 if len(liblist) > 0:
800 liblistnames = list(os.path.split(item)[1] for item in liblist)
801 notliblist = []
802 for exclude in excludelist:
803 notliblist.extend(fnmatch.filter(liblistnames, exclude))
804
805 # Apply exclude list
806 if len(notliblist) > 0:
807 for file in liblist[:]:
808 if os.path.split(file)[1] in notliblist:
809 liblist.remove(file)
810
811 if len(excludelist) > 0 and len(notliblist) == 0:
812 print('Warning: Nothing from the exclude list found in sources.')
813 print('excludelist = ' + str(excludelist))
814 print('destlibdir = ' + destlibdir)
815
816 # Diagnostic
817 print('Collecting files from ' + testpath)
818 print('Files to install:')
819 if len(liblist) < 10:
820 for item in liblist:
821 print(' ' + item)
822 else:
823 for item in liblist[0:4]:
824 print(' ' + item)
825 print(' .')
826 print(' .')
827 print(' .')
828 for item in liblist[-6:-1]:
829 print(' ' + item)
830 print('(' + str(len(liblist)) + ' files total)')
831
Tim Edwards995c1332020-09-25 15:33:58 -0400832 destfilelist = []
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400833 for libname in liblist:
834 # Note that there may be a hierarchy to the files in option[1],
835 # say for liberty timing files under different conditions, so
836 # make sure directories have been created as needed.
837
838 libfile = os.path.split(libname)[1]
839 libfilepath = os.path.split(libname)[0]
840 destpathcomp = []
841 for i in range(hier_up):
842 destpathcomp.append('/' + os.path.split(libfilepath)[1])
843 libfilepath = os.path.split(libfilepath)[0]
844 destpathcomp.reverse()
845 destpath = ''.join(destpathcomp)
846
Tim Edwards05e66eb2020-09-24 13:11:59 -0400847 if option[0] == 'verilog':
848 fileext = '.v'
Tim Edwards05e66eb2020-09-24 13:11:59 -0400849 elif option[0] == 'liberty' or option[0] == 'lib':
850 fileext = '.lib'
851 elif option[0] == 'spice' or option[0] == 'spi':
852 fileext = '.spice' if not ef_format else '.spi'
Tim Edwardsbbab9f62020-11-02 10:12:20 -0500853 elif option[0] == 'techlef':
Tim Edwards05e66eb2020-09-24 13:11:59 -0400854 fileext = '.lef'
Tim Edwardsbbab9f62020-11-02 10:12:20 -0500855 else:
856 fileext = '.' + option[0]
Tim Edwards05e66eb2020-09-24 13:11:59 -0400857
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400858 if newname:
Tim Edwards05e66eb2020-09-24 13:11:59 -0400859 if os.path.splitext(newname)[1] == '':
860 newname = newname + fileext
861
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400862 if len(liblist) == 1:
863 destfile = newname
864 else:
865 if not do_compile and not do_compile_only:
866 print('Error: rename specified but more than one file found!')
867 destfile = libfile
868 else:
869 destfile = libfile
870
871 targname = destlibdir + destpath + '/' + destfile
872
873 # NOTE: When using "up" with link_from, could just make
874 # destpath itself a symbolic link; this way is more flexible
875 # but adds one symbolic link per file.
876
877 if destpath != '':
878 if not os.path.isdir(destlibdir + destpath):
879 os.makedirs(destlibdir + destpath, exist_ok=True)
880
881 # Remove any existing file
882 if os.path.isfile(targname):
883 os.remove(targname)
884 elif os.path.isdir(targname):
885 shutil.rmtree(targname)
886
887 # NOTE: Diagnostic, probably much too much output.
888 print(' Install:' + libname + ' to ' + targname)
889 if os.path.isfile(libname):
890 shutil.copy(libname, targname)
891 else:
892 shutil.copytree(libname, targname)
893
894 # File filtering options: Two options 'stub' and 'nospec' are
895 # handled by scripts in ../common/. Custom filters can also be
896 # specified.
897
898 local_filter_scripts = filter_scripts[:]
899
900 if option[0] == 'verilog':
901 # Internally handle syntactical issues with verilog and iverilog
902 vfilter(targname)
903
904 if do_remove_spec:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400905 local_filter_scripts.append(scriptdir + '/remove_specify.py')
906
907 elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
908 if do_stub:
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400909 local_filter_scripts.append(scriptdir + '/makestub.py')
910
911 for filter_script in local_filter_scripts:
912 # Apply filter script to all files in the target directory
Tim Edwardsfd20a0a2021-08-31 19:58:51 -0400913 tfilter(targname, filter_script, ef_format)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400914
Tim Edwards995c1332020-09-25 15:33:58 -0400915 destfilelist.append(os.path.split(targname)[1])
916
917 if sortscript:
Tim Edwardsf35c6072021-06-15 16:00:27 -0400918 with open(destlibdir + '/filelist.txt', 'w') as ofile:
919 for destfile in destfilelist:
920 print(destfile, file=ofile)
Tim Edwards995c1332020-09-25 15:33:58 -0400921 if os.path.isfile(sortscript):
922 print('Diagnostic: Sorting files with ' + sortscript)
923 subprocess.run([sortscript, destlibdir],
924 stdout = subprocess.DEVNULL,
925 stderr = subprocess.DEVNULL)
926
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400927 if do_compile == True or do_compile_only == True:
928 # NOTE: The purpose of "rename" is to put a destlib-named
929 # library elsewhere so that it can be merged with another
Tim Edwards05e66eb2020-09-24 13:11:59 -0400930 # library into a compiled <destlib>.<ext> on another pass.
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400931
932 compname = destlib
933
934 # To do: Make this compatible with linking from another PDK.
935
936 if option[0] == 'verilog':
937 # If there is not a single file with all verilog cells in it,
938 # then compile one, because one does not want to have to have
939 # an include line for every single cell used in a design.
940
941 create_verilog_library(destlibdir, compname, do_compile_only, do_stub, excludelist)
942
943 elif option[0] == 'gds' and have_mag_8_2:
944 # If there is not a single file with all GDS cells in it,
945 # then compile one.
946
947 # Link to the PDK magic startup file from the target directory
948 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
949 if not os.path.isfile(startup_script):
950 startup_script = targetdir + mag_current + pdkname + '.magicrc'
951 create_gds_library(destlibdir, compname, startup_script, do_compile_only, excludelist)
952
953 elif option[0] == 'liberty' or option[0] == 'lib':
954 # If there is not a single file with all liberty cells 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 create_lib_library(destlibdir, compname, do_compile_only, excludelist)
959
960 elif option[0] == 'spice' or option[0] == 'spi':
961 # If there is not a single file with all SPICE subcircuits in it,
962 # then compile one, because one does not want to have to have
963 # an include line for every single cell used in a design.
964
965 spiext = '.spice' if not ef_format else '.spi'
966 create_spice_library(destlibdir, compname, spiext, do_compile_only, do_stub, excludelist)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400967
968 elif option[0] == 'cdl':
969 # If there is not a single file with all CDL subcircuits 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_spice_library(destlibdir, compname, '.cdl', do_compile_only, do_stub, excludelist)
Tim Edwards55f4d0e2020-07-05 15:41:02 -0400974
975 elif option[0] == 'lef':
976 # If there is not a single file with all LEF cells in it,
977 # then compile one, because one does not want to have to have
978 # an include line for every single cell used in a design.
979
980 create_lef_library(destlibdir, compname, do_compile_only, excludelist)
981
Tim Edwards05e66eb2020-09-24 13:11:59 -0400982 if do_compile_only == True:
Tim Edwards4db522f2021-05-04 13:34:40 -0400983 if newname and targname:
Tim Edwards05e66eb2020-09-24 13:11:59 -0400984 if os.path.isfile(targname):
985 os.remove(targname)
986
987 # "rename" with "compile" or "compile-only": Change the name
988 # of the compiled file.
989
990 if newname:
991 print(' Renaming ' + compname + fileext + ' to ' + newname)
992 origname = destlibdir + '/' + compname + fileext
993 targrename = destlibdir + destpath + '/' + newname
994 if os.path.isfile(origname):
995 os.rename(origname, targrename)
Tim Edwards995c1332020-09-25 15:33:58 -0400996
997 # If "filelist.txt" was created, remove it
Tim Edwardsf35c6072021-06-15 16:00:27 -0400998 if sortscript:
999 if os.path.isfile(destlibdir + '/filelist.txt'):
1000 os.remove(destlibdir + '/filelist.txt')
Tim Edwards9be4ac22020-07-26 12:59:30 -04001001
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001002 # Find any libraries/options marked as "privileged" (or "private") and
1003 # move the files from libs.tech or libs.ref to libs.priv, leaving a
1004 # symbolic link in the original location. Do this during the initial
1005 # install so that options following in the list can add files to the
1006 # non-privileged equivalent directory path.
1007
1008 if 'priv' in option or 'privileged' in option or 'private' in option:
1009
1010 # Diagnostic
1011 print("Install option: " + str(option[0]))
1012
1013 if ef_format == True:
1014 os.makedirs(targetdir + '/libs.priv', exist_ok=True)
1015
1016 for library in libraries:
1017 if len(library) == 3:
1018 destlib = library[2]
1019 else:
1020 destlib = library[1]
1021
1022 if ef_format:
1023 srclibdir = targetdir + '/libs.ref/' + option[0] + '/' + destlib
1024 destlibdir = targetdir + '/libs.priv/' + option[0] + '/' + destlib
1025 else:
1026 srclibdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
1027 destlibdir = targetdir + '/libs.priv/' + destlib + '/' + option[0]
1028
1029 if not os.path.exists(destlibdir):
1030 os.makedirs(destlibdir)
1031
1032 print('Moving files in ' + srclibdir + ' to privileged space.')
1033 filelist = os.listdir(srclibdir)
1034 for file in filelist:
1035 srcfile = srclibdir + '/' + file
1036 destfile = destlibdir + '/' + file
1037 if os.path.isfile(destfile):
1038 os.remove(destfile)
1039 elif os.path.isdir(destfile):
1040 shutil.rmtree(destfile)
1041
1042 if os.path.isfile(srcfile):
1043 shutil.copy(srcfile, destfile)
1044 os.remove(srcfile)
1045 else:
1046 shutil.copytree(srcfile, destfile)
1047 shutil.rmtree(srcfile)
1048
1049 print("Completed installation of vendor files.")
1050
1051 #----------------------------------------------------------------
1052 # Installation part 2: Generate derived file formats
1053 #----------------------------------------------------------------
1054
1055 # Now for the harder part. If GDS and/or LEF databases were specified,
1056 # then migrate them to magic (.mag files in layout/ or abstract/).
1057
1058 ignorelist = []
Tim Edwardsc02ef362021-01-04 10:06:56 -05001059 tclscript = None
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001060 do_cdl_scaleu = False
1061 no_cdl_convert = False
1062 no_gds_convert = False
1063 no_lef_convert = False
1064 cdl_compile_only = False
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001065 lef_compile = False
1066 lef_compile_only = False
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001067
1068 cdl_exclude = []
1069 lef_exclude = []
1070 gds_exclude = []
1071 spice_exclude = []
1072 verilog_exclude = []
1073
1074 cdl_reflib = '/libs.ref/'
1075 gds_reflib = '/libs.ref/'
1076 lef_reflib = '/libs.ref/'
1077
1078 for option in optionlist[:]:
1079 if option[0] == 'cdl':
1080 # Option 'scaleu' is a standalone keyword
1081 do_cdl_scaleu = 'scaleu' in option
1082
1083 # Option 'ignore' has arguments after '='
1084 for item in option:
1085 if item.split('=')[0] == 'ignore':
1086 ignorelist = item.split('=')[1].split(',')
1087
Tim Edwards26ab4962021-01-03 14:22:54 -05001088 elif option[0] == 'gds':
1089 for item in option:
1090 if item.split('=')[0] == 'options':
1091 tclscript = item.split('=')[1]
Tim Edwards4db522f2021-05-04 13:34:40 -04001092 tcllines = []
Tim Edwards26ab4962021-01-03 14:22:54 -05001093 print('Adding Tcl script options from file ' + tclscript)
1094
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001095 # Option 'noconvert' is a standalone keyword.
1096 if 'noconvert' in option:
1097 if option[0] == 'cdl':
1098 no_cdl_convert = True
1099 elif option[0] == 'gds':
1100 no_gds_convert = True
1101 elif option[0] == 'lef':
1102 no_lef_convert = True
1103
1104 # Option 'privileged' is a standalone keyword.
1105 if 'priv' in option or 'privileged' in option or 'private' in option:
1106 if option[0] == 'cdl':
1107 cdl_reflib = '/libs.priv/'
1108 elif option[0] == 'gds':
1109 gds_reflib = '/libs.priv/'
1110 elif option[0] == 'lef':
1111 lef_reflib = '/libs.priv/'
1112
1113 # If CDL is marked 'compile-only' then CDL should only convert the
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001114 # compiled file to SPICE if conversion is needed. If LEF is marked
1115 # 'compile' or 'compile-only' in annotate mode, then create a LEF
1116 # library from magic LEF output.
1117
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001118 if 'compile-only' in option:
1119 if option[0] == 'cdl':
1120 cdl_compile_only = True
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001121 elif option[0] == 'lef':
1122 lef_compile_only = True
1123 elif 'compile' in option:
1124 if option[0] == 'lef':
1125 lef_compile = True
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001126
1127 # Find exclude list for any option
1128 for item in option:
1129 if item.split('=')[0] == 'exclude':
1130 exclude_list = item.split('=')[1].split(',')
1131 if option[0] == 'cdl':
1132 cdl_exclude = exclude_list
1133 elif option[0] == 'lef':
1134 lef_exclude = exclude_list
1135 elif option[0] == 'gds':
1136 gds_exclude = exclude_list
1137 elif option[0] == 'spi' or option[0] == 'spice':
1138 spice_exclude = exclude_list
1139 elif option[0] == 'verilog':
1140 verilog_exclude = exclude_list
1141
1142 devlist = []
1143 pdklibrary = None
1144
Tim Edwards4db522f2021-05-04 13:34:40 -04001145 if tclscript:
1146 # If tclscript is a file, then read it. Otherwise, assume
1147 # that the option contents should be inserted verbatim.
1148 if os.path.isfile(tclscript):
1149 with open(tclscript, 'r') as ifile:
1150 tcllines = ifile.read().splitlines()
1151 else:
1152 tcllines = list(tclscript)
1153
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001154 if have_gds and not no_gds_convert:
1155 print("Migrating GDS files to layout.")
1156
1157 if ef_format:
1158 destdir = targetdir + gds_reflib + 'mag'
1159 srcdir = targetdir + gds_reflib + 'gds'
1160 vdir = targetdir + '/libs.ref/' + 'verilog'
1161 cdir = targetdir + cdl_reflib + 'cdl'
1162 sdir = targetdir + cdl_reflib + 'spi'
1163
1164 os.makedirs(destdir, exist_ok=True)
1165
1166 # For each library, create the library subdirectory
1167 for library in libraries:
1168 if len(library) == 3:
1169 destlib = library[2]
1170 else:
1171 destlib = library[1]
1172
1173 if ef_format:
1174 destlibdir = destdir + '/' + destlib
1175 srclibdir = srcdir + '/' + destlib
1176 vlibdir = vdir + '/' + destlib
1177 clibdir = cdir + '/' + destlib
1178 slibdir = sdir + '/' + destlib
1179 else:
1180 destdir = targetdir + gds_reflib + destlib + '/mag'
1181 srcdir = targetdir + gds_reflib + destlib + '/gds'
1182 vdir = targetdir + '/libs.ref/' + destlib + '/verilog'
1183 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1184 sdir = targetdir + cdl_reflib + destlib + '/spice'
1185 destlibdir = destdir
1186 srclibdir = srcdir
1187 vlibdir = vdir
1188 clibdir = cdir
1189 slibdir = sdir
1190
1191 os.makedirs(destlibdir, exist_ok=True)
1192
1193 # For primitive devices, check the PDK script and find the name
1194 # of the library and get a list of supported devices.
1195
1196 if library[0] == 'primitive':
1197 pdkscript = targetdir + mag_current + pdkname + '.tcl'
1198 print('Searching for supported devices in PDK script ' + pdkscript + '.')
1199
1200 if os.path.isfile(pdkscript):
1201 librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
1202 devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
1203 fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
1204 devlist = []
1205 fixedlist = []
1206 with open(pdkscript, 'r') as ifile:
1207 scripttext = ifile.read().splitlines()
1208 for line in scripttext:
1209 lmatch = librex.match(line)
1210 if lmatch:
1211 pdklibrary = lmatch.group(1)
1212 dmatch = devrex.match(line)
1213 if dmatch:
1214 if dmatch.group(1) == pdklibrary:
1215 devlist.append(dmatch.group(2))
1216 fmatch = fixrex.match(line)
1217 if fmatch:
1218 if fmatch.group(1) == pdklibrary:
1219 fixedlist.append(fmatch.group(2))
1220
1221 # Diagnostic
1222 print("PDK library is " + str(pdklibrary))
1223
1224 # Link to the PDK magic startup file from the target directory
1225 # If there is no -F version then look for one without -F (open source PDK)
1226 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1227 if not os.path.isfile(startup_script):
1228 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1229
1230 if have_mag_8_2 and os.path.isfile(startup_script):
1231 # If the symbolic link exists, remove it.
1232 if os.path.isfile(destlibdir + '/.magicrc'):
1233 os.remove(destlibdir + '/.magicrc')
1234 os.symlink(startup_script, destlibdir + '/.magicrc')
1235
1236 # Find GDS file names in the source
1237 print('Getting GDS file list from ' + srclibdir + '.')
1238 gdsfilesraw = os.listdir(srclibdir)
1239 gdsfiles = []
1240 for gdsfile in gdsfilesraw:
1241 gdsext = os.path.splitext(gdsfile)[1].lower()
1242 if gdsext == '.gds' or gdsext == '.gdsii' or gdsext == '.gds2':
1243 gdsfiles.append(gdsfile)
1244
1245 # Create exclude list with glob-style matching using fnmatch
1246 if len(gdsfiles) > 0:
1247 gdsnames = list(os.path.split(item)[1] for item in gdsfiles)
1248 notgdsnames = []
1249 for exclude in gds_exclude:
1250 notgdsnames.extend(fnmatch.filter(gdsnames, exclude))
1251
1252 # Apply exclude list
1253 if len(notgdsnames) > 0:
1254 for file in gdsfiles[:]:
1255 if os.path.split(file)[1] in notgdsnames:
1256 gdsfiles.remove(file)
1257
1258 # Generate a script called "generate_magic.tcl" and leave it in
1259 # the target directory. Use it as input to magic to create the
1260 # .mag files from the database.
1261
1262 print('Creating magic generation script to generate magic database files.')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001263 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1264 print('#!/usr/bin/env wish', file=ofile)
1265 print('#--------------------------------------------', file=ofile)
1266 print('# Script to generate .mag files from .gds ', file=ofile)
1267 print('#--------------------------------------------', file=ofile)
Tim Edwardsb6e8c7e2021-03-30 12:20:15 -04001268 print('crashbackups stop', file=ofile)
1269 print('drc off', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001270 print('gds readonly true', file=ofile)
1271 print('gds flatten true', file=ofile)
1272 print('gds rescale false', file=ofile)
1273 print('tech unlock *', file=ofile)
1274
Tim Edwards26ab4962021-01-03 14:22:54 -05001275 # Add custom Tcl script lines before "gds read".
1276 if tclscript:
1277 for line in tcllines:
1278 print(line, file=ofile)
1279
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001280 for gdsfile in gdsfiles:
1281 # Note: DO NOT use a relative path here.
1282 print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
1283
1284 # Make sure properties include the Tcl generated cell
1285 # information from the PDK script
1286
1287 if pdklibrary:
1288 tclfixedlist = '{' + ' '.join(fixedlist) + '}'
1289 print('set devlist ' + tclfixedlist, file=ofile)
1290 print('set topcell [lindex [cellname list top] 0]',
1291 file=ofile)
1292
1293 print('foreach cellname $devlist {', file=ofile)
1294 print(' load $cellname', file=ofile)
1295 print(' property gencell $cellname', file=ofile)
1296 print(' property parameter m=1', file=ofile)
1297 print(' property library ' + pdklibrary, file=ofile)
1298 print('}', file=ofile)
1299 print('load $topcell', file=ofile)
1300
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001301 else:
1302 # Use LEF files to set the port properties
1303 if have_lefanno or have_lef:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001304 lefdirname = 'lef'
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001305
1306 # Find LEF file names in the source
1307 if ef_format:
1308 lefsrcdir = targetdir + lef_reflib + lefdirname
1309 lefsrclibdir = lefsrcdir + '/' + destlib
1310 else:
1311 lefsrcdir = targetdir + lef_reflib + destlib + '/' + lefdirname
1312 lefsrclibdir = lefsrcdir
1313
1314 leffiles = os.listdir(lefsrclibdir)
1315 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001316 if len(leffiles) > 0:
1317 lefnames = list(os.path.split(item)[1] for item in leffiles)
1318 notlefnames = []
1319 for exclude in lef_exclude:
1320 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1321
1322 # Apply exclude list
1323 if len(notlefnames) > 0:
1324 for file in leffiles[:]:
1325 if os.path.split(file)[1] in notlefnames:
1326 leffiles.remove(file)
1327
1328 if len(leffiles) > 0:
1329 print('puts stdout "Annotating cells from LEF"', file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001330 for leffile in leffiles:
1331 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1332
1333 # Use CDL or SPICE netlists to set the port order
1334 if have_cdl or have_spice:
1335 if have_cdl:
1336 netdir = clibdir
1337 else:
1338 netdir = slibdir
1339
1340 # Find CDL/SPICE file names in the source
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001341 # Ignore "sources.txt" if it is in the list.
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001342 netfiles = os.listdir(netdir)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001343 print('puts stdout "Annotating cells from CDL/SPICE"',
1344 file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001345 for netfile in netfiles:
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001346 if os.path.split(netfile)[1] != 'sources.txt':
1347 print('catch {readspice ' + netdir + '/' + netfile
1348 + '}', file=ofile)
Tim Edwardsf00bbc62020-10-14 17:29:58 -04001349
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001350 # print('cellname delete \(UNNAMED\)', file=ofile)
1351 print('puts stdout "Writing all magic database files"', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001352 print('writeall force', file=ofile)
1353
1354 leffiles = []
1355 lefmacros = []
Tim Edwards41c19302021-06-25 15:45:16 -04001356 if have_lef:
1357 # Nothing to do; LEF macros were already installed.
1358 pass
1359 elif have_lefanno:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001360 # Find LEF file names in the source
1361 if ef_format:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001362 lefsrcdir = targetdir + lef_reflib + 'lef'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001363 lefsrclibdir = lefsrcdir + '/' + destlib
1364 else:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001365 lefsrcdir = targetdir + lef_reflib + destlib + '/lef'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001366 lefsrclibdir = lefsrcdir
1367
1368 leffiles = os.listdir(lefsrclibdir)
1369 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
Tim Edwards41c19302021-06-25 15:45:16 -04001370 # Create exclude list with glob-style matching using fnmatch
1371 if len(leffiles) > 0:
1372 lefnames = list(os.path.split(item)[1] for item in leffiles)
1373 notlefnames = []
1374 for exclude in lef_exclude:
1375 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1376
1377 # Apply exclude list
1378 if len(notlefnames) > 0:
1379 for file in leffiles[:]:
1380 if os.path.split(file)[1] in notlefnames:
1381 leffiles.remove(file)
1382
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001383 # Get list of abstract views to make from LEF macros
Tim Edwards41c19302021-06-25 15:45:16 -04001384 # (Note: exclude list can only contain the file being
1385 # read, not individual macro names in the file; might
1386 # need some additional feature to accommodate this.)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001387 for leffile in leffiles:
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001388 with open(lefsrclibdir + '/' + leffile, 'r') as ifile:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001389 ltext = ifile.read()
1390 llines = ltext.splitlines()
1391 for lline in llines:
1392 ltok = re.split(' |\t|\(', lline)
1393 if ltok[0] == 'MACRO':
1394 lefmacros.append(ltok[1])
1395
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001396 elif have_verilog and os.path.isdir(vlibdir):
1397 # Get list of abstract views to make from verilog modules
Tim Edwards41c19302021-06-25 15:45:16 -04001398 # (NOTE: no way to apply exclude list here!)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001399 vfiles = os.listdir(vlibdir)
1400 vfiles = list(item for item in vfiles if os.path.splitext(item)[1] == '.v')
Tim Edwards41c19302021-06-25 15:45:16 -04001401 # Create exclude list with glob-style matching using fnmatch
1402 if len(vfiles) > 0:
1403 vnames = list(os.path.split(item)[1] for item in vfiles)
1404 notvnames = []
1405 for exclude in verilog_exclude:
1406 notvnames.extend(fnmatch.filter(vnames, exclude))
1407
1408 # Apply exclude list
1409 if len(notvnames) > 0:
1410 for file in vfiles[:]:
1411 if os.path.split(file)[1] in notvnames:
1412 vfiles.remove(file)
1413
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001414 for vfile in vfiles:
1415 with open(vlibdir + '/' + vfile, 'r') as ifile:
1416 vtext = ifile.read()
1417 vlines = vtext.splitlines()
1418 for vline in vlines:
1419 vtok = re.split(' |\t|\(', vline)
1420 try:
1421 if vtok[0] == 'module':
1422 if vtok[1] not in lefmacros:
1423 lefmacros.append(vtok[1])
1424 except:
1425 pass
1426
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001427 elif have_cdl and os.path.isdir(clibdir):
1428 # Get list of abstract views to make from CDL subcircuits
1429 cfiles = os.listdir(clibdir)
1430 cfiles = list(item for item in cfiles if os.path.splitext(item)[1] == '.cdl')
Tim Edwards41c19302021-06-25 15:45:16 -04001431 # Create exclude list with glob-style matching using fnmatch
1432 if len(cfiles) > 0:
1433 cnames = list(os.path.split(item)[1] for item in cfiles)
1434 notcnames = []
1435 for exclude in cdl_exclude:
1436 notcnames.extend(fnmatch.filter(cnames, exclude))
1437
1438 # Apply exclude list
1439 if len(notcnames) > 0:
1440 for file in cfiles[:]:
1441 if os.path.split(file)[1] in notcnames:
1442 cfiles.remove(file)
1443
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001444 for cfile in cfiles:
1445 with open(clibdir + '/' + cfile, 'r') as ifile:
1446 ctext = ifile.read()
1447 clines = ctext.splitlines()
1448 for cline in clines:
1449 ctok = cline.split()
1450 try:
1451 if ctok[0].lower() == '.subckt':
1452 if ctok[1] not in lefmacros:
1453 lefmacros.append(ctok[1])
1454 except:
1455 pass
1456
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001457 elif have_spice and os.path.isdir(slibdir):
1458 # Get list of abstract views to make from SPICE subcircuits
1459 sfiles = os.listdir(slibdir)
1460 sfiles = list(item for item in sfiles)
Tim Edwards41c19302021-06-25 15:45:16 -04001461
1462 # Create exclude list with glob-style matching using fnmatch
1463 if len(sfiles) > 0:
1464 snames = list(os.path.split(item)[1] for item in sfiles)
1465 notsnames = []
1466 for exclude in spice_exclude:
1467 notsnames.extend(fnmatch.filter(snames, exclude))
1468
1469 # Apply exclude list
1470 if len(notsnames) > 0:
1471 for file in sfiles[:]:
1472 if os.path.split(file)[1] in notsnames:
1473 sfiles.remove(file)
1474
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001475 for sfile in sfiles:
1476 with open(slibdir + '/' + sfile, 'r') as ifile:
1477 stext = ifile.read()
1478 slines = stext.splitlines()
1479 for sline in slines:
1480 stok = sline.split()
1481 try:
1482 if stok[0].lower() == '.subckt':
1483 if stok[1] not in lefmacros:
1484 lefmacros.append(stok[1])
1485 except:
1486 pass
1487
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001488 if not lefmacros:
1489 print('No source for abstract views: Abstract views not made.')
1490 elif not have_lef:
1491 # This library has a GDS database but no LEF database. Use
1492 # magic to create abstract views of the GDS cells. If
1493 # option "annotate" is given, then read the LEF file after
1494 # loading the database file to annotate the cell with
1495 # information from the LEF file. This usually indicates
1496 # that the LEF file has some weird definition of obstruction
1497 # layers and we want to normalize them by using magic's LEF
1498 # write procedure, but we still need the pin use and class
1499 # information from the LEF file, and maybe the bounding box.
1500
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001501
1502 # For annotation, the LEF file output will overwrite the
1503 # original source LEF file.
1504 lefdest = lefsrclibdir + '/' if have_lefanno else ''
1505
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001506 for leffile in leffiles:
1507 if have_lefanno:
1508 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1509 for lefmacro in lefmacros:
1510 print('if {[cellname list exists ' + lefmacro + '] != 0} {', file=ofile)
1511 print(' load ' + lefmacro, file=ofile)
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001512 print(' lef write ' + lefdest + lefmacro + ' -hide', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001513 print('}', file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001514
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001515 print('puts stdout "Done."', file=ofile)
1516 print('quit -noprompt', file=ofile)
1517
1518 print('Running magic to create magic database files.')
1519 sys.stdout.flush()
1520
1521 # Run magic to read in the GDS file and write out magic databases.
1522 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1523 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1524 stdin = ifile, stdout = subprocess.PIPE,
1525 stderr = subprocess.PIPE, cwd = destlibdir,
1526 universal_newlines = True)
1527 if mproc.stdout:
1528 for line in mproc.stdout.splitlines():
1529 print(line)
1530 if mproc.stderr:
1531 print('Error message output from magic:')
1532 for line in mproc.stderr.splitlines():
1533 print(line)
1534 if mproc.returncode != 0:
1535 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1536
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001537 # Set have_lef now that LEF files were made, so they
1538 # can be used to generate the maglef/ databases.
1539 have_lef = True
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001540
1541 elif not have_mag_8_2:
1542 print('The installer is not able to run magic.')
1543 else:
1544 print("Master PDK magic startup file not found. Did you install")
1545 print("PDK tech files before PDK vendor files?")
1546
Tim Edwards91ddc9a2021-06-24 22:50:30 -04001547 if have_lefanno:
1548 # LEF files were used for annotation. If "compile" or "compile-only"
1549 # was also passed as an option, then build the LEF library now from
1550 # the LEF output from magic.
1551 print("Compiling LEF library from magic output.")
1552 if lef_compile or lef_compile_only:
1553 create_lef_library(lefsrclibdir, destlib, lef_compile_only, lef_exclude)
1554
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001555 if have_lef and not no_lef_convert:
1556 print("Migrating LEF files to layout.")
1557 if ef_format:
1558 destdir = targetdir + '/libs.ref/' + 'maglef'
1559 srcdir = targetdir + lef_reflib + 'lef'
1560 magdir = targetdir + gds_reflib + 'mag'
1561 cdldir = targetdir + cdl_reflib + 'cdl'
1562 os.makedirs(destdir, exist_ok=True)
1563
1564 # For each library, create the library subdirectory
1565 for library in libraries:
1566 if len(library) == 3:
1567 destlib = library[2]
1568 else:
1569 destlib = library[1]
1570
1571 if ef_format:
1572 destlibdir = destdir + '/' + destlib
1573 srclibdir = srcdir + '/' + destlib
1574 maglibdir = magdir + '/' + destlib
1575 cdllibdir = cdldir + '/' + destlib
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001576 clibdir = cdir + '/' + destlib
1577 slibdir = sdir + '/' + destlib
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001578 else:
1579 destdir = targetdir + '/libs.ref/' + destlib + '/maglef'
1580 srcdir = targetdir + lef_reflib + destlib + '/lef'
1581 magdir = targetdir + gds_reflib + destlib + '/mag'
1582 cdldir = targetdir + cdl_reflib + destlib + '/cdl'
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001583 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1584 sdir = targetdir + cdl_reflib + destlib + '/spice'
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001585
1586 destlibdir = destdir
1587 srclibdir = srcdir
1588 maglibdir = magdir
1589 cdllibdir = cdldir
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001590 clibdir = cdir
1591 slibdir = sdir
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001592
1593 os.makedirs(destlibdir, exist_ok=True)
1594
1595 # Link to the PDK magic startup file from the target directory
1596 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1597 if not os.path.isfile(startup_script):
1598 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1599
1600 if have_mag_8_2 and os.path.isfile(startup_script):
1601 # If the symbolic link exists, remove it.
1602 if os.path.isfile(destlibdir + '/.magicrc'):
1603 os.remove(destlibdir + '/.magicrc')
1604 os.symlink(startup_script, destlibdir + '/.magicrc')
1605
1606 # Find LEF file names in the source
Tim Edwards855295e2021-09-08 10:57:54 -04001607 leffiles = []
1608 if os.path.isdir(srclibdir):
1609 leffiles = os.listdir(srclibdir)
1610 leffiles = list(item for item in leffiles if os.path.splitext(item)[1].lower() == '.lef')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001611
1612 # Get list of abstract views to make from LEF macros
1613 lefmacros = []
1614 err_no_macros = False
1615 for leffile in leffiles:
1616 with open(srclibdir + '/' + leffile, 'r') as ifile:
1617 ltext = ifile.read()
1618 llines = ltext.splitlines()
1619 for lline in llines:
1620 ltok = re.split(' |\t|\(', lline)
1621 if ltok[0] == 'MACRO':
1622 lefmacros.append(ltok[1])
1623
1624 # Create exclude list with glob-style matching using fnmatch
1625 if len(lefmacros) > 0:
1626 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1627 notlefnames = []
1628 for exclude in lef_exclude:
1629 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1630
1631 # Apply exclude list
1632 if len(notlefnames) > 0:
1633 for file in lefmacros[:]:
1634 if os.path.split(file)[1] in notlefnames:
1635 lefmacros.remove(file)
1636
1637 if len(leffiles) == 0:
1638 print('Warning: No LEF files found in ' + srclibdir)
1639 continue
1640
1641 print('Generating conversion script to create magic databases from LEF')
1642
1643 # Generate a script called "generate_magic.tcl" and leave it in
1644 # the target directory. Use it as input to magic to create the
1645 # .mag files from the database.
1646
1647 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1648 print('#!/usr/bin/env wish', file=ofile)
1649 print('#--------------------------------------------', file=ofile)
1650 print('# Script to generate .mag files from .lef ', file=ofile)
1651 print('#--------------------------------------------', file=ofile)
1652 print('tech unlock *', file=ofile)
1653
1654 # If there are devices in the LEF file that come from the
1655 # PDK library, then copy this list into the script.
1656
1657 if pdklibrary:
1658 shortdevlist = []
1659 for macro in lefmacros:
1660 if macro in devlist:
1661 shortdevlist.append(macro)
1662
1663 tcldevlist = '{' + ' '.join(shortdevlist) + '}'
1664 print('set devlist ' + tcldevlist, file=ofile)
1665
1666 for leffile in leffiles:
1667 print('lef read ' + srclibdir + '/' + leffile, file=ofile)
1668
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001669 # Use CDL or SPICE netlists to make sure that ports are
1670 # present, and to set the port order
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001671
Tim Edwardsbc6e3912020-11-10 11:02:45 -05001672 if have_cdl or have_spice:
1673 if have_cdl:
1674 netdir = clibdir
1675 else:
1676 netdir = slibdir
1677
1678 # Find CDL/SPICE file names in the source
1679 # Ignore "sources.txt" if it is in the list.
1680 netfiles = os.listdir(netdir)
1681 print('puts stdout "Annotating cells from CDL/SPICE"',
1682 file=ofile)
1683 for netfile in netfiles:
1684 if os.path.split(netfile)[1] != 'sources.txt':
1685 print('catch {readspice ' + netdir + '/' + netfile
1686 + '}', file=ofile)
1687
1688 for lefmacro in lefmacros:
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001689
1690 if pdklibrary and lefmacro in shortdevlist:
1691 print('set cellname ' + lefmacro, file=ofile)
1692 print('if {[lsearch $devlist $cellname] >= 0} {',
1693 file=ofile)
1694 print(' load $cellname', file=ofile)
1695 print(' property gencell $cellname', file=ofile)
1696 print(' property parameter m=1', file=ofile)
1697 print(' property library ' + pdklibrary, file=ofile)
1698 print('}', file=ofile)
1699
1700 # Load one of the LEF files so that the default (UNNAMED) cell
1701 # is not loaded, then delete (UNNAMED) so it doesn't generate
1702 # an error message.
1703 if len(lefmacros) > 0:
1704 print('load ' + lefmacros[0], file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05001705 # print('cellname delete \(UNNAMED\)', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001706 else:
1707 err_no_macros = True
1708 print('writeall force', file=ofile)
1709 print('puts stdout "Done."', file=ofile)
1710 print('quit -noprompt', file=ofile)
1711
1712 if err_no_macros == True:
1713 print('Warning: No LEF macros were defined.')
1714
1715 print('Running magic to create magic databases from LEF')
1716 sys.stdout.flush()
1717
1718 # Run magic to read in the LEF file and write out magic databases.
1719 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1720 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1721 stdin = ifile, stdout = subprocess.PIPE,
1722 stderr = subprocess.PIPE, cwd = destlibdir,
1723 universal_newlines = True)
1724 if mproc.stdout:
1725 for line in mproc.stdout.splitlines():
1726 print(line)
1727 if mproc.stderr:
1728 print('Error message output from magic:')
1729 for line in mproc.stderr.splitlines():
1730 print(line)
1731 if mproc.returncode != 0:
1732 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1733
1734
1735 # Now list all the .mag files generated, and for each, read the
1736 # corresponding file from the mag/ directory, pull the GDS file
1737 # properties, and add those properties to the maglef view. Also
1738 # read the CDL (or SPICE) netlist, read the ports, and rewrite
1739 # the port order in the mag and maglef file accordingly.
1740
1741 # Diagnostic
1742 print('Annotating files in ' + destlibdir)
1743 sys.stdout.flush()
1744 magfiles = os.listdir(destlibdir)
1745 magfiles = list(item for item in magfiles if os.path.splitext(item)[1] == '.mag')
1746 for magroot in magfiles:
1747 magname = os.path.splitext(magroot)[0]
1748 magfile = maglibdir + '/' + magroot
1749 magleffile = destlibdir + '/' + magroot
1750 prop_lines = get_gds_properties(magfile)
1751
1752 # Make sure properties include the Tcl generated cell
1753 # information from the PDK script
1754
1755 prop_gencell = []
1756 if pdklibrary:
1757 if magname in fixedlist:
1758 prop_gencell.append('gencell ' + magname)
1759 prop_gencell.append('library ' + pdklibrary)
1760 prop_gencell.append('parameter m=1')
1761
1762 nprops = len(prop_lines) + len(prop_gencell)
1763
1764 cdlfile = cdllibdir + '/' + magname + '.cdl'
1765 if os.path.exists(cdlfile):
1766 cdlfiles = [cdlfile]
1767 else:
1768 # Assume there is at least one file with all cell subcircuits
1769 # in it.
1770 try:
1771 cdlfiles = glob.glob(cdllibdir + '/*.cdl')
1772 except:
1773 pass
1774 if len(cdlfiles) > 0:
1775 for cdlfile in cdlfiles:
1776 port_dict = get_subckt_ports(cdlfile, magname)
1777 if port_dict != {}:
1778 break
1779 else:
1780 port_dict = {}
1781
1782 if port_dict == {}:
1783 print('No CDL file contains ' + destlib + ' device ' + magname)
1784 cdlfile = None
1785 # To be done: If destlib is 'primitive', then look in
1786 # SPICE models for port order.
1787 if destlib == 'primitive':
1788 print('Fix me: Need to look in SPICE models!')
1789
1790 proprex = re.compile('<< properties >>')
1791 endrex = re.compile('<< end >>')
1792 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 -05001793 flabrex = re.compile('flabel[ \t]+.*[ \t]+([^ \t]+)[ \t]*')
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001794 portrex = re.compile('port[ \t]+([^ \t]+)[ \t]+(.*)')
1795 gcellrex = re.compile('string gencell')
1796 portnum = -1
1797
1798 with open(magleffile, 'r') as ifile:
1799 magtext = ifile.read().splitlines()
1800
1801 with open(magleffile, 'w') as ofile:
1802 has_props = False
1803 is_gencell = False
1804 for line in magtext:
1805 tmatch = portrex.match(line)
1806 if tmatch:
1807 if portnum >= 0:
1808 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1809 else:
1810 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1811 ematch = endrex.match(line)
1812 if ematch and nprops > 0:
1813 if not has_props:
1814 print('<< properties >>', file=ofile)
1815 if not is_gencell:
1816 for prop in prop_gencell:
1817 print('string ' + prop, file=ofile)
1818 for prop in prop_lines:
1819 print('string ' + prop, file=ofile)
1820
1821 print(line, file=ofile)
1822 pmatch = proprex.match(line)
1823 if pmatch:
1824 has_props = True
1825
1826 gmatch = gcellrex.match(line)
1827 if gmatch:
1828 is_gencell = True
1829
1830 lmatch = flabrex.match(line)
1831 if not lmatch:
1832 lmatch = rlabrex.match(line)
1833 if lmatch:
1834 labname = lmatch.group(1).lower()
1835 try:
1836 portnum = port_dict[labname]
1837 except:
1838 portnum = -1
1839
1840 if os.path.exists(magfile):
1841 with open(magfile, 'r') as ifile:
1842 magtext = ifile.read().splitlines()
1843
1844 with open(magfile, 'w') as ofile:
1845 for line in magtext:
1846 tmatch = portrex.match(line)
1847 if tmatch:
1848 if portnum >= 0:
1849 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1850 else:
1851 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1852 ematch = endrex.match(line)
1853 print(line, file=ofile)
1854 lmatch = flabrex.match(line)
1855 if not lmatch:
1856 lmatch = rlabrex.match(line)
1857 if lmatch:
1858 labname = lmatch.group(1).lower()
1859 try:
1860 portnum = port_dict[labname]
1861 except:
1862 portnum = -1
1863 elif os.path.splitext(magfile)[1] == '.mag':
1864 # NOTE: Possibly this means the GDS cell has a different name.
1865 print('Error: No file ' + magfile + '. Why is it in maglef???')
1866
1867 elif not have_mag_8_2:
1868 print('The installer is not able to run magic.')
1869 else:
1870 print("Master PDK magic startup file not found. Did you install")
1871 print("PDK tech files before PDK vendor files?")
1872
1873 # If SPICE or CDL databases were specified, then convert them to
1874 # a form that can be used by ngspice, using the cdl2spi.py script
1875
1876 if have_spice:
1877 if ef_format:
1878 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1879 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1880
1881 elif have_cdl and not no_cdl_convert:
1882 if ef_format:
1883 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
1884 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
1885
1886 print("Migrating CDL netlists to SPICE.")
1887 sys.stdout.flush()
1888
1889 if ef_format:
1890 destdir = targetdir + cdl_reflib + 'spi'
1891 srcdir = targetdir + cdl_reflib + 'cdl'
1892 os.makedirs(destdir, exist_ok=True)
1893
1894 # For each library, create the library subdirectory
1895 for library in libraries:
1896 if len(library) == 3:
1897 destlib = library[2]
1898 else:
1899 destlib = library[1]
1900
1901 if ef_format:
1902 destlibdir = destdir + '/' + destlib
1903 srclibdir = srcdir + '/' + destlib
1904 else:
1905 destdir = targetdir + cdl_reflib + destlib + '/spice'
1906 srcdir = targetdir + cdl_reflib + destlib + '/cdl'
1907
1908 destlibdir = destdir
1909 srclibdir = srcdir
1910
1911 os.makedirs(destlibdir, exist_ok=True)
1912
1913 # Find CDL file names in the source
1914 # If CDL is marked compile-only then ONLY convert <distdir>.cdl
1915 if cdl_compile_only:
1916 alllibname = destlibdir + '/' + destlib + '.cdl'
1917 if not os.path.exists(alllibname):
1918 cdl_compile_only = False
1919 else:
1920 cdlfiles = [alllibname]
1921
1922 if not cdl_compile_only:
1923 cdlfiles = os.listdir(srclibdir)
1924 cdlfiles = list(item for item in cdlfiles if os.path.splitext(item)[1].lower() == '.cdl')
1925
1926 # The directory with scripts should be in ../common with respect
1927 # to the Makefile that determines the cwd.
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001928
1929 # Run cdl2spi.py script to read in the CDL file and write out SPICE
1930 for cdlfile in cdlfiles:
1931 if ef_format:
1932 spiname = os.path.splitext(cdlfile)[0] + '.spi'
1933 else:
1934 spiname = os.path.splitext(cdlfile)[0] + '.spice'
1935 procopts = [scriptdir + '/cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
1936 if do_cdl_scaleu:
1937 procopts.append('-dscale=u')
1938 for item in ignorelist:
1939 procopts.append('-ignore=' + item)
1940
1941 print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
1942 pproc = subprocess.run(procopts,
1943 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
1944 stderr = subprocess.PIPE, cwd = destlibdir,
1945 universal_newlines = True)
1946 if pproc.stdout:
1947 for line in pproc.stdout.splitlines():
1948 print(line)
1949 if pproc.stderr:
1950 print('Error message output from cdl2spi.py:')
1951 for line in pproc.stderr.splitlines():
1952 print(line)
1953
1954 elif have_gds and not no_gds_convert:
1955 # If neither SPICE nor CDL formats is available in the source, then
1956 # read GDS; if the result has no ports, then read the corresponding
1957 # LEF library to get port information. Then write out a SPICE netlist
1958 # for the whole library. NOTE: If there is no CDL or SPICE source,
1959 # then the port numbering is arbitrary, and becomes whatever the
1960 # output of this script makes it.
1961
1962 if ef_format:
1963 destdir = targetdir + cdl_reflib + 'spi'
1964 srcdir = targetdir + gds_reflib + 'gds'
1965 lefdir = targetdir + lef_reflib + 'lef'
1966 os.makedirs(destdir, exist_ok=True)
1967
1968 # For each library, create the library subdirectory
1969 for library in libraries:
1970 if len(library) == 3:
1971 destlib = library[2]
1972 else:
1973 destlib = library[1]
1974
1975 if ef_format:
1976 destlibdir = destdir + '/' + destlib
1977 srclibdir = srcdir + '/' + destlib
1978 leflibdir = lefdir + '/' + destlib
1979 else:
1980 destdir = targetdir + cdl_reflib + destlib + '/spice'
1981 srcdir = targetdir + gds_reflib + destlib + '/gds'
1982 lefdir = targetdir + lef_reflib + destlib + '/lef'
1983
1984 destlibdir = destdir
1985 srclibdir = srcdir
1986 leflibdir = lefdir
1987
1988 os.makedirs(destlibdir, exist_ok=True)
1989
1990 # Link to the PDK magic startup file from the target directory
1991 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1992 if not os.path.isfile(startup_script):
1993 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1994 if os.path.isfile(startup_script):
1995 # If the symbolic link exists, remove it.
1996 if os.path.isfile(destlibdir + '/.magicrc'):
1997 os.remove(destlibdir + '/.magicrc')
1998 os.symlink(startup_script, destlibdir + '/.magicrc')
1999
2000 # Get the consolidated GDS library file, or a list of all GDS files
2001 # if there is no single consolidated library
2002
2003 allgdslibname = srclibdir + '/' + destlib + '.gds'
2004 if not os.path.isfile(allgdslibname):
2005 glist = glob.glob(srclibdir + '/*.gds')
2006 glist.extend(glob.glob(srclibdir + '/*.gdsii'))
2007 glist.extend(glob.glob(srclibdir + '/*.gds2'))
2008
2009 allleflibname = leflibdir + '/' + destlib + '.lef'
2010 if not os.path.isfile(allleflibname):
2011 llist = glob.glob(leflibdir + '/*.lef')
2012
2013 print('Creating magic generation script to generate SPICE library.')
2014 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
2015 print('#!/usr/bin/env wish', file=ofile)
2016 print('#---------------------------------------------', file=ofile)
2017 print('# Script to generate SPICE library from GDS ', file=ofile)
2018 print('#---------------------------------------------', file=ofile)
2019 print('drc off', file=ofile)
2020 print('gds readonly true', file=ofile)
2021 print('gds flatten true', file=ofile)
2022 print('gds rescale false', file=ofile)
2023 print('tech unlock *', file=ofile)
2024
Tim Edwards8b96a1b2021-04-27 15:16:26 -04002025 # Add custom Tcl script lines before "gds read".
2026 if tclscript:
2027 for line in tcllines:
2028 print(line, file=ofile)
2029
Tim Edwards55f4d0e2020-07-05 15:41:02 -04002030 if not os.path.isfile(allgdslibname):
2031 for gdsfile in glist:
2032 print('gds read ' + gdsfile, file=ofile)
2033 else:
2034 print('gds read ' + allgdslibname, file=ofile)
2035
2036 if not os.path.isfile(allleflibname):
2037 # Annotate the cells with information from the LEF files
2038 for leffile in llist:
2039 print('lef read ' + leffile, file=ofile)
2040 else:
2041 print('lef read ' + allleflibname, file=ofile)
2042
2043 # Load first file and remove the (UNNAMED) cell
2044 if not os.path.isfile(allgdslibname):
2045 print('load ' + os.path.splitext(glist[0])[0], file=ofile)
2046 else:
2047 gdslibroot = os.path.split(allgdslibname)[1]
2048 print('load ' + os.path.splitext(gdslibroot)[0], file=ofile)
Tim Edwardsfaac36a2020-11-06 20:37:24 -05002049 # print('cellname delete \(UNNAMED\)', file=ofile)
Tim Edwards55f4d0e2020-07-05 15:41:02 -04002050
2051 print('ext2spice lvs', file=ofile)
2052
2053 # NOTE: Leaving "subcircuit top" as "auto" (default) can cause
2054 # cells like decap that have no I/O to be output without a subcircuit
2055 # wrapper. Also note that if this happens, it is an indication that
2056 # power supplies have not been labeled as ports, which is harder to
2057 # handle and should be fixed in the source.
2058 print('ext2spice subcircuit top on', file=ofile)
2059
2060 print('ext2spice cthresh 0.1', file=ofile)
2061
2062 if os.path.isfile(allgdslibname):
2063 print('select top cell', file=ofile)
2064 print('set glist [cellname list children]', file=ofile)
2065 print('foreach cell $glist {', file=ofile)
2066 else:
2067 print('foreach cell [cellname list top] {', file=ofile)
2068
2069 print(' load $cell', file=ofile)
2070 print(' puts stdout "Extracting cell $cell"', file=ofile)
2071 print(' extract all', file=ofile)
2072 print(' ext2spice', file=ofile)
2073 print('}', file=ofile)
2074 print('puts stdout "Done."', file=ofile)
2075 print('quit -noprompt', file=ofile)
2076
2077 # Run magic to read in the individual GDS files and
2078 # write out the consolidated GDS library
2079
2080 print('Running magic to create GDS library.')
2081 sys.stdout.flush()
2082
2083 mproc = subprocess.run(['magic', '-dnull', '-noconsole',
2084 destlibdir + '/generate_magic.tcl'],
2085 stdin = subprocess.DEVNULL,
2086 stdout = subprocess.PIPE,
2087 stderr = subprocess.PIPE, cwd = destlibdir,
2088 universal_newlines = True)
2089 if mproc.stdout:
2090 for line in mproc.stdout.splitlines():
2091 print(line)
2092 if mproc.stderr:
2093 print('Error message output from magic:')
2094 for line in mproc.stderr.splitlines():
2095 print(line)
2096 if mproc.returncode != 0:
2097 print('ERROR: Magic exited with status ' + str(mproc.returncode))
2098
2099 # Remove intermediate extraction files
2100 extfiles = glob.glob(destlibdir + '/*.ext')
2101 for extfile in extfiles:
2102 os.remove(extfile)
2103
2104 # If the GDS file was a consolidated file of all cells, then
2105 # create a similar SPICE library of all cells.
2106
2107 if os.path.isfile(allgdslibname):
2108 spiext = '.spice' if not ef_format else '.spi'
2109 create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist)
2110
2111 sys.exit(0)