blob: 20676639827edc112e434b2247725ef0560ad437 [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
616 print('puts stdout "Writing GDS file ' + destlib + '"', file=ofile)
617 print('gds write ' + destlib, file=ofile)
618 print('puts stdout "Done."', file=ofile)
619 print('quit -noprompt', file=ofile)
620
621 # Run magic to read in the individual GDS files and
622 # write out the consolidated GDS library
623
624 print('Running magic to create GDS library.')
625 sys.stdout.flush()
626
627 mproc = subprocess.run(['magic', '-dnull', '-noconsole',
628 destlibdir + '/generate_magic.tcl'],
629 stdin = subprocess.DEVNULL,
630 stdout = subprocess.PIPE,
631 stderr = subprocess.PIPE, cwd = destlibdir,
632 universal_newlines = True)
633
634 if mproc.stdout:
635 for line in mproc.stdout.splitlines():
636 print(line)
637 if mproc.stderr:
638 print('Error message output from magic:')
639 for line in mproc.stderr.splitlines():
640 print(line)
641 if mproc.returncode != 0:
642 print('ERROR: Magic exited with status ' + str(mproc.returncode))
643 if do_compile_only == True:
644 print('Compile-only: Removing individual GDS files')
645 for gfile in glist:
646 if os.path.isfile(gfile):
647 os.remove(gfile)
648 if newname:
649 if os.path.isfile(newname):
650 os.remove(newname)
651 else:
652 print('Only one file (' + str(glist) + '); ignoring "compile" option.')
653
654#----------------------------------------------------------------------------
655# Given a destination directory holding individual SPICE netlists of a number
656# of cells, create a single SPICE library file named <alllibname> and place
657# it in the same directory. This is done for the option "compile" if specified
658# for the "-spice" install.
659#----------------------------------------------------------------------------
660
661def create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist):
662
663 fformat = 'CDL' if spiext == '.cdl' else 'SPICE'
664
665 allstubname = destlibdir + '/stub' + spiext
666 alllibname = destlibdir + '/' + destlib + spiext
667 if do_stub:
668 outputname = allstubname
669 else:
670 outputname = alllibname
671
672 print('Diagnostic: Creating consolidated ' + fformat + ' library ' + outputname)
673
674 if os.path.isfile(outputname):
675 os.remove(outputname)
676
677 if fformat == 'CDL':
678 slist = glob.glob(destlibdir + '/*.cdl')
679 else:
680 # Sadly, there is no consensus on what a SPICE file extension should be.
681 slist = glob.glob(destlibdir + '/*.spc')
682 slist.extend(glob.glob(destlibdir + '/*.spice'))
683 slist.extend(glob.glob(destlibdir + '/*.spi'))
684 slist.extend(glob.glob(destlibdir + '/*.ckt'))
685
686 if alllibname in slist:
687 slist.remove(alllibname)
688
689 if allstubname in slist:
690 slist.remove(allstubname)
691
692 # Create exclude list with glob-style matching using fnmatch
693 if len(slist) > 0:
694 slistnames = list(os.path.split(item)[1] for item in slist)
695 notslist = []
696 for exclude in excludelist:
697 notslist.extend(fnmatch.filter(slistnames, exclude))
698
699 # Apply exclude list
700 if len(notslist) > 0:
701 for file in slist[:]:
702 if os.path.split(file)[1] in notslist:
703 slist.remove(file)
704
705 if len(slist) > 1:
706 with open(outputname, 'w') as ofile:
707 allsubckts = []
708 for sfile in slist:
709 with open(sfile, 'r') as ifile:
710 # print('Adding ' + sfile + ' to library.')
711 stext = ifile.read()
712 subckts = re.findall(r'\.subckt[ \t]+([^ \t\n]+)', stext, flags=re.IGNORECASE)
713 sseen = list(item for item in subckts if item in allsubckts)
714 allsubckts.extend(list(item for item in subckts if item not in allsubckts))
715 sfilter = remove_redundant_subckts(stext, allsubckts, sseen)
716 print(sfilter, file=ofile)
717 print('\n******* EOF\n', file=ofile)
718
719 if do_compile_only == True:
720 print('Compile-only: Removing individual SPICE files')
721 for sfile in slist:
722 if os.path.isfile(sfile):
723 os.remove(sfile)
724 elif os.path.islink(sfile):
725 os.unlink(sfile)
726 else:
727 print('Only one file (' + str(slist) + '); ignoring "compile" option.')
728
729#----------------------------------------------------------------------------
730# Remove redundant subcircuit entries from a SPICE or CDL netlist file. "sseen"
731# is a list of subcircuit names gleaned from all previously read files using
732# re.findall(). "slist" is a list of subcircuits including those in "ntext".
733# If a subcircuit is defined outside of "ntext", then remove all occurrences in
734# "ntext". Otherwise, if a subcircuit is defined more than once in "ntext",
735# remove all but one copy. The reason for doing this is that some netlists will
736# include primitive device definitions used by all the standard cell subcircuits.
737#
738# It may be necessary to remove redundant .include statements and redundant .model
739# and/or .option statements as well.
740#----------------------------------------------------------------------------
741
742def remove_redundant_subckts(ntext, slist, sseen):
743 updated = ntext
744 for subckt in slist:
745 if subckt in sseen:
746 # Remove all occurrences of subckt
747 updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', updated, flags=re.IGNORECASE | re.DOTALL)
748
749 else:
750 # Determine the number of times the subcircuit appears in the text
751 n = len(re.findall(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', updated, flags=re.IGNORECASE | re.DOTALL))
752 # Optimization: Just keep original text if n < 2
753 if n < 2:
754 continue
755
756 # Remove all but one
757 updated = re.sub(r'\n\.subckt[ \t]+' + subckt + '[ \t\n]+.*\n\.ends[ \t\n]+', '\n', n - 1, updated, flags=re.IGNORECASE | re.DOTALL)
758 return updated
759
760#----------------------------------------------------------------------------
761# This is the main entry point for the foundry install script.
762#----------------------------------------------------------------------------
763
764if __name__ == '__main__':
765
766 if len(sys.argv) == 1:
767 print("No options given to foundry_install.py.")
768 usage()
769 sys.exit(0)
770
771 optionlist = []
772 newopt = []
773
774 sourcedir = None
775 targetdir = None
776
777 ef_format = False
778 do_clean = False
779
780 have_lef = False
781 have_techlef = False
782 have_lefanno = False
783 have_gds = False
784 have_spice = False
785 have_cdl = False
786 have_verilog = False
787 have_lib = False
788
789 # Break arguments into groups where the first word begins with "-".
790 # All following words not beginning with "-" are appended to the
791 # same list (optionlist). Then each optionlist is processed.
792 # Note that the first entry in optionlist has the '-' removed.
793
794 for option in sys.argv[1:]:
795 if option.find('-', 0) == 0:
796 if newopt != []:
797 optionlist.append(newopt)
798 newopt = []
799 newopt.append(option[1:])
800 else:
801 newopt.append(option)
802
803 if newopt != []:
804 optionlist.append(newopt)
805
806 # Pull library names from optionlist
807 libraries = []
808 for option in optionlist[:]:
809 if option[0] == 'library':
810 optionlist.remove(option)
811 libraries.append(option[1:])
812
813 # Check for option "ef_format" or "std_format" or "clean"
814 for option in optionlist[:]:
815 if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
816 optionlist.remove(option)
817 ef_format = True
818 elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
819 optionlist.remove(option)
820 ef_format = False
821 elif option[0] == 'clean':
822 do_clean = True
823
824 # Check for options "source" and "target"
825 for option in optionlist[:]:
826 if option[0] == 'source':
827 optionlist.remove(option)
828 sourcedir = option[1]
829 elif option[0] == 'target':
830 optionlist.remove(option)
831 targetdir = option[1]
832
833 if not targetdir:
834 print("No target directory specified. Exiting.")
835 sys.exit(1)
836
837 # Take the target PDK name from the target path last component
838 pdkname = os.path.split(targetdir)[1]
839
840 # If targetdir (the staging area) exists, make sure it's empty.
841
842 if os.path.isdir(targetdir):
843 # Error if targetdir exists but is not writeable
844 if not os.access(targetdir, os.W_OK):
845 print("Target installation directory " + targetdir + " is not writable.")
846 sys.exit(1)
847
848 # Clear out the staging directory if specified
849 if do_clean:
850 shutil.rmtree(targetdir)
851 elif os.path.exists(targetdir):
852 print("Target installation directory " + targetdir + " is not a directory.")
853 sys.exit(1)
854
855 # Error if no source or dest specified unless "-clean" was specified
856 if not sourcedir:
857 if do_clean:
858 print("Done removing staging area.")
859 sys.exit(0)
860 else:
861 print("No source directory specified. Exiting.")
862 sys.exit(1)
863
864 # Create the target directory
865 os.makedirs(targetdir, exist_ok=True)
866
867 #----------------------------------------------------------------
868 # Installation part 1: Install files into the staging directory
869 #----------------------------------------------------------------
870
871 # Diagnostic
872 print("Installing in target (staging) directory " + targetdir)
873
874 # Create the top-level directories
875
876 os.makedirs(targetdir + '/libs.tech', exist_ok=True)
877 os.makedirs(targetdir + '/libs.ref', exist_ok=True)
878
879 # Path to magic techfile depends on ef_format
880
881 if ef_format == True:
882 mag_current = '/libs.tech/magic/current/'
883 else:
884 mag_current = '/libs.tech/magic/'
885
886 # Check for magic version and set flag if it does not exist or if
887 # it has the wrong version.
888 have_mag_8_2 = False
889 try:
890 mproc = subprocess.run(['magic', '--version'],
891 stdout = subprocess.PIPE,
892 stderr = subprocess.PIPE,
893 universal_newlines = True)
894 if mproc.stdout:
895 mag_version = mproc.stdout.splitlines()[0]
896 mag_version_info = mag_version.split('.')
897 try:
898 if int(mag_version_info[0]) > 8:
899 have_mag_8_2 = True
900 elif int(mag_version_info[0]) == 8:
901 if int(mag_version_info[1]) >= 2:
902 have_mag_8_2 = True
903 print('Magic version 8.2 available on the system.')
904 except ValueError:
905 print('Error: "magic --version" did not return valid version number.')
906 except FileNotFoundError:
907 print('Error: Failed to find executable for magic in standard search path.')
908
909 if not have_mag_8_2:
910 print('WARNING: Magic version 8.2 cannot be executed from the standard executable search path.')
911 print('Please install or correct the search path.')
912 print('Magic database files will not be created, and other missing file formats may not be generated.')
913
914 # Populate any targets that do not specify a library, or where the library is
915 # specified as "primitive".
916
917 # Populate the techLEF and SPICE models, if specified. Also, this section can add
918 # to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
919 # install into libs.tech/ngspice/.
920
921 if libraries == [] or 'primitive' in libraries[0]:
922
923 for option in optionlist[:]:
924
925 # Legacy behavior is to put libs.tech models and techLEF files in
926 # the same grouping as files for the primdev library (which go in
927 # libs.ref). Current behavior is to put all libs.tech files in
928 # a grouping with no library, with unrestricted ability to write
929 # into any subdirectory of libs.tech/. Therefore, need to restrict
930 # legacy use to just 'techlef' and 'models'.
931
932 if len(libraries) > 0 and 'primitive' in libraries[0]:
933 if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
934 continue
935
936 # Normally technology LEF files are associated with IP libraries.
937 # However, if no library is specified or the library is 'primitive'
938 # (legacy behavior), then put in the techLEF directory with no subdirectory.
939
940 filter_scripts = []
941 if option[0] == 'techlef' or option[0] == 'techLEF':
942 for item in option:
943 if item.split('=')[0] == 'filter':
944 filter_scripts.append(item.split('=')[1])
945 break
946
947 if ef_format:
948 techlefdir = targetdir + '/libs.ref/' + 'techLEF'
949 else:
950 techlefdir = targetdir + '/libs.tech/lef'
951
952 os.makedirs(techlefdir, exist_ok=True)
953 # All techlef files should be copied, so use "glob" on the wildcards
954 techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
955
956 for lefname in techlist:
957 leffile = os.path.split(lefname)[1]
958 targname = techlefdir + '/' + leffile
959
960 if os.path.isfile(lefname):
961 shutil.copy(lefname, targname)
962 else:
963 shutil.copytree(lefname, targname)
964
965 for filter_script in filter_scripts:
966 # Apply filter script to all files in the target directory
967 tfilter(targname, filter_script)
968
969 optionlist.remove(option)
970
971 # All remaining options will refer to specific tools (e.g., -ngspice, -magic)
972 # although generic names (.e.g, -models) are acceptable if the tools know
973 # where to find the files. Currently, most tools have their own formats
974 # and standards for setup, and so generally each install directory will be
975 # unique to one EDA tool.
976
977 else:
978 filter_scripts = []
979 for item in option:
980 if item.split('=')[0] == 'filter':
981 filter_scripts.append(item.split('=')[1])
982 break
983
984 print('Diagnostic: installing ' + option[0] + '.')
985 tooldir = targetdir + '/libs.tech/' + option[0]
986 os.makedirs(tooldir, exist_ok=True)
987
988 # All files should be linked or copied, so use "glob" on
989 # the wildcards. Copy each file and recursively copy each
990 # directory.
991 toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
992
993 for toolname in toollist:
994 toolfile = os.path.split(toolname)[1]
995 targname = tooldir + '/' + toolfile
996
997 if os.path.isdir(toolname):
998 # Remove any existing directory, and its contents
999 if os.path.isdir(targname):
1000 shutil.rmtree(targname)
1001 os.makedirs(targname)
1002
1003 # Recursively find and copy or link the whole directory
1004 # tree from this point.
1005
1006 alltoollist = glob.glob(toolname + '/**', recursive=True)
1007 commonpart = os.path.commonpath(alltoollist)
1008 for subtoolname in alltoollist:
1009 if os.path.isdir(subtoolname):
1010 continue
1011 # Get the path part that is not common between toollist and
1012 # alltoollist.
1013 subpart = os.path.relpath(subtoolname, commonpart)
1014 subtargname = targname + '/' + subpart
1015 os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
1016
1017 if os.path.isfile(subtoolname):
1018 shutil.copy(subtoolname, subtargname)
1019 else:
1020 shutil.copytree(subtoolname, subtargname)
1021
1022 for filter_script in filter_scripts:
1023 # Apply filter script to all files in the target directory
1024 tfilter(subtargname, filter_script)
1025
1026 else:
1027 # Remove any existing file
1028 if os.path.isfile(targname):
1029 os.remove(targname)
1030 elif os.path.isdir(targname):
1031 shutil.rmtree(targname)
1032
1033 if os.path.isfile(toolname):
1034 shutil.copy(toolname, targname)
1035 else:
1036 shutil.copytree(toolname, targname)
1037
1038 for filter_script in filter_scripts:
1039 # Apply filter script to all files in the target directory
1040 tfilter(targname, filter_script)
1041
1042 optionlist.remove(option)
1043
1044 # Do an initial pass through all of the options and determine what is being
1045 # installed, so that we know in advance which file formats are missing and
1046 # need to be generated.
1047
1048 for option in optionlist[:]:
1049 if option[0] == 'lef':
1050 have_lef = True
1051 if option[0] == 'techlef' or option[0] == 'techLEF':
1052 have_techlef = True
1053 elif option[0] == 'gds':
1054 have_gds = True
1055 elif option[0] == 'spice' or option[0] == 'spi':
1056 have_spice = True
1057 elif option[0] == 'cdl':
1058 have_cdl = True
1059 elif option[0] == 'verilog':
1060 have_verilog = True
1061 elif option[0] == 'lib' or option[0] == 'liberty':
1062 have_lib = True
1063
1064 # The remaining options in optionlist should all be types like 'lef' or 'liberty'
1065 # and there should be a corresponding library list specified by '-library'
1066
1067 for option in optionlist[:]:
1068
1069 # Ignore if no library list---should have been taken care of above.
1070 if libraries == []:
1071 break
1072
1073 # Diagnostic
1074 print("Install option: " + str(option[0]))
1075
1076 # For ef_format: always make techlef -> techLEF and spice -> spi
1077
1078 if ef_format:
1079 if option[0] == 'techlef':
1080 option[0] = 'techLEF'
1081 elif option[0] == 'spice':
1082 option[0] = 'spi'
1083
1084 destdir = targetdir + '/libs.ref/' + option[0]
1085 os.makedirs(destdir, exist_ok=True)
1086
1087 # If the option is followed by the keyword "up" and a number, then
1088 # the source should be copied (or linked) from <number> levels up
1089 # in the hierarchy (see below).
1090
1091 if 'up' in option:
1092 uparg = option.index('up')
1093 try:
1094 hier_up = int(option[uparg + 1])
1095 except:
1096 print("Non-numeric option to 'up': " + option[uparg + 1])
1097 print("Ignoring 'up' option.")
1098 hier_up = 0
1099 else:
1100 hier_up = 0
1101
1102 filter_scripts = []
1103 for item in option:
1104 if item.split('=')[0] == 'filter':
1105 filter_scripts.append(item.split('=')[1])
1106 break
1107
1108 # Option 'stub' applies to netlists ('cdl' or 'spice') and generates
1109 # a file with only stub entries.
1110 do_stub = 'stub' in option
1111
1112 # Option 'compile' is a standalone keyword ('comp' may be used).
1113 do_compile = 'compile' in option or 'comp' in option
1114 do_compile_only = 'compile-only' in option or 'comp-only' in option
1115
1116 # Option 'nospecify' is a standalone keyword ('nospec' may be used).
1117 do_remove_spec = 'nospecify' in option or 'nospec' in option
1118
1119 # Option 'exclude' has an argument
1120 try:
1121 excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
1122 except IndexError:
1123 excludelist = []
1124 else:
1125 print('Excluding files: ' + (',').join(excludelist))
1126
1127 # Option 'rename' has an argument
1128 try:
1129 newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
1130 except IndexError:
1131 newname = None
1132 else:
1133 print('Renaming file to: ' + newname)
1134
1135 # 'anno' may be specified for LEF, in which case the LEF is used only
1136 # to annotate GDS and is not itself installed; this allows LEF to
1137 # be generated from Magic and avoids quirky use of obstruction layers.
1138 have_lefanno = True if 'annotate' in option or 'anno' in option else False
1139 if have_lefanno:
1140 if option[0] != 'lef':
1141 print("Warning: 'annotate' option specified outside of -lef. Ignoring.")
1142 else:
1143 # Mark as NOT having LEF since we want to use it only for annotation.
1144 have_lef = False
1145
1146 # For each library, create the library subdirectory
1147 for library in libraries:
1148 if len(library) == 3:
1149 destlib = library[2]
1150 else:
1151 destlib = library[1]
1152
1153 if ef_format:
1154 destlibdir = destdir + '/' + destlib
1155 else:
1156 destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
1157 destlibdir = destdir
1158
1159 os.makedirs(destlibdir, exist_ok=True)
1160
1161 # Populate the library subdirectory
1162 # Parse the option and replace each '/*/' with the library name,
1163 # and check if it is a valid directory name. Then glob the
1164 # resulting option name. Warning: This assumes that all
1165 # occurences of the text '/*/' match a library name. It should
1166 # be possible to wild-card the directory name in such a way that
1167 # this is always true.
1168
1169 testpath = substitute(sourcedir + '/' + option[1], library[1])
1170 liblist = glob.glob(testpath)
1171
1172 # Create a file "sources.txt" (or append to it if it exists)
1173 # and add the source directory name so that the staging install
1174 # script can know where the files came from.
1175
1176 with open(destlibdir + '/sources.txt', 'a') as ofile:
1177 print(testpath, file=ofile)
1178
1179 # Create exclude list with glob-style matching using fnmatch
1180 if len(liblist) > 0:
1181 liblistnames = list(os.path.split(item)[1] for item in liblist)
1182 notliblist = []
1183 for exclude in excludelist:
1184 notliblist.extend(fnmatch.filter(liblistnames, exclude))
1185
1186 # Apply exclude list
1187 if len(notliblist) > 0:
1188 for file in liblist[:]:
1189 if os.path.split(file)[1] in notliblist:
1190 liblist.remove(file)
1191
1192 if len(excludelist) > 0 and len(notliblist) == 0:
1193 print('Warning: Nothing from the exclude list found in sources.')
1194 print('excludelist = ' + str(excludelist))
1195 print('destlibdir = ' + destlibdir)
1196
1197 # Diagnostic
1198 print('Collecting files from ' + testpath)
1199 print('Files to install:')
1200 if len(liblist) < 10:
1201 for item in liblist:
1202 print(' ' + item)
1203 else:
1204 for item in liblist[0:4]:
1205 print(' ' + item)
1206 print(' .')
1207 print(' .')
1208 print(' .')
1209 for item in liblist[-6:-1]:
1210 print(' ' + item)
1211 print('(' + str(len(liblist)) + ' files total)')
1212
1213 for libname in liblist:
1214 # Note that there may be a hierarchy to the files in option[1],
1215 # say for liberty timing files under different conditions, so
1216 # make sure directories have been created as needed.
1217
1218 libfile = os.path.split(libname)[1]
1219 libfilepath = os.path.split(libname)[0]
1220 destpathcomp = []
1221 for i in range(hier_up):
1222 destpathcomp.append('/' + os.path.split(libfilepath)[1])
1223 libfilepath = os.path.split(libfilepath)[0]
1224 destpathcomp.reverse()
1225 destpath = ''.join(destpathcomp)
1226
1227 if newname:
1228 if len(liblist) == 1:
1229 destfile = newname
1230 else:
1231 if not do_compile and not do_compile_only:
1232 print('Error: rename specified but more than one file found!')
1233 destfile = libfile
1234 else:
1235 destfile = libfile
1236
1237 targname = destlibdir + destpath + '/' + destfile
1238
1239 # NOTE: When using "up" with link_from, could just make
1240 # destpath itself a symbolic link; this way is more flexible
1241 # but adds one symbolic link per file.
1242
1243 if destpath != '':
1244 if not os.path.isdir(destlibdir + destpath):
1245 os.makedirs(destlibdir + destpath, exist_ok=True)
1246
1247 # Remove any existing file
1248 if os.path.isfile(targname):
1249 os.remove(targname)
1250 elif os.path.isdir(targname):
1251 shutil.rmtree(targname)
1252
1253 # NOTE: Diagnostic, probably much too much output.
1254 print(' Install:' + libname + ' to ' + targname)
1255 if os.path.isfile(libname):
1256 shutil.copy(libname, targname)
1257 else:
1258 shutil.copytree(libname, targname)
1259
1260 # File filtering options: Two options 'stub' and 'nospec' are
1261 # handled by scripts in ../common/. Custom filters can also be
1262 # specified.
1263
1264 local_filter_scripts = filter_scripts[:]
1265
1266 if option[0] == 'verilog':
1267 # Internally handle syntactical issues with verilog and iverilog
1268 vfilter(targname)
1269
1270 if do_remove_spec:
1271 scriptdir = os.path.split(os.getcwd())[0] + '/common'
1272 local_filter_scripts.append(scriptdir + '/remove_specify.py')
1273
1274 elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
1275 if do_stub:
1276 scriptdir = os.path.split(os.getcwd())[0] + '/common'
1277 local_filter_scripts.append(scriptdir + '/makestub.py')
1278
1279 for filter_script in local_filter_scripts:
1280 # Apply filter script to all files in the target directory
1281 tfilter(targname, filter_script)
1282
1283 if do_compile == True or do_compile_only == True:
1284 # NOTE: The purpose of "rename" is to put a destlib-named
1285 # library elsewhere so that it can be merged with another
1286 # library into a compiled <destlib>.<ext>
1287
1288 compname = destlib
1289
1290 # To do: Make this compatible with linking from another PDK.
1291
1292 if option[0] == 'verilog':
1293 # If there is not a single file with all verilog cells in it,
1294 # then compile one, because one does not want to have to have
1295 # an include line for every single cell used in a design.
1296
1297 create_verilog_library(destlibdir, compname, do_compile_only, do_stub, excludelist)
1298
1299 elif option[0] == 'gds' and have_mag_8_2:
1300 # If there is not a single file with all GDS cells in it,
1301 # then compile one.
1302
1303 # Link to the PDK magic startup file from the target directory
1304 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1305 if not os.path.isfile(startup_script):
1306 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1307 create_gds_library(destlibdir, compname, startup_script, do_compile_only, excludelist)
1308
1309 elif option[0] == 'liberty' or option[0] == 'lib':
1310 # If there is not a single file with all liberty cells in it,
1311 # then compile one, because one does not want to have to have
1312 # an include line for every single cell used in a design.
1313
1314 create_lib_library(destlibdir, compname, do_compile_only, excludelist)
1315
1316 elif option[0] == 'spice' or option[0] == 'spi':
1317 # If there is not a single file with all SPICE subcircuits in it,
1318 # then compile one, because one does not want to have to have
1319 # an include line for every single cell used in a design.
1320
1321 spiext = '.spice' if not ef_format else '.spi'
1322 create_spice_library(destlibdir, compname, spiext, do_compile_only, do_stub, excludelist)
1323 if do_compile_only == True:
1324 if newname:
1325 if os.path.isfile(newname):
1326 os.remove(newname)
1327
1328 elif option[0] == 'cdl':
1329 # If there is not a single file with all CDL subcircuits in it,
1330 # then compile one, because one does not want to have to have
1331 # an include line for every single cell used in a design.
1332
1333 create_spice_library(destlibdir, compname, '.cdl', do_compile_only, do_stub, excludelist)
1334 if do_compile_only == True:
1335 if newname:
1336 if os.path.isfile(newname):
1337 os.remove(newname)
1338
1339 elif option[0] == 'lef':
1340 # If there is not a single file with all LEF cells in it,
1341 # then compile one, because one does not want to have to have
1342 # an include line for every single cell used in a design.
1343
1344 create_lef_library(destlibdir, compname, do_compile_only, excludelist)
1345
1346 # Find any libraries/options marked as "privileged" (or "private") and
1347 # move the files from libs.tech or libs.ref to libs.priv, leaving a
1348 # symbolic link in the original location. Do this during the initial
1349 # install so that options following in the list can add files to the
1350 # non-privileged equivalent directory path.
1351
1352 if 'priv' in option or 'privileged' in option or 'private' in option:
1353
1354 # Diagnostic
1355 print("Install option: " + str(option[0]))
1356
1357 if ef_format == True:
1358 os.makedirs(targetdir + '/libs.priv', exist_ok=True)
1359
1360 for library in libraries:
1361 if len(library) == 3:
1362 destlib = library[2]
1363 else:
1364 destlib = library[1]
1365
1366 if ef_format:
1367 srclibdir = targetdir + '/libs.ref/' + option[0] + '/' + destlib
1368 destlibdir = targetdir + '/libs.priv/' + option[0] + '/' + destlib
1369 else:
1370 srclibdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
1371 destlibdir = targetdir + '/libs.priv/' + destlib + '/' + option[0]
1372
1373 if not os.path.exists(destlibdir):
1374 os.makedirs(destlibdir)
1375
1376 print('Moving files in ' + srclibdir + ' to privileged space.')
1377 filelist = os.listdir(srclibdir)
1378 for file in filelist:
1379 srcfile = srclibdir + '/' + file
1380 destfile = destlibdir + '/' + file
1381 if os.path.isfile(destfile):
1382 os.remove(destfile)
1383 elif os.path.isdir(destfile):
1384 shutil.rmtree(destfile)
1385
1386 if os.path.isfile(srcfile):
1387 shutil.copy(srcfile, destfile)
1388 os.remove(srcfile)
1389 else:
1390 shutil.copytree(srcfile, destfile)
1391 shutil.rmtree(srcfile)
1392
1393 print("Completed installation of vendor files.")
1394
1395 #----------------------------------------------------------------
1396 # Installation part 2: Generate derived file formats
1397 #----------------------------------------------------------------
1398
1399 # Now for the harder part. If GDS and/or LEF databases were specified,
1400 # then migrate them to magic (.mag files in layout/ or abstract/).
1401
1402 ignorelist = []
1403 do_cdl_scaleu = False
1404 no_cdl_convert = False
1405 no_gds_convert = False
1406 no_lef_convert = False
1407 cdl_compile_only = False
1408
1409 cdl_exclude = []
1410 lef_exclude = []
1411 gds_exclude = []
1412 spice_exclude = []
1413 verilog_exclude = []
1414
1415 cdl_reflib = '/libs.ref/'
1416 gds_reflib = '/libs.ref/'
1417 lef_reflib = '/libs.ref/'
1418
1419 for option in optionlist[:]:
1420 if option[0] == 'cdl':
1421 # Option 'scaleu' is a standalone keyword
1422 do_cdl_scaleu = 'scaleu' in option
1423
1424 # Option 'ignore' has arguments after '='
1425 for item in option:
1426 if item.split('=')[0] == 'ignore':
1427 ignorelist = item.split('=')[1].split(',')
1428
1429 # Option 'noconvert' is a standalone keyword.
1430 if 'noconvert' in option:
1431 if option[0] == 'cdl':
1432 no_cdl_convert = True
1433 elif option[0] == 'gds':
1434 no_gds_convert = True
1435 elif option[0] == 'lef':
1436 no_lef_convert = True
1437
1438 # Option 'privileged' is a standalone keyword.
1439 if 'priv' in option or 'privileged' in option or 'private' in option:
1440 if option[0] == 'cdl':
1441 cdl_reflib = '/libs.priv/'
1442 elif option[0] == 'gds':
1443 gds_reflib = '/libs.priv/'
1444 elif option[0] == 'lef':
1445 lef_reflib = '/libs.priv/'
1446
1447 # If CDL is marked 'compile-only' then CDL should only convert the
1448 # compiled file to SPICE if conversion is needed.
1449 if 'compile-only' in option:
1450 if option[0] == 'cdl':
1451 cdl_compile_only = True
1452
1453 # Find exclude list for any option
1454 for item in option:
1455 if item.split('=')[0] == 'exclude':
1456 exclude_list = item.split('=')[1].split(',')
1457 if option[0] == 'cdl':
1458 cdl_exclude = exclude_list
1459 elif option[0] == 'lef':
1460 lef_exclude = exclude_list
1461 elif option[0] == 'gds':
1462 gds_exclude = exclude_list
1463 elif option[0] == 'spi' or option[0] == 'spice':
1464 spice_exclude = exclude_list
1465 elif option[0] == 'verilog':
1466 verilog_exclude = exclude_list
1467
1468 devlist = []
1469 pdklibrary = None
1470
1471 if have_gds and not no_gds_convert:
1472 print("Migrating GDS files to layout.")
1473
1474 if ef_format:
1475 destdir = targetdir + gds_reflib + 'mag'
1476 srcdir = targetdir + gds_reflib + 'gds'
1477 vdir = targetdir + '/libs.ref/' + 'verilog'
1478 cdir = targetdir + cdl_reflib + 'cdl'
1479 sdir = targetdir + cdl_reflib + 'spi'
1480
1481 os.makedirs(destdir, exist_ok=True)
1482
1483 # For each library, create the library subdirectory
1484 for library in libraries:
1485 if len(library) == 3:
1486 destlib = library[2]
1487 else:
1488 destlib = library[1]
1489
1490 if ef_format:
1491 destlibdir = destdir + '/' + destlib
1492 srclibdir = srcdir + '/' + destlib
1493 vlibdir = vdir + '/' + destlib
1494 clibdir = cdir + '/' + destlib
1495 slibdir = sdir + '/' + destlib
1496 else:
1497 destdir = targetdir + gds_reflib + destlib + '/mag'
1498 srcdir = targetdir + gds_reflib + destlib + '/gds'
1499 vdir = targetdir + '/libs.ref/' + destlib + '/verilog'
1500 cdir = targetdir + cdl_reflib + destlib + '/cdl'
1501 sdir = targetdir + cdl_reflib + destlib + '/spice'
1502 destlibdir = destdir
1503 srclibdir = srcdir
1504 vlibdir = vdir
1505 clibdir = cdir
1506 slibdir = sdir
1507
1508 os.makedirs(destlibdir, exist_ok=True)
1509
1510 # For primitive devices, check the PDK script and find the name
1511 # of the library and get a list of supported devices.
1512
1513 if library[0] == 'primitive':
1514 pdkscript = targetdir + mag_current + pdkname + '.tcl'
1515 print('Searching for supported devices in PDK script ' + pdkscript + '.')
1516
1517 if os.path.isfile(pdkscript):
1518 librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
1519 devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
1520 fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
1521 devlist = []
1522 fixedlist = []
1523 with open(pdkscript, 'r') as ifile:
1524 scripttext = ifile.read().splitlines()
1525 for line in scripttext:
1526 lmatch = librex.match(line)
1527 if lmatch:
1528 pdklibrary = lmatch.group(1)
1529 dmatch = devrex.match(line)
1530 if dmatch:
1531 if dmatch.group(1) == pdklibrary:
1532 devlist.append(dmatch.group(2))
1533 fmatch = fixrex.match(line)
1534 if fmatch:
1535 if fmatch.group(1) == pdklibrary:
1536 fixedlist.append(fmatch.group(2))
1537
1538 # Diagnostic
1539 print("PDK library is " + str(pdklibrary))
1540
1541 # Link to the PDK magic startup file from the target directory
1542 # If there is no -F version then look for one without -F (open source PDK)
1543 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1544 if not os.path.isfile(startup_script):
1545 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1546
1547 if have_mag_8_2 and os.path.isfile(startup_script):
1548 # If the symbolic link exists, remove it.
1549 if os.path.isfile(destlibdir + '/.magicrc'):
1550 os.remove(destlibdir + '/.magicrc')
1551 os.symlink(startup_script, destlibdir + '/.magicrc')
1552
1553 # Find GDS file names in the source
1554 print('Getting GDS file list from ' + srclibdir + '.')
1555 gdsfilesraw = os.listdir(srclibdir)
1556 gdsfiles = []
1557 for gdsfile in gdsfilesraw:
1558 gdsext = os.path.splitext(gdsfile)[1].lower()
1559 if gdsext == '.gds' or gdsext == '.gdsii' or gdsext == '.gds2':
1560 gdsfiles.append(gdsfile)
1561
1562 # Create exclude list with glob-style matching using fnmatch
1563 if len(gdsfiles) > 0:
1564 gdsnames = list(os.path.split(item)[1] for item in gdsfiles)
1565 notgdsnames = []
1566 for exclude in gds_exclude:
1567 notgdsnames.extend(fnmatch.filter(gdsnames, exclude))
1568
1569 # Apply exclude list
1570 if len(notgdsnames) > 0:
1571 for file in gdsfiles[:]:
1572 if os.path.split(file)[1] in notgdsnames:
1573 gdsfiles.remove(file)
1574
1575 # Generate a script called "generate_magic.tcl" and leave it in
1576 # the target directory. Use it as input to magic to create the
1577 # .mag files from the database.
1578
1579 print('Creating magic generation script to generate magic database files.')
1580
1581 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1582 print('#!/usr/bin/env wish', file=ofile)
1583 print('#--------------------------------------------', file=ofile)
1584 print('# Script to generate .mag files from .gds ', file=ofile)
1585 print('#--------------------------------------------', file=ofile)
1586 print('gds readonly true', file=ofile)
1587 print('gds flatten true', file=ofile)
1588 print('gds rescale false', file=ofile)
1589 print('tech unlock *', file=ofile)
1590
1591 for gdsfile in gdsfiles:
1592 # Note: DO NOT use a relative path here.
1593 print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
1594
1595 # Make sure properties include the Tcl generated cell
1596 # information from the PDK script
1597
1598 if pdklibrary:
1599 tclfixedlist = '{' + ' '.join(fixedlist) + '}'
1600 print('set devlist ' + tclfixedlist, file=ofile)
1601 print('set topcell [lindex [cellname list top] 0]',
1602 file=ofile)
1603
1604 print('foreach cellname $devlist {', file=ofile)
1605 print(' load $cellname', file=ofile)
1606 print(' property gencell $cellname', file=ofile)
1607 print(' property parameter m=1', file=ofile)
1608 print(' property library ' + pdklibrary, file=ofile)
1609 print('}', file=ofile)
1610 print('load $topcell', file=ofile)
1611
1612 print('cellname delete \(UNNAMED\)', file=ofile)
1613 print('writeall force', file=ofile)
1614
1615 leffiles = []
1616 lefmacros = []
1617 if have_lefanno:
1618 # Find LEF file names in the source
1619 if ef_format:
1620 lefsrcdir = targetdir + lef_reflib + 'lefanno'
1621 lefsrclibdir = lefsrcdir + '/' + destlib
1622 else:
1623 lefsrcdir = targetdir + lef_reflib + destlib + '/lefanno'
1624 lefsrclibdir = lefsrcdir
1625
1626 leffiles = os.listdir(lefsrclibdir)
1627 leffiles = list(item for item in leffiles if os.path.splitext(item)[1] == '.lef')
1628 # Get list of abstract views to make from LEF macros
1629 for leffile in leffiles:
1630 with open(leffile, 'r') as ifile:
1631 ltext = ifile.read()
1632 llines = ltext.splitlines()
1633 for lline in llines:
1634 ltok = re.split(' |\t|\(', lline)
1635 if ltok[0] == 'MACRO':
1636 lefmacros.append(ltok[1])
1637
1638 # Create exclude list with glob-style matching using fnmatch
1639 if len(lefmacros) > 0:
1640 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1641 notlefnames = []
1642 for exclude in lef_exclude:
1643 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1644
1645 # Apply exclude list
1646 if len(notlefnames) > 0:
1647 for file in lefmacros[:]:
1648 if os.path.split(file)[1] in notlefnames:
1649 lefmacros.remove(file)
1650
1651 elif have_verilog and os.path.isdir(vlibdir):
1652 # Get list of abstract views to make from verilog modules
1653 vfiles = os.listdir(vlibdir)
1654 vfiles = list(item for item in vfiles if os.path.splitext(item)[1] == '.v')
1655 for vfile in vfiles:
1656 with open(vlibdir + '/' + vfile, 'r') as ifile:
1657 vtext = ifile.read()
1658 vlines = vtext.splitlines()
1659 for vline in vlines:
1660 vtok = re.split(' |\t|\(', vline)
1661 try:
1662 if vtok[0] == 'module':
1663 if vtok[1] not in lefmacros:
1664 lefmacros.append(vtok[1])
1665 except:
1666 pass
1667
1668 # Create exclude list with glob-style matching using fnmatch
1669 if len(lefmacros) > 0:
1670 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1671 notlefnames = []
1672 for exclude in verilog_exclude:
1673 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1674
1675 # Apply exclude list
1676 if len(notlefnames) > 0:
1677 for file in lefmacros[:]:
1678 if os.path.split(file)[1] in notlefnames:
1679 lefmacros.remove(file)
1680
1681 elif have_cdl and os.path.isdir(clibdir):
1682 # Get list of abstract views to make from CDL subcircuits
1683 cfiles = os.listdir(clibdir)
1684 cfiles = list(item for item in cfiles if os.path.splitext(item)[1] == '.cdl')
1685 for cfile in cfiles:
1686 with open(clibdir + '/' + cfile, 'r') as ifile:
1687 ctext = ifile.read()
1688 clines = ctext.splitlines()
1689 for cline in clines:
1690 ctok = cline.split()
1691 try:
1692 if ctok[0].lower() == '.subckt':
1693 if ctok[1] not in lefmacros:
1694 lefmacros.append(ctok[1])
1695 except:
1696 pass
1697
1698 # Create exclude list with glob-style matching using fnmatch
1699 if len(lefmacros) > 0:
1700 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1701 notlefnames = []
1702 for exclude in cdl_exclude:
1703 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1704
1705 # Apply exclude list
1706 if len(notlefnames) > 0:
1707 for file in lefmacros[:]:
1708 if os.path.split(file)[1] in notlefnames:
1709 lefmacros.remove(file)
1710
1711 elif have_spice and os.path.isdir(slibdir):
1712 # Get list of abstract views to make from SPICE subcircuits
1713 sfiles = os.listdir(slibdir)
1714 sfiles = list(item for item in sfiles)
1715 for sfile in sfiles:
1716 with open(slibdir + '/' + sfile, 'r') as ifile:
1717 stext = ifile.read()
1718 slines = stext.splitlines()
1719 for sline in slines:
1720 stok = sline.split()
1721 try:
1722 if stok[0].lower() == '.subckt':
1723 if stok[1] not in lefmacros:
1724 lefmacros.append(stok[1])
1725 except:
1726 pass
1727
1728 # Create exclude list with glob-style matching using fnmatch
1729 if len(lefmacros) > 0:
1730 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1731 notlefnames = []
1732 for exclude in spice_exclude:
1733 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1734
1735 # Apply exclude list
1736 if len(notlefnames) > 0:
1737 for file in lefmacros[:]:
1738 if os.path.split(file)[1] in notlefnames:
1739 lefmacros.remove(file)
1740
1741 if not lefmacros:
1742 print('No source for abstract views: Abstract views not made.')
1743 elif not have_lef:
1744 # This library has a GDS database but no LEF database. Use
1745 # magic to create abstract views of the GDS cells. If
1746 # option "annotate" is given, then read the LEF file after
1747 # loading the database file to annotate the cell with
1748 # information from the LEF file. This usually indicates
1749 # that the LEF file has some weird definition of obstruction
1750 # layers and we want to normalize them by using magic's LEF
1751 # write procedure, but we still need the pin use and class
1752 # information from the LEF file, and maybe the bounding box.
1753
1754 for leffile in leffiles:
1755 if have_lefanno:
1756 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
1757 for lefmacro in lefmacros:
1758 print('if {[cellname list exists ' + lefmacro + '] != 0} {', file=ofile)
1759 print(' load ' + lefmacro, file=ofile)
1760 print(' lef write ' + lefmacro + ' -hide', file=ofile)
1761 print('}', file=ofile)
1762 print('puts stdout "Done."', file=ofile)
1763 print('quit -noprompt', file=ofile)
1764
1765 print('Running magic to create magic database files.')
1766 sys.stdout.flush()
1767
1768 # Run magic to read in the GDS file and write out magic databases.
1769 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1770 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1771 stdin = ifile, stdout = subprocess.PIPE,
1772 stderr = subprocess.PIPE, cwd = destlibdir,
1773 universal_newlines = True)
1774 if mproc.stdout:
1775 for line in mproc.stdout.splitlines():
1776 print(line)
1777 if mproc.stderr:
1778 print('Error message output from magic:')
1779 for line in mproc.stderr.splitlines():
1780 print(line)
1781 if mproc.returncode != 0:
1782 print('ERROR: Magic exited with status ' + str(mproc.returncode))
1783
1784 if not have_lef:
1785 print('No LEF file install; need to generate LEF.')
1786 # Remove the lefanno/ target and its contents.
1787 if have_lefanno:
1788 if ef_format:
1789 lefannosrcdir = targetdir + lef_reflib + 'lefanno'
1790 else:
1791 lefannosrcdir = targetdir + lef_reflib + destlib + '/lefanno'
1792 if os.path.isdir(lefannosrcdir):
1793 shutil.rmtree(lefannosrcdir)
1794
1795 if ef_format:
1796 destlefdir = targetdir + lef_reflib + 'lef'
1797 destleflibdir = destlefdir + '/' + destlib
1798 else:
1799 destlefdir = targetdir + lef_reflib + destlib + '/lef'
1800 destleflibdir = destlefdir
1801
1802 os.makedirs(destleflibdir, exist_ok=True)
1803 leflist = os.listdir(destlibdir)
1804 leflist = list(item for item in leflist if os.path.splitext(item)[1] == '.lef')
1805
1806 # All macros will go into one file
1807 destleflib = destleflibdir + '/' + destlib + '.lef'
1808 # Remove any existing library file from the target directory
1809 if os.path.isfile(destleflib):
1810 print('Removing existing library ' + destleflib)
1811 os.remove(destleflib)
1812
1813 first = True
1814 with open(destleflib, 'w') as ofile:
1815 for leffile in leflist:
1816 # Remove any existing single file from the target directory
1817 if os.path.isfile(destleflibdir + '/' + leffile):
1818 print('Removing ' + destleflibdir + '/' + leffile)
1819 os.remove(destleflibdir + '/' + leffile)
1820
1821 # Append contents
1822 sourcelef = destlibdir + '/' + leffile
1823 with open(sourcelef, 'r') as ifile:
1824 leflines = ifile.read().splitlines()
1825 if not first:
1826 # Remove header from all but the first file
1827 leflines = leflines[8:]
1828 else:
1829 first = False
1830
1831 for line in leflines:
1832 print(line, file=ofile)
1833
1834 # Remove file from the source directory
1835 print('Removing source file ' + sourcelef)
1836 os.remove(sourcelef)
1837
1838 # Set have_lef now that LEF files were made, so they
1839 # can be used to generate the maglef/ databases.
1840 have_lef = True
1841
1842 elif not have_mag_8_2:
1843 print('The installer is not able to run magic.')
1844 else:
1845 print("Master PDK magic startup file not found. Did you install")
1846 print("PDK tech files before PDK vendor files?")
1847
1848 if have_lef and not no_lef_convert:
1849 print("Migrating LEF files to layout.")
1850 if ef_format:
1851 destdir = targetdir + '/libs.ref/' + 'maglef'
1852 srcdir = targetdir + lef_reflib + 'lef'
1853 magdir = targetdir + gds_reflib + 'mag'
1854 cdldir = targetdir + cdl_reflib + 'cdl'
1855 os.makedirs(destdir, exist_ok=True)
1856
1857 # For each library, create the library subdirectory
1858 for library in libraries:
1859 if len(library) == 3:
1860 destlib = library[2]
1861 else:
1862 destlib = library[1]
1863
1864 if ef_format:
1865 destlibdir = destdir + '/' + destlib
1866 srclibdir = srcdir + '/' + destlib
1867 maglibdir = magdir + '/' + destlib
1868 cdllibdir = cdldir + '/' + destlib
1869 else:
1870 destdir = targetdir + '/libs.ref/' + destlib + '/maglef'
1871 srcdir = targetdir + lef_reflib + destlib + '/lef'
1872 magdir = targetdir + gds_reflib + destlib + '/mag'
1873 cdldir = targetdir + cdl_reflib + destlib + '/cdl'
1874
1875 destlibdir = destdir
1876 srclibdir = srcdir
1877 maglibdir = magdir
1878 cdllibdir = cdldir
1879
1880 os.makedirs(destlibdir, exist_ok=True)
1881
1882 # Link to the PDK magic startup file from the target directory
1883 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
1884 if not os.path.isfile(startup_script):
1885 startup_script = targetdir + mag_current + pdkname + '.magicrc'
1886
1887 if have_mag_8_2 and os.path.isfile(startup_script):
1888 # If the symbolic link exists, remove it.
1889 if os.path.isfile(destlibdir + '/.magicrc'):
1890 os.remove(destlibdir + '/.magicrc')
1891 os.symlink(startup_script, destlibdir + '/.magicrc')
1892
1893 # Find LEF file names in the source
1894 leffiles = os.listdir(srclibdir)
1895 leffiles = list(item for item in leffiles if os.path.splitext(item)[1].lower() == '.lef')
1896
1897 # Get list of abstract views to make from LEF macros
1898 lefmacros = []
1899 err_no_macros = False
1900 for leffile in leffiles:
1901 with open(srclibdir + '/' + leffile, 'r') as ifile:
1902 ltext = ifile.read()
1903 llines = ltext.splitlines()
1904 for lline in llines:
1905 ltok = re.split(' |\t|\(', lline)
1906 if ltok[0] == 'MACRO':
1907 lefmacros.append(ltok[1])
1908
1909 # Create exclude list with glob-style matching using fnmatch
1910 if len(lefmacros) > 0:
1911 lefnames = list(os.path.split(item)[1] for item in lefmacros)
1912 notlefnames = []
1913 for exclude in lef_exclude:
1914 notlefnames.extend(fnmatch.filter(lefnames, exclude))
1915
1916 # Apply exclude list
1917 if len(notlefnames) > 0:
1918 for file in lefmacros[:]:
1919 if os.path.split(file)[1] in notlefnames:
1920 lefmacros.remove(file)
1921
1922 if len(leffiles) == 0:
1923 print('Warning: No LEF files found in ' + srclibdir)
1924 continue
1925
1926 print('Generating conversion script to create magic databases from LEF')
1927
1928 # Generate a script called "generate_magic.tcl" and leave it in
1929 # the target directory. Use it as input to magic to create the
1930 # .mag files from the database.
1931
1932 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
1933 print('#!/usr/bin/env wish', file=ofile)
1934 print('#--------------------------------------------', file=ofile)
1935 print('# Script to generate .mag files from .lef ', file=ofile)
1936 print('#--------------------------------------------', file=ofile)
1937 print('tech unlock *', file=ofile)
1938
1939 # If there are devices in the LEF file that come from the
1940 # PDK library, then copy this list into the script.
1941
1942 if pdklibrary:
1943 shortdevlist = []
1944 for macro in lefmacros:
1945 if macro in devlist:
1946 shortdevlist.append(macro)
1947
1948 tcldevlist = '{' + ' '.join(shortdevlist) + '}'
1949 print('set devlist ' + tcldevlist, file=ofile)
1950
1951 for leffile in leffiles:
1952 print('lef read ' + srclibdir + '/' + leffile, file=ofile)
1953
1954 for lefmacro in lefmacros:
1955
1956 # To be completed: Parse SPICE file for port order, make
1957 # sure ports are present and ordered.
1958
1959 if pdklibrary and lefmacro in shortdevlist:
1960 print('set cellname ' + lefmacro, file=ofile)
1961 print('if {[lsearch $devlist $cellname] >= 0} {',
1962 file=ofile)
1963 print(' load $cellname', file=ofile)
1964 print(' property gencell $cellname', file=ofile)
1965 print(' property parameter m=1', file=ofile)
1966 print(' property library ' + pdklibrary, file=ofile)
1967 print('}', file=ofile)
1968
1969 # Load one of the LEF files so that the default (UNNAMED) cell
1970 # is not loaded, then delete (UNNAMED) so it doesn't generate
1971 # an error message.
1972 if len(lefmacros) > 0:
1973 print('load ' + lefmacros[0], file=ofile)
1974 print('cellname delete \(UNNAMED\)', file=ofile)
1975 else:
1976 err_no_macros = True
1977 print('writeall force', file=ofile)
1978 print('puts stdout "Done."', file=ofile)
1979 print('quit -noprompt', file=ofile)
1980
1981 if err_no_macros == True:
1982 print('Warning: No LEF macros were defined.')
1983
1984 print('Running magic to create magic databases from LEF')
1985 sys.stdout.flush()
1986
1987 # Run magic to read in the LEF file and write out magic databases.
1988 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1989 mproc = subprocess.run(['magic', '-dnull', '-noconsole'],
1990 stdin = ifile, stdout = subprocess.PIPE,
1991 stderr = subprocess.PIPE, cwd = destlibdir,
1992 universal_newlines = True)
1993 if mproc.stdout:
1994 for line in mproc.stdout.splitlines():
1995 print(line)
1996 if mproc.stderr:
1997 print('Error message output from magic:')
1998 for line in mproc.stderr.splitlines():
1999 print(line)
2000 if mproc.returncode != 0:
2001 print('ERROR: Magic exited with status ' + str(mproc.returncode))
2002
2003
2004 # Now list all the .mag files generated, and for each, read the
2005 # corresponding file from the mag/ directory, pull the GDS file
2006 # properties, and add those properties to the maglef view. Also
2007 # read the CDL (or SPICE) netlist, read the ports, and rewrite
2008 # the port order in the mag and maglef file accordingly.
2009
2010 # Diagnostic
2011 print('Annotating files in ' + destlibdir)
2012 sys.stdout.flush()
2013 magfiles = os.listdir(destlibdir)
2014 magfiles = list(item for item in magfiles if os.path.splitext(item)[1] == '.mag')
2015 for magroot in magfiles:
2016 magname = os.path.splitext(magroot)[0]
2017 magfile = maglibdir + '/' + magroot
2018 magleffile = destlibdir + '/' + magroot
2019 prop_lines = get_gds_properties(magfile)
2020
2021 # Make sure properties include the Tcl generated cell
2022 # information from the PDK script
2023
2024 prop_gencell = []
2025 if pdklibrary:
2026 if magname in fixedlist:
2027 prop_gencell.append('gencell ' + magname)
2028 prop_gencell.append('library ' + pdklibrary)
2029 prop_gencell.append('parameter m=1')
2030
2031 nprops = len(prop_lines) + len(prop_gencell)
2032
2033 cdlfile = cdllibdir + '/' + magname + '.cdl'
2034 if os.path.exists(cdlfile):
2035 cdlfiles = [cdlfile]
2036 else:
2037 # Assume there is at least one file with all cell subcircuits
2038 # in it.
2039 try:
2040 cdlfiles = glob.glob(cdllibdir + '/*.cdl')
2041 except:
2042 pass
2043 if len(cdlfiles) > 0:
2044 for cdlfile in cdlfiles:
2045 port_dict = get_subckt_ports(cdlfile, magname)
2046 if port_dict != {}:
2047 break
2048 else:
2049 port_dict = {}
2050
2051 if port_dict == {}:
2052 print('No CDL file contains ' + destlib + ' device ' + magname)
2053 cdlfile = None
2054 # To be done: If destlib is 'primitive', then look in
2055 # SPICE models for port order.
2056 if destlib == 'primitive':
2057 print('Fix me: Need to look in SPICE models!')
2058
2059 proprex = re.compile('<< properties >>')
2060 endrex = re.compile('<< end >>')
2061 rlabrex = re.compile('rlabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
2062 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]+)')
2063 portrex = re.compile('port[ \t]+([^ \t]+)[ \t]+(.*)')
2064 gcellrex = re.compile('string gencell')
2065 portnum = -1
2066
2067 with open(magleffile, 'r') as ifile:
2068 magtext = ifile.read().splitlines()
2069
2070 with open(magleffile, 'w') as ofile:
2071 has_props = False
2072 is_gencell = False
2073 for line in magtext:
2074 tmatch = portrex.match(line)
2075 if tmatch:
2076 if portnum >= 0:
2077 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
2078 else:
2079 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
2080 ematch = endrex.match(line)
2081 if ematch and nprops > 0:
2082 if not has_props:
2083 print('<< properties >>', file=ofile)
2084 if not is_gencell:
2085 for prop in prop_gencell:
2086 print('string ' + prop, file=ofile)
2087 for prop in prop_lines:
2088 print('string ' + prop, file=ofile)
2089
2090 print(line, file=ofile)
2091 pmatch = proprex.match(line)
2092 if pmatch:
2093 has_props = True
2094
2095 gmatch = gcellrex.match(line)
2096 if gmatch:
2097 is_gencell = True
2098
2099 lmatch = flabrex.match(line)
2100 if not lmatch:
2101 lmatch = rlabrex.match(line)
2102 if lmatch:
2103 labname = lmatch.group(1).lower()
2104 try:
2105 portnum = port_dict[labname]
2106 except:
2107 portnum = -1
2108
2109 if os.path.exists(magfile):
2110 with open(magfile, 'r') as ifile:
2111 magtext = ifile.read().splitlines()
2112
2113 with open(magfile, 'w') as ofile:
2114 for line in magtext:
2115 tmatch = portrex.match(line)
2116 if tmatch:
2117 if portnum >= 0:
2118 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
2119 else:
2120 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
2121 ematch = endrex.match(line)
2122 print(line, file=ofile)
2123 lmatch = flabrex.match(line)
2124 if not lmatch:
2125 lmatch = rlabrex.match(line)
2126 if lmatch:
2127 labname = lmatch.group(1).lower()
2128 try:
2129 portnum = port_dict[labname]
2130 except:
2131 portnum = -1
2132 elif os.path.splitext(magfile)[1] == '.mag':
2133 # NOTE: Possibly this means the GDS cell has a different name.
2134 print('Error: No file ' + magfile + '. Why is it in maglef???')
2135
2136 elif not have_mag_8_2:
2137 print('The installer is not able to run magic.')
2138 else:
2139 print("Master PDK magic startup file not found. Did you install")
2140 print("PDK tech files before PDK vendor files?")
2141
2142 # If SPICE or CDL databases were specified, then convert them to
2143 # a form that can be used by ngspice, using the cdl2spi.py script
2144
2145 if have_spice:
2146 if ef_format:
2147 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
2148 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
2149
2150 elif have_cdl and not no_cdl_convert:
2151 if ef_format:
2152 if not os.path.isdir(targetdir + cdl_reflib + 'spi'):
2153 os.makedirs(targetdir + cdl_reflib + 'spi', exist_ok=True)
2154
2155 print("Migrating CDL netlists to SPICE.")
2156 sys.stdout.flush()
2157
2158 if ef_format:
2159 destdir = targetdir + cdl_reflib + 'spi'
2160 srcdir = targetdir + cdl_reflib + 'cdl'
2161 os.makedirs(destdir, exist_ok=True)
2162
2163 # For each library, create the library subdirectory
2164 for library in libraries:
2165 if len(library) == 3:
2166 destlib = library[2]
2167 else:
2168 destlib = library[1]
2169
2170 if ef_format:
2171 destlibdir = destdir + '/' + destlib
2172 srclibdir = srcdir + '/' + destlib
2173 else:
2174 destdir = targetdir + cdl_reflib + destlib + '/spice'
2175 srcdir = targetdir + cdl_reflib + destlib + '/cdl'
2176
2177 destlibdir = destdir
2178 srclibdir = srcdir
2179
2180 os.makedirs(destlibdir, exist_ok=True)
2181
2182 # Find CDL file names in the source
2183 # If CDL is marked compile-only then ONLY convert <distdir>.cdl
2184 if cdl_compile_only:
2185 alllibname = destlibdir + '/' + destlib + '.cdl'
2186 if not os.path.exists(alllibname):
2187 cdl_compile_only = False
2188 else:
2189 cdlfiles = [alllibname]
2190
2191 if not cdl_compile_only:
2192 cdlfiles = os.listdir(srclibdir)
2193 cdlfiles = list(item for item in cdlfiles if os.path.splitext(item)[1].lower() == '.cdl')
2194
2195 # The directory with scripts should be in ../common with respect
2196 # to the Makefile that determines the cwd.
2197 scriptdir = os.path.split(os.getcwd())[0] + '/common'
2198
2199 # Run cdl2spi.py script to read in the CDL file and write out SPICE
2200 for cdlfile in cdlfiles:
2201 if ef_format:
2202 spiname = os.path.splitext(cdlfile)[0] + '.spi'
2203 else:
2204 spiname = os.path.splitext(cdlfile)[0] + '.spice'
2205 procopts = [scriptdir + '/cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
2206 if do_cdl_scaleu:
2207 procopts.append('-dscale=u')
2208 for item in ignorelist:
2209 procopts.append('-ignore=' + item)
2210
2211 print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
2212 pproc = subprocess.run(procopts,
2213 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
2214 stderr = subprocess.PIPE, cwd = destlibdir,
2215 universal_newlines = True)
2216 if pproc.stdout:
2217 for line in pproc.stdout.splitlines():
2218 print(line)
2219 if pproc.stderr:
2220 print('Error message output from cdl2spi.py:')
2221 for line in pproc.stderr.splitlines():
2222 print(line)
2223
2224 elif have_gds and not no_gds_convert:
2225 # If neither SPICE nor CDL formats is available in the source, then
2226 # read GDS; if the result has no ports, then read the corresponding
2227 # LEF library to get port information. Then write out a SPICE netlist
2228 # for the whole library. NOTE: If there is no CDL or SPICE source,
2229 # then the port numbering is arbitrary, and becomes whatever the
2230 # output of this script makes it.
2231
2232 if ef_format:
2233 destdir = targetdir + cdl_reflib + 'spi'
2234 srcdir = targetdir + gds_reflib + 'gds'
2235 lefdir = targetdir + lef_reflib + 'lef'
2236 os.makedirs(destdir, exist_ok=True)
2237
2238 # For each library, create the library subdirectory
2239 for library in libraries:
2240 if len(library) == 3:
2241 destlib = library[2]
2242 else:
2243 destlib = library[1]
2244
2245 if ef_format:
2246 destlibdir = destdir + '/' + destlib
2247 srclibdir = srcdir + '/' + destlib
2248 leflibdir = lefdir + '/' + destlib
2249 else:
2250 destdir = targetdir + cdl_reflib + destlib + '/spice'
2251 srcdir = targetdir + gds_reflib + destlib + '/gds'
2252 lefdir = targetdir + lef_reflib + destlib + '/lef'
2253
2254 destlibdir = destdir
2255 srclibdir = srcdir
2256 leflibdir = lefdir
2257
2258 os.makedirs(destlibdir, exist_ok=True)
2259
2260 # Link to the PDK magic startup file from the target directory
2261 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
2262 if not os.path.isfile(startup_script):
2263 startup_script = targetdir + mag_current + pdkname + '.magicrc'
2264 if os.path.isfile(startup_script):
2265 # If the symbolic link exists, remove it.
2266 if os.path.isfile(destlibdir + '/.magicrc'):
2267 os.remove(destlibdir + '/.magicrc')
2268 os.symlink(startup_script, destlibdir + '/.magicrc')
2269
2270 # Get the consolidated GDS library file, or a list of all GDS files
2271 # if there is no single consolidated library
2272
2273 allgdslibname = srclibdir + '/' + destlib + '.gds'
2274 if not os.path.isfile(allgdslibname):
2275 glist = glob.glob(srclibdir + '/*.gds')
2276 glist.extend(glob.glob(srclibdir + '/*.gdsii'))
2277 glist.extend(glob.glob(srclibdir + '/*.gds2'))
2278
2279 allleflibname = leflibdir + '/' + destlib + '.lef'
2280 if not os.path.isfile(allleflibname):
2281 llist = glob.glob(leflibdir + '/*.lef')
2282
2283 print('Creating magic generation script to generate SPICE library.')
2284 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
2285 print('#!/usr/bin/env wish', file=ofile)
2286 print('#---------------------------------------------', file=ofile)
2287 print('# Script to generate SPICE library from GDS ', file=ofile)
2288 print('#---------------------------------------------', file=ofile)
2289 print('drc off', file=ofile)
2290 print('gds readonly true', file=ofile)
2291 print('gds flatten true', file=ofile)
2292 print('gds rescale false', file=ofile)
2293 print('tech unlock *', file=ofile)
2294
2295 if not os.path.isfile(allgdslibname):
2296 for gdsfile in glist:
2297 print('gds read ' + gdsfile, file=ofile)
2298 else:
2299 print('gds read ' + allgdslibname, file=ofile)
2300
2301 if not os.path.isfile(allleflibname):
2302 # Annotate the cells with information from the LEF files
2303 for leffile in llist:
2304 print('lef read ' + leffile, file=ofile)
2305 else:
2306 print('lef read ' + allleflibname, file=ofile)
2307
2308 # Load first file and remove the (UNNAMED) cell
2309 if not os.path.isfile(allgdslibname):
2310 print('load ' + os.path.splitext(glist[0])[0], file=ofile)
2311 else:
2312 gdslibroot = os.path.split(allgdslibname)[1]
2313 print('load ' + os.path.splitext(gdslibroot)[0], file=ofile)
2314 print('cellname delete \(UNNAMED\)', file=ofile)
2315
2316 print('ext2spice lvs', file=ofile)
2317
2318 # NOTE: Leaving "subcircuit top" as "auto" (default) can cause
2319 # cells like decap that have no I/O to be output without a subcircuit
2320 # wrapper. Also note that if this happens, it is an indication that
2321 # power supplies have not been labeled as ports, which is harder to
2322 # handle and should be fixed in the source.
2323 print('ext2spice subcircuit top on', file=ofile)
2324
2325 print('ext2spice cthresh 0.1', file=ofile)
2326
2327 if os.path.isfile(allgdslibname):
2328 print('select top cell', file=ofile)
2329 print('set glist [cellname list children]', file=ofile)
2330 print('foreach cell $glist {', file=ofile)
2331 else:
2332 print('foreach cell [cellname list top] {', file=ofile)
2333
2334 print(' load $cell', file=ofile)
2335 print(' puts stdout "Extracting cell $cell"', file=ofile)
2336 print(' extract all', file=ofile)
2337 print(' ext2spice', file=ofile)
2338 print('}', file=ofile)
2339 print('puts stdout "Done."', file=ofile)
2340 print('quit -noprompt', file=ofile)
2341
2342 # Run magic to read in the individual GDS files and
2343 # write out the consolidated GDS library
2344
2345 print('Running magic to create GDS library.')
2346 sys.stdout.flush()
2347
2348 mproc = subprocess.run(['magic', '-dnull', '-noconsole',
2349 destlibdir + '/generate_magic.tcl'],
2350 stdin = subprocess.DEVNULL,
2351 stdout = subprocess.PIPE,
2352 stderr = subprocess.PIPE, cwd = destlibdir,
2353 universal_newlines = True)
2354 if mproc.stdout:
2355 for line in mproc.stdout.splitlines():
2356 print(line)
2357 if mproc.stderr:
2358 print('Error message output from magic:')
2359 for line in mproc.stderr.splitlines():
2360 print(line)
2361 if mproc.returncode != 0:
2362 print('ERROR: Magic exited with status ' + str(mproc.returncode))
2363
2364 # Remove intermediate extraction files
2365 extfiles = glob.glob(destlibdir + '/*.ext')
2366 for extfile in extfiles:
2367 os.remove(extfile)
2368
2369 # If the GDS file was a consolidated file of all cells, then
2370 # create a similar SPICE library of all cells.
2371
2372 if os.path.isfile(allgdslibname):
2373 spiext = '.spice' if not ef_format else '.spi'
2374 create_spice_library(destlibdir, destlib, spiext, do_compile_only, do_stub, excludelist)
2375
2376 sys.exit(0)