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