blob: 96caf14a939c536995241dbb6536997523f04276 [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.
7#
8# Options:
9# -link_from <type> Make symbolic links to vendor files from target
10# Types are: "none", "source", or a PDK name.
11# Default "none" (copy all files from source)
12# -ef_names Use efabless naming (libs.ref/techLEF),
13# otherwise use generic naming (libs.tech/lef)
14#
15# -source <path> Path to source data top level directory
16# -target <path> Path to target top level directory
17#
18#
19# All other options represent paths to vendor files. They may all be
20# wildcarded with "*" to represent, e.g., version number directories,
21# or names of supported libraries. Where wildcards exist, if there is
22# more than one directory in the path, the value represented by "*"
23# will first be checked against library names. If no library name is
24# found, then the wildcard value will be assumed to be numeric and
25# separated by either "." or "_" to represent major/minor/sub/...
26# revision numbers (alphanumeric).
27#
28# Note only one of "-spice" or "-cdl" need be specified. Since the
29# open source tools use ngspice, CDL files are converted to ngspice
30# syntax when needed.
31#
32# -techlef <path> Path to technology LEF file
33# -doc <path> Path to technology documentation
34# -lef <path> Path to LEF file
35# -lefanno <path> Path to LEF file (for annotation only)
36# -spice <path> Path to SPICE netlists
37# -cdl <path> Path to CDL netlists
38# -models <path> Path to SPICE (primitive device) models
39# -liberty <path> Path to Liberty timing files
40# -gds <path> Path to GDS data
41# -verilog <path> Path to verilog models
42#
43# -library <type> <name> [<target>] See below
44#
45# For the "-library" option, any number of libraries may be supported, and
46# one "-library" option should be provided for each supported library.
47# <type> is one of: "digital", "primitive", or "general". Analog and I/O
48# libraries fall under the category "general", as they are all treated the
49# same way. <name> is the vendor name of the library. [<target>] is the
50# (optional) local name of the library. If omitted, then the vendor name
51# is used for the target (there is no particular reason to specify a
52# different local name for a library).
53#
54# All options "-lef", "-spice", etc., can take the additional arguments
55# up <number>
56#
57# to indicate that the source hierarchy should be copied from <number>
58# levels above the files. For example, if liberty files are kept in
59# multiple directories according to voltage level, then
60#
61# -liberty x/y/z/PVT_*/*.lib
62#
63# would install all .lib files directly into libs.ref/lef/<libname>/*.lib
64# while
65#
66# -liberty x/y/z/PVT_*/*.lib up 1
67#
68# would install all .lib files into libs.ref/lef/PVT_*/<libname>/*.lib
69#
70# Other library-specific arguments are:
71#
72# nospec : Remove timing specification before installing
73# (used with verilog files; needs to be extended to
74# liberty files)
75# compile : Create a single library from all components. Used
76# when a foundry library has inconveniently split
77# an IP library (LEF, CDL, verilog, etc.) into
78# individual files.
79#
80# NOTE: This script can be called once for all libraries if all file
81# types (gds, cdl, lef, etc.) happen to all work with the same wildcards.
82# However, it is more likely that it will be called several times for the
83# same PDK, once to install I/O cells, once to install digital, and so
84# forth, as made possible by the wild-carding.
85
86import re
87import os
88import sys
89import glob
90import shutil
91import subprocess
92
93def usage():
94 print("foundry_install.py [options...]")
95 print(" -link_from <name> Make symbolic links from target to <name>")
96 print(" where <name> can be 'source' or a PDK name.")
97 print(" Default behavior is to copy all files.")
98 print(" -copy Copy files from source to target (default)")
99 print(" -ef_names Use efabless naming conventions for local directories")
100 print("")
101 print(" -source <path> Path to top of source directory tree")
102 print(" -target <path> Path to top of target directory tree")
103 print("")
104 print(" -techlef <path> Path to technology LEF file")
105 print(" -doc <path> Path to technology documentation")
106 print(" -lef <path> Path to LEF file")
107 print(" -lefanno <path> Path to LEF file (for annotation only)")
108 print(" -spice <path> Path to SPICE netlists")
109 print(" -cdl <path> Path to CDL netlists")
110 print(" -models <path> Path to SPICE (primitive device) models")
111 print(" -lib <path> Path to Liberty timing files")
112 print(" -liberty <path> Path to Liberty timing files")
113 print(" -gds <path> Path to GDS data")
114 print(" -verilog <path> Path to verilog models")
115 print(" -library <type> <name> [<target>] See below")
116 print("")
117 print(" All <path> names may be wild-carded with '*' ('glob'-style wild-cards)")
118 print("")
119 print(" All options with <path> other than source and target may take the additional")
120 print(" arguments 'up <number>', where <number> indicates the number of levels of")
121 print(" hierarchy of the source path to include when copying to the target.")
122 print("")
123 print(" Library <type> may be one of:")
124 print(" digital Digital standard cell library")
125 print(" primitive Primitive device library")
126 print(" general All other library types (I/O, analog, etc.)")
127 print("")
128 print(" If <target> is unspecified then <name> is used for the target.")
129
130def get_gds_properties(magfile):
131 proprex = re.compile('^[ \t]*string[ \t]+(GDS_[^ \t]+)[ \t]+([^ \t]+)$')
132 proplines = []
133 if os.path.isfile(magfile):
134 with open(magfile, 'r') as ifile:
135 magtext = ifile.read().splitlines()
136 for line in magtext:
137 lmatch = proprex.match(line)
138 if lmatch:
139 propline = lmatch.group(1) + ' ' + lmatch.group(2)
140 proplines.append(propline)
141 return proplines
142
143# Read subcircuit ports from a CDL file, given a subcircuit name that should
144# appear in the file as a subcircuit entry, and return a dictionary of ports
145# and their indexes in the subcircuit line.
146
147def get_subckt_ports(cdlfile, subname):
148 portdict = {}
149 pidx = 1
150 portrex = re.compile('^\.subckt[ \t]+([^ \t]+)[ \t]+(.*)$', re.IGNORECASE)
151 with open(cdlfile, 'r') as ifile:
152 cdltext = ifile.read()
153 cdllines = cdltext.replace('\n+', ' ').splitlines()
154 for line in cdllines:
155 lmatch = portrex.match(line)
156 if lmatch:
157 if lmatch.group(1).lower() == subname.lower():
158 ports = lmatch.group(2).split()
159 for port in ports:
160 portdict[port.lower()] = pidx
161 pidx += 1
162 break
163 return portdict
164
165# Filter a verilog file to remove any backslash continuation lines, which
166# iverilog does not parse. If targetroot is a directory, then find and
167# process all files in the path of targetroot. If any file to be processed
168# is unmodified (has no backslash continuation lines), then ignore it. If
169# any file is a symbolic link and gets modified, then remove the symbolic
170# link before overwriting with the modified file.
171#
172# If 'do_remove_spec' is True, then remove timing information from the file,
173# which is everything between the keywords "specify" and "endspecify".
174
175def vfilefilter(vfile, do_remove_spec):
176 modified = False
177 with open(vfile, 'r') as ifile:
178 vtext = ifile.read()
179
180 # Remove backslash-followed-by-newline and absorb initial whitespace. It
181 # is unclear what initial whitespace means in this context, as the use-
182 # case that has been seen seems to work under the assumption that leading
183 # whitespace is ignored up to the amount used by the last indentation.
184
185 vlines = re.sub('\\\\\n[ \t]*', '', vtext)
186
187 if do_remove_spec:
188 specrex = re.compile('\n[ \t]*specify[ \t\n]+')
189 endspecrex = re.compile('\n[ \t]*endspecify')
190 smatch = specrex.search(vlines)
191 while smatch:
192 specstart = smatch.start()
193 specpos = smatch.end()
194 ematch = endspecrex.search(vlines[specpos:])
195 specend = ematch.end()
196 vtemp = vlines[0:specstart + 1] + vlines[specpos + specend + 1:]
197 vlines = vtemp
198 smatch = specrex.search(vlines)
199
200 if vlines != vtext:
201 # File contents have been modified, so if this file was a symbolic
202 # link, then remove it. Otherwise, overwrite the file with the
203 # modified contents.
204 if os.path.islink(vfile):
205 os.unlink(vfile)
206 with open(vfile, 'w') as ofile:
207 ofile.write(vlines)
208
209# Run a filter on verilog files that cleans up known syntax issues.
210# This is embedded in the foundry_install script and is not a custom
211# filter largely because the issues are in the tool, not the PDK.
212
213def vfilter(targetroot, do_remove_spec):
214 if os.path.isfile(targetroot):
215 vfilefilter(targetroot, do_remove_spec)
216 else:
217 vlist = glob.glob(targetroot + '/*')
218 for vfile in vlist:
219 if os.path.isfile(vfile):
220 vfilefilter(vfile, do_remove_spec)
221
222# For issues that are PDK-specific, a script can be written and put in
223# the PDK's custom/scripts/ directory, and passed to the foundry_install
224# script using the "filter" option.
225
226def tfilter(targetroot, filterscript):
227 if os.path.isfile(targetroot):
228 print(' Filtering file ' + targetroot)
229 subprocess.run([filterscript, targetroot, targetroot],
230 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
231 stderr = subprocess.PIPE, universal_newlines = True)
232 else:
233 tlist = glob.glob(targetroot + '/*')
234 for tfile in tlist:
235 if os.path.isfile(tfile):
236 print(' Filtering file ' + tfile)
237 subprocess.run([filterscript, tfile, tfile],
238 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
239 stderr = subprocess.PIPE, universal_newlines = True)
240
241# This is the main entry point for the foundry install script.
242
243if __name__ == '__main__':
244
245 if len(sys.argv) == 1:
246 print("No options given to foundry_install.py.")
247 usage()
248 sys.exit(0)
249
250 optionlist = []
251 newopt = []
252
253 sourcedir = None
254 targetdir = None
255 link_from = None
256
257 ef_names = False
258
259 have_lef = False
260 have_lefanno = False
261 have_gds = False
262 have_spice = False
263 have_cdl = False
264 ignorelist = []
265
266 do_install = True
267
268 # Break arguments into groups where the first word begins with "-".
269 # All following words not beginning with "-" are appended to the
270 # same list (optionlist). Then each optionlist is processed.
271 # Note that the first entry in optionlist has the '-' removed.
272
273 for option in sys.argv[1:]:
274 if option.find('-', 0) == 0:
275 if newopt != []:
276 optionlist.append(newopt)
277 newopt = []
278 newopt.append(option[1:])
279 else:
280 newopt.append(option)
281
282 if newopt != []:
283 optionlist.append(newopt)
284
285 # Pull library names from optionlist
286 libraries = []
287 for option in optionlist[:]:
288 if option[0] == 'library':
289 optionlist.remove(option)
290 libraries.append(option[1:])
291
292 # Check for option "ef_names" or "std_names"
293 for option in optionlist[:]:
294 if option[0] == 'ef_naming' or option[0] == 'ef_names':
295 optionlist.remove(option)
296 ef_names = True
297 elif option[0] == 'std_naming' or option[0] == 'std_names':
298 optionlist.remove(option)
299 ef_names = False
300 elif option[0] == 'uninstall':
301 optionlist.remove(option)
302 do_install = False
303
304 # Check for options "link_from", "source", and "target"
305 link_name = None
306 for option in optionlist[:]:
307 if option[0] == 'link_from':
308 optionlist.remove(option)
309 if option[1].lower() == 'none':
310 link_from = None
311 elif option[1].lower() == 'source':
312 link_from = 'source'
313 else:
314 link_from = option[1]
315 link_name = os.path.split(link_from)[1]
316 elif option[0] == 'source':
317 optionlist.remove(option)
318 sourcedir = option[1]
319 elif option[0] == 'target':
320 optionlist.remove(option)
321 targetdir = option[1]
322
323 # Error if no source or dest specified
324 if not sourcedir:
325 print("No source directory specified. Exiting.")
326 sys.exit(1)
327
328 if not targetdir:
329 print("No target directory specified. Exiting.")
330 sys.exit(1)
331
332 # If link source is a PDK name, if it has no path, then pull the
333 # path from the target name.
334
335 if link_from:
336 if link_from != 'source':
337 if link_from.find('/', 0) < 0:
338 target_root = os.path.split(targetdir)[0]
339 link_from = target_root + '/' + link_from
340 link_name = link_from
341 else:
342 # If linking from source, convert the source path to an
343 # absolute pathname.
344 sourcedir = os.path.abspath(sourcedir)
345
346 # Take the target PDK name from the target path last component
347 pdkname = os.path.split(targetdir)[1]
348
349 # checkdir is the DIST target directory for the PDK pointed
350 # to by link_name. Files must be found there before creating
351 # symbolic links to the (not yet existing) final install location.
352
353 if link_name:
354 checkdir = os.path.split(targetdir)[0] + '/' + link_name
355 else:
356 checkdir = ''
357
358 # Diagnostic
359 if do_install:
360 print("Installing in target directory " + targetdir)
361
362 # Create the top-level directories
363
364 os.makedirs(targetdir, exist_ok=True)
365 os.makedirs(targetdir + '/libs.ref', exist_ok=True)
366 os.makedirs(targetdir + '/libs.tech', exist_ok=True)
367
368 # Path to magic techfile depends on ef_names
369
370 if ef_names == True:
371 mag_current = '/libs.tech/magic/current/'
372 else:
373 mag_current = '/libs.tech/magic/'
374
375 # Populate the techLEF and SPICE models, if specified.
376
377 for option in optionlist[:]:
378 if option[0] == 'techlef':
379 filter_script = None
380 for item in option:
381 if item.split('=')[0] == 'filter':
382 filter_script = item.split('=')[1]
383 break
384
385 if ef_names == True:
386 techlefdir = targetdir + '/libs.ref/techLEF'
387 checklefdir = checkdir + '/libs.ref/techLEF'
388 if link_from:
389 linklefdir = link_from + '/libs.ref/techLEF'
390 else:
391 linklefdir = ''
392 else:
393 techlefdir = targetdir + '/libs.tech/lef'
394 checklefdir = checkdir + '/libs.tech/lef'
395 if link_from:
396 linklefdir = link_from + '/libs.tech/lef'
397 else:
398 linklefdir = ''
399 os.makedirs(techlefdir, exist_ok=True)
400 # All techlef files should be linked or copied, so use "glob"
401 # on the wildcards
402 techlist = glob.glob(sourcedir + '/' + option[1])
403
404 for lefname in techlist:
405 leffile = os.path.split(lefname)[1]
406 targname = techlefdir + '/' + leffile
407 checklefname = checklefdir + '/' + leffile
408 linklefname = linklefdir + '/' + leffile
409 # Remove any existing file(s)
410 if os.path.isfile(targname):
411 os.remove(targname)
412 elif os.path.islink(targname):
413 os.unlink(targname)
414 elif os.path.isdir(targname):
415 shutil.rmtree(targname)
416
417 if do_install:
418 if not link_from:
419 if os.path.isfile(lefname):
420 shutil.copy(lefname, targname)
421 else:
422 shutil.copytree(lefname, targname)
423 elif link_from == 'source':
424 os.symlink(lefname, targname)
425 else:
426 if os.path.exists(checklefname):
427 os.symlink(linklefname, targname)
428 elif os.path.isfile(lefname):
429 shutil.copy(lefname, targname)
430 else:
431 shutil.copytree(lefname, targname)
432
433 if filter_script:
434 # Apply filter script to all files in the target directory
435 tfilter(targname, filter_script)
436 optionlist.remove(option)
437
438 elif option[0] == 'models':
439 filter_script = None
440 for item in option:
441 if item.split('=')[0] == 'filter':
442 filter_script = item.split('=')[1]
443 break
444
445 print('Diagnostic: installing models.')
446 modelsdir = targetdir + '/libs.tech/models'
447 checkmoddir = checkdir + '/libs.tech/models'
448 if link_from:
449 linkmoddir = link_from + '/libs.tech/models'
450 else:
451 linkmoddir = ''
452
453 os.makedirs(modelsdir, exist_ok=True)
454
455 # All model files should be linked or copied, so use "glob"
456 # on the wildcards. Copy each file and recursively copy each
457 # directory.
458 modellist = glob.glob(sourcedir + '/' + option[1])
459
460 for modname in modellist:
461 modfile = os.path.split(modname)[1]
462 targname = modelsdir + '/' + modfile
463 checkmodname = checkmoddir + '/' + modfile
464 linkmodname = linkmoddir + '/' + modfile
465
466 if os.path.isdir(modname):
467 # Remove any existing directory, and its contents
468 if os.path.isdir(targname):
469 shutil.rmtree(targname)
470 os.makedirs(targname)
471
472 # Recursively find and copy or link the whole directory
473 # tree from this point.
474
475 allmodlist = glob.glob(modname + '/**', recursive=True)
476 commonpart = os.path.commonpath(allmodlist)
477 for submodname in allmodlist:
478 if os.path.isdir(submodname):
479 continue
480 # Get the path part that is not common between modlist and
481 # allmodlist.
482 subpart = os.path.relpath(submodname, commonpart)
483 subtargname = targname + '/' + subpart
484 os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
485 if do_install:
486 if not link_from:
487 if os.path.isfile(submodname):
488 shutil.copy(submodname, subtargname)
489 else:
490 shutil.copytree(submodname, subtargname)
491 elif link_from == 'source':
492 os.symlink(submodname, subtargname)
493 else:
494 if os.path.exists(checkmodname):
495 os.symlink(linkmodname, subtargname)
496 elif os.path.isfile(submodname):
497 shutil.copy(submodname, subtargname)
498 else:
499 shutil.copytree(submodname, subtargname)
500
501 if filter_script:
502 # Apply filter script to all files in the target directory
503 tfilter(targname, filter_script)
504
505 else:
506 # Remove any existing file
507 if os.path.isfile(targname):
508 os.remove(targname)
509 elif os.path.islink(targname):
510 os.unlink(targname)
511 elif os.path.isdir(targname):
512 shutil.rmtree(targname)
513
514 if do_install:
515 if not link_from:
516 if os.path.isfile(modname):
517 shutil.copy(modname, targname)
518 else:
519 shutil.copytree(modname, targname)
520 elif link_from == 'source':
521 os.symlink(modname, targname)
522 else:
523 if os.path.isfile(checkmodname):
524 os.symlink(linkmodname, targname)
525 elif os.path.isfile(modname):
526 shutil.copy(modname, targname)
527 else:
528 shutil.copytree(modname, targname)
529
530 if filter_script:
531 # Apply filter script to all files in the target directory
532 tfilter(targname, filter_script)
533
534 optionlist.remove(option)
535
536 # The remaining options in optionlist should all be types like 'lef' or 'liberty'
537 for option in optionlist[:]:
538 # Diagnostic
539 if do_install:
540 print("Installing option: " + str(option[0]))
541 destdir = targetdir + '/libs.ref/' + option[0]
542 checklibdir = checkdir + '/libs.ref/' + option[0]
543 if link_from:
544 destlinkdir = link_from + '/libs.ref/' + option[0]
545 else:
546 destlinkdir = ''
547 os.makedirs(destdir, exist_ok=True)
548
549 # If the option is followed by the keyword "up" and a number, then
550 # the source should be copied (or linked) from <number> levels up
551 # in the hierarchy (see below).
552
553 if 'up' in option:
554 uparg = option.index('up')
555 try:
556 hier_up = int(option[uparg + 1])
557 except:
558 print("Non-numeric option to 'up': " + option[uparg + 1])
559 print("Ignoring 'up' option.")
560 hier_up = 0
561 else:
562 hier_up = 0
563
564 filter_script = None
565 for item in option:
566 if item.split('=')[0] == 'filter':
567 filter_script = item.split('=')[1]
568 break
569
570 # Option 'compile' is a standalone keyword ('comp' may be used).
571 do_compile = 'compile' in option or 'comp' in option
572
573 # Option 'nospecify' is a standalone keyword ('nospec' may be used).
574 do_remove_spec = 'nospecify' in option or 'nospec' in option
575
576 # Check off things we need to do migration to magic database and
577 # abstact files.
578 if option[0] == 'lef':
579 have_lef = True
580 elif option[0] == 'gds':
581 have_gds = True
582 elif option[0] == 'lefanno':
583 have_lefanno = True
584 elif option[0] == 'spice':
585 have_spice = True
586 elif option[0] == 'cdl':
587 have_cdl = True
588
589 # For each library, create the library subdirectory
590 for library in libraries:
591 if len(library) == 3:
592 destlib = library[2]
593 else:
594 destlib = library[1]
595 destlibdir = destdir + '/' + destlib
596 destlinklibdir = destlinkdir + '/' + destlib
597 checksrclibdir = checklibdir + '/' + destlib
598 os.makedirs(destlibdir, exist_ok=True)
599
600 # Populate the library subdirectory
601 # Parse the option and replace each '/*/' with the library name,
602 # and check if it is a valid directory name. Then glob the
603 # resulting option name. Warning: This assumes that all
604 # occurences of the text '/*/' match a library name. It should
605 # be possible to wild-card the directory name in such a way that
606 # this is always true.
607
608 testopt = re.sub('\/\*\/', '/' + library[1] + '/', option[1])
609
610 liblist = glob.glob(sourcedir + '/' + testopt)
611
612 # Diagnostic
613 print('Collecting files from ' + str(sourcedir + '/' + testopt))
614 print('Files to install:')
615 if len(liblist) < 10:
616 for item in liblist:
617 print(' ' + item)
618 else:
619 for item in liblist[0:4]:
620 print(' ' + item)
621 print(' .')
622 print(' .')
623 print(' .')
624 for item in liblist[-6:-1]:
625 print(' ' + item)
626 print('(' + str(len(liblist)) + ' files total)')
627
628 for libname in liblist:
629 # Note that there may be a hierarchy to the files in option[1],
630 # say for liberty timing files under different conditions, so
631 # make sure directories have been created as needed.
632
633 libfile = os.path.split(libname)[1]
634 libfilepath = os.path.split(libname)[0]
635 destpathcomp = []
636 for i in range(hier_up):
637 destpathcomp.append('/' + os.path.split(libfilepath)[1])
638 libfilepath = os.path.split(libfilepath)[0]
639 destpathcomp.reverse()
640 destpath = ''.join(destpathcomp)
641
642 targname = destlibdir + destpath + '/' + libfile
643
644 # NOTE: When using "up" with link_from, could just make
645 # destpath itself a symbolic link; this way is more flexible
646 # but adds one symbolic link per file.
647
648 if destpath != '':
649 if not os.path.isdir(destlibdir + destpath):
650 os.makedirs(destlibdir + destpath, exist_ok=True)
651
652 # Both linklibname and checklibname need to contain any hierarchy
653 # implied by the "up" option.
654
655 linklibname = destlinklibdir + destpath + '/' + libfile
656 checklibname = checksrclibdir + destpath + '/' + libfile
657
658 # Remove any existing file
659 if os.path.isfile(targname):
660 os.remove(targname)
661 elif os.path.islink(targname):
662 os.unlink(targname)
663 elif os.path.isdir(targname):
664 shutil.rmtree(targname)
665
666 if do_install:
667 if not link_from:
668 if os.path.isfile(libname):
669 shutil.copy(libname, targname)
670 else:
671 shutil.copytree(libname, targname)
672 elif link_from == 'source':
673 os.symlink(libname, targname)
674 else:
675 if os.path.exists(checklibname):
676 os.symlink(linklibname, targname)
677 elif os.path.isfile(libname):
678 shutil.copy(libname, targname)
679 else:
680 shutil.copytree(libname, targname)
681
682 if option[0] == 'verilog':
683 # Special handling of verilog files to make them
684 # syntactically acceptable to iverilog.
685 # NOTE: Perhaps this should be recast as a custom filter?
686 vfilter(targname, do_remove_spec)
687
688 if filter_script:
689 # Apply filter script to all files in the target directory
690 tfilter(targname, filter_script)
691
692 if do_compile == True:
693 # To do: Extend this option to include formats other than verilog.
694 # Also to do: Make this compatible with linking from another PDK.
695
696 if option[0] == 'verilog':
697 # If there is not a single file with all verilog cells in it,
698 # then compile one, because one does not want to have to have
699 # an include line for every single cell used in a design.
700
701 alllibname = destlibdir + '/' + destlib + '.v'
702
703 print('Diagnostic: Creating consolidated verilog library ' + destlib + '.v')
704 vlist = glob.glob(destlibdir + '/*.v')
705 if alllibname in vlist:
706 vlist.remove(alllibname)
707
708 if len(vlist) > 1:
709 print('New file is: ' + alllibname)
710 with open(alllibname, 'w') as ofile:
711 for vfile in vlist:
712 with open(vfile, 'r') as ifile:
713 # print('Adding ' + vfile + ' to library.')
714 vtext = ifile.read()
715 # NOTE: The following workaround resolves an
716 # issue with iverilog, which does not properly
717 # parse specify timing paths that are not in
718 # parentheses. Easy to work around
719 vlines = re.sub(r'\)[ \t]*=[ \t]*([01]:[01]:[01])[ \t]*;', r') = ( \1 ) ;', vtext)
720 print(vlines, file=ofile)
721 print('\n//--------EOF---------\n', file=ofile)
722 else:
723 print('Only one file (' + str(vlist) + '); ignoring "compile" option.')
724
725 print("Completed installation of vendor files.")
726
727 # Now for the harder part. If GDS and/or LEF databases were specified,
728 # then migrate them to magic (.mag files in layout/ or abstract/).
729
730 ignore = []
731 do_cdl_scaleu = False
732 for option in optionlist[:]:
733 if option[0] == 'cdl':
734 # Option 'scaleu' is a standalone keyword
735 do_cdl_scaleu = 'scaleu' in option
736
737 # Option 'ignore' has arguments after '='
738 for item in option:
739 if item.split('=')[0] == 'ignore':
740 ignorelist = item.split('=')[1].split(',')
741
742 devlist = []
743 pdklibrary = None
744
745 if have_gds:
746 print("Migrating GDS files to layout.")
747 destdir = targetdir + '/libs.ref/mag'
748 srcdir = targetdir + '/libs.ref/gds'
749 os.makedirs(destdir, exist_ok=True)
750
751 # For each library, create the library subdirectory
752 for library in libraries:
753 if len(library) == 3:
754 destlib = library[2]
755 else:
756 destlib = library[1]
757 destlibdir = destdir + '/' + destlib
758 srclibdir = srcdir + '/' + destlib
759 os.makedirs(destlibdir, exist_ok=True)
760
761 # For primitive devices, check the PDK script and find the name
762 # of the library and get a list of supported devices.
763
764 if library[0] == 'primitive':
765 pdkscript = targetdir + mag_current + pdkname + '.tcl'
766 print('Searching for supported devices in PDK script ' + pdkscript + '.')
767
768 if os.path.isfile(pdkscript):
769 librex = re.compile('^[ \t]*set[ \t]+PDKNAMESPACE[ \t]+([^ \t]+)$')
770 devrex = re.compile('^[ \t]*proc[ \t]+([^ :\t]+)::([^ \t_]+)_defaults')
771 fixrex = re.compile('^[ \t]*return[ \t]+\[([^ :\t]+)::fixed_draw[ \t]+([^ \t]+)[ \t]+')
772 devlist = []
773 fixedlist = []
774 with open(pdkscript, 'r') as ifile:
775 scripttext = ifile.read().splitlines()
776 for line in scripttext:
777 lmatch = librex.match(line)
778 if lmatch:
779 pdklibrary = lmatch.group(1)
780 dmatch = devrex.match(line)
781 if dmatch:
782 if dmatch.group(1) == pdklibrary:
783 devlist.append(dmatch.group(2))
784 fmatch = fixrex.match(line)
785 if fmatch:
786 if fmatch.group(1) == pdklibrary:
787 fixedlist.append(fmatch.group(2))
788
789 # Diagnostic
790 print("PDK library is " + str(pdklibrary))
791
792 # Link to the PDK magic startup file from the target directory
793 # If there is no -F version then look for one without -F (open source PDK)
794 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
795 if not os.path.isfile(startup_script):
796 startup_script = targetdir + mag_current + pdkname + '.magicrc'
797
798 if os.path.isfile(startup_script):
799 # If the symbolic link exists, remove it.
800 if os.path.isfile(destlibdir + '/.magicrc'):
801 os.remove(destlibdir + '/.magicrc')
802 os.symlink(startup_script, destlibdir + '/.magicrc')
803
804 # Find GDS file names in the source
805 print('Getting GDS file list from ' + srclibdir + '.')
806 gdsfiles = os.listdir(srclibdir)
807
808 # Generate a script called "generate_magic.tcl" and leave it in
809 # the target directory. Use it as input to magic to create the
810 # .mag files from the database.
811
812 print('Creating magic generation script.')
813 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
814 print('#!/usr/bin/env wish', file=ofile)
815 print('#--------------------------------------------', file=ofile)
816 print('# Script to generate .mag files from .gds ', file=ofile)
817 print('#--------------------------------------------', file=ofile)
818 print('gds readonly true', file=ofile)
819 print('gds flatten true', file=ofile)
820 # print('gds rescale false', file=ofile)
821 print('tech unlock *', file=ofile)
822
823 for gdsfile in gdsfiles:
824 # Note: DO NOT use a relative path here.
825 # print('gds read ../../gds/' + destlib + '/' + gdsfile, file=ofile)
826 print('gds read ' + srclibdir + '/' + gdsfile, file=ofile)
827
828 # Make sure properties include the Tcl generated cell
829 # information from the PDK script
830
831 if pdklibrary:
832 tclfixedlist = '{' + ' '.join(fixedlist) + '}'
833 print('set devlist ' + tclfixedlist, file=ofile)
834 print('set topcell [lindex [cellname list top] 0]',
835 file=ofile)
836
837 print('foreach cellname $devlist {', file=ofile)
838 print(' load $cellname', file=ofile)
839 print(' property gencell $cellname', file=ofile)
840 print(' property parameter m=1', file=ofile)
841 print(' property library ' + pdklibrary, file=ofile)
842 print('}', file=ofile)
843 print('load $topcell', file=ofile)
844
845 print('writeall force', file=ofile)
846
847 if have_lefanno:
848 # Find LEF file names in the source
849 lefsrcdir = targetdir + '/libs.ref/lefanno'
850 lefsrclibdir = lefsrcdir + '/' + destlib
851 leffiles = list(item for item in os.listdir(lefsrclibdir) if os.path.splitext(item)[1] == '.lef')
852
853 if not have_lef:
854 # This library has a GDS database but no LEF database. Use
855 # magic to create abstract views of the GDS cells. If
856 # option "-lefanno" is given, then read the LEF file after
857 # loading the database file to annotate the cell with
858 # information from the LEF file. This usually indicates
859 # that the LEF file has some weird definition of obstruction
860 # layers and we want to normalize them by using magic's LEF
861 # write procedure, but we still need the pin use and class
862 # information from the LEF file, and maybe the bounding box.
863
864 print('set maglist [glob *.mag]', file=ofile)
865 print('foreach name $maglist {', file=ofile)
866 print(' load [file root $name]', file=ofile)
867 if have_lefanno:
868 print('}', file=ofile)
869 for leffile in leffiles:
870 print('lef read ' + lefsrclibdir + '/' + leffile, file=ofile)
871 print('foreach name $maglist {', file=ofile)
872 print(' load [file root $name]', file=ofile)
873 print(' lef write [file root $name]', file=ofile)
874 print('}', file=ofile)
875 print('quit -noprompt', file=ofile)
876
877 # Run magic to read in the GDS file and write out magic databases.
878 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
879 subprocess.run(['magic', '-dnull', '-noconsole'],
880 stdin = ifile, stdout = subprocess.PIPE,
881 stderr = subprocess.PIPE, cwd = destlibdir,
882 universal_newlines = True)
883
884 if not have_lef:
885 # Remove the lefanno/ target and its contents.
886 if have_lefanno:
887 lefannosrcdir = targetdir + '/libs.ref/lefanno'
888 if os.path.isdir(lefannosrcdir):
889 shutil.rmtree(lefannosrcdir)
890
891 destlefdir = targetdir + '/libs.ref/lef'
892 destleflibdir = destlefdir + '/' + destlib
893 os.makedirs(destleflibdir, exist_ok=True)
894 leflist = list(item for item in os.listdir(destlibdir) if os.path.splitext(item)[1] == '.lef')
895
896 # All macros will go into one file
897 destleflib = destleflibdir + '/' + destlib + '.lef'
898 # Remove any existing library file from the target directory
899 if os.path.isfile(destleflib):
900 os.remove(destleflib)
901
902 first = True
903 with open(destleflib, 'w') as ofile:
904 for leffile in leflist:
905 # Remove any existing single file from the target directory
906 if os.path.isfile(destleflibdir + '/' + leffile):
907 os.remove(destleflibdir + '/' + leffile)
908
909 # Append contents
910 sourcelef = destlibdir + '/' + leffile
911 with open(sourcelef, 'r') as ifile:
912 leflines = ifile.read().splitlines()
913 if not first:
914 # Remove header from all but the first file
915 leflines = leflines[8:]
916 else:
917 first = False
918
919 for line in leflines:
920 print(line, file=ofile)
921
922 # Remove file from the source directory
923 os.remove(sourcelef)
924
925 have_lef = True
926
927 # Remove the startup script and generation script
928 os.remove(destlibdir + '/.magicrc')
929 os.remove(destlibdir + '/generate_magic.tcl')
930 else:
931 print("Master PDK magic startup file not found. Did you install")
932 print("PDK tech files before PDK vendor files?")
933
934 if have_lef:
935 print("Migrating LEF files to layout.")
936 destdir = targetdir + '/libs.ref/maglef'
937 srcdir = targetdir + '/libs.ref/lef'
938 magdir = targetdir + '/libs.ref/mag'
939 cdldir = targetdir + '/libs.ref/cdl'
940 os.makedirs(destdir, exist_ok=True)
941
942 # For each library, create the library subdirectory
943 for library in libraries:
944 if len(library) == 3:
945 destlib = library[2]
946 else:
947 destlib = library[1]
948 destlibdir = destdir + '/' + destlib
949 srclibdir = srcdir + '/' + destlib
950 maglibdir = magdir + '/' + destlib
951 cdllibdir = cdldir + '/' + destlib
952 os.makedirs(destlibdir, exist_ok=True)
953
954 # Link to the PDK magic startup file from the target directory
955 startup_script = targetdir + mag_current + pdkname + '-F.magicrc'
956 if not os.path.isfile(startup_script):
957 startup_script = targetdir + mag_current + pdkname + '.magicrc'
958
959 if os.path.isfile(startup_script):
960 # If the symbolic link exists, remove it.
961 if os.path.isfile(destlibdir + '/.magicrc'):
962 os.remove(destlibdir + '/.magicrc')
963 os.symlink(startup_script, destlibdir + '/.magicrc')
964
965 # Find LEF file names in the source
966 leffiles = os.listdir(srclibdir)
967
968 # Generate a script called "generate_magic.tcl" and leave it in
969 # the target directory. Use it as input to magic to create the
970 # .mag files from the database.
971
972 with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
973 print('#!/usr/bin/env wish', file=ofile)
974 print('#--------------------------------------------', file=ofile)
975 print('# Script to generate .mag files from .lef ', file=ofile)
976 print('#--------------------------------------------', file=ofile)
977 print('tech unlock *', file=ofile)
978
979 if pdklibrary:
980 tcldevlist = '{' + ' '.join(devlist) + '}'
981 print('set devlist ' + tcldevlist, file=ofile)
982
983 for leffile in leffiles:
984
985 # Okay to use a relative path here.
986 # print('lef read ' + srclibdir + '/' + leffile', file=ofile)
987 print('lef read ../../lef/' + destlib + '/' + leffile, file=ofile)
988
989 # To be completed: Parse SPICE file for port order, make
990 # sure ports are present and ordered.
991
992 if pdklibrary:
993 print('set cellname [file root ' + leffile + ']', file=ofile)
994 print('if {[lsearch $devlist $cellname] >= 0} {',
995 file=ofile)
996 print(' load $cellname', file=ofile)
997 print(' property gencell $cellname', file=ofile)
998 print(' property parameter m=1', file=ofile)
999 print(' property library ' + pdklibrary, file=ofile)
1000 print('}', file=ofile)
1001
1002 print('writeall force', file=ofile)
1003 print('quit -noprompt', file=ofile)
1004
1005 # Run magic to read in the LEF file and write out magic databases.
1006 with open(destlibdir + '/generate_magic.tcl', 'r') as ifile:
1007 subprocess.run(['magic', '-dnull', '-noconsole'],
1008 stdin = ifile, stdout = subprocess.PIPE,
1009 stderr = subprocess.PIPE, cwd = destlibdir,
1010 universal_newlines = True)
1011
1012 # Now list all the .mag files generated, and for each, read the
1013 # corresponding file from the mag/ directory, pull the GDS file
1014 # properties, and add those properties to the maglef view. Also
1015 # read the CDL (or SPICE) netlist, read the ports, and rewrite
1016 # the port order in the mag and maglef file accordingly.
1017
1018 # Diagnostic
1019 print('Annotating files in ' + destlibdir)
1020 magfiles = os.listdir(destlibdir)
1021 for magroot in magfiles:
1022 magname = os.path.splitext(magroot)[0]
1023 magfile = maglibdir + '/' + magroot
1024 magleffile = destlibdir + '/' + magroot
1025 prop_lines = get_gds_properties(magfile)
1026
1027 # Make sure properties include the Tcl generated cell
1028 # information from the PDK script
1029
1030 if pdklibrary:
1031 if magname in fixedlist:
1032 prop_lines.append('string gencell ' + magname)
1033 prop_lines.append('string library ' + pdklibrary)
1034 prop_lines.append('string parameter m=1')
1035
1036 cdlfile = cdllibdir + '/' + magname + '.cdl'
1037 if not os.path.exists(cdlfile):
1038 # Assume there is one file with all cell subcircuits in it.
1039 try:
1040 cdlfile = glob.glob(cdllibdir + '/*.cdl')[0]
1041 except:
1042 print('No CDL file for ' + destlib + ' device ' + magname)
1043 cdlfile = None
1044 # To be done: If destlib is 'primitive', then look in
1045 # SPICE models for port order.
1046 if destlib == 'primitive':
1047 print('Fix me: Need to look in SPICE models!')
1048 if cdlfile:
1049 port_dict = get_subckt_ports(cdlfile, magname)
1050 else:
1051 port_dict = {}
1052
1053 proprex = re.compile('<< properties >>')
1054 endrex = re.compile('<< end >>')
1055 rlabrex = re.compile('rlabel[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+([^ \t]+)')
1056 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]+)')
1057 portrex = re.compile('port[ \t]+([^ \t])+[ \t]+(.*)')
1058 portnum = -1
1059
1060 with open(magleffile, 'r') as ifile:
1061 magtext = ifile.read().splitlines()
1062
1063 with open(magleffile, 'w') as ofile:
1064 for line in magtext:
1065 tmatch = portrex.match(line)
1066 if tmatch:
1067 if portnum >= 0:
1068 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1069 else:
1070 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1071 ematch = endrex.match(line)
1072 if ematch and len(prop_lines) > 0:
1073 print('<< properties >>', file=ofile)
1074 for prop in prop_lines:
1075 print('string ' + prop, file=ofile)
1076
1077 print(line, file=ofile)
1078 pmatch = proprex.match(line)
1079 if pmatch:
1080 for prop in prop_lines:
1081 print('string ' + prop, file=ofile)
1082 prop_lines = []
1083
1084 lmatch = flabrex.match(line)
1085 if not lmatch:
1086 lmatch = rlabrex.match(line)
1087 if lmatch:
1088 labname = lmatch.group(1).lower()
1089 try:
1090 portnum = port_dict[labname]
1091 except:
1092 portnum = -1
1093
1094 if os.path.exists(magfile):
1095 with open(magfile, 'r') as ifile:
1096 magtext = ifile.read().splitlines()
1097
1098 with open(magfile, 'w') as ofile:
1099 for line in magtext:
1100 tmatch = portrex.match(line)
1101 if tmatch:
1102 if portnum >= 0:
1103 line = 'port ' + str(portnum) + ' ' + tmatch.group(2)
1104 else:
1105 line = 'port ' + tmatch.group(1) + ' ' + tmatch.group(2)
1106 ematch = endrex.match(line)
1107 print(line, file=ofile)
1108 lmatch = flabrex.match(line)
1109 if not lmatch:
1110 lmatch = rlabrex.match(line)
1111 if lmatch:
1112 labname = lmatch.group(1).lower()
1113 try:
1114 portnum = port_dict[labname]
1115 except:
1116 portnum = -1
1117 elif os.path.splitext(magfile)[1] == '.mag':
1118 # NOTE: Probably this means the GDS cell has a different name.
1119 print('Error: No file ' + magfile + '. Why is it in maglef???')
1120
1121 # Remove the startup script and generation script
1122 os.remove(destlibdir + '/.magicrc')
1123 os.remove(destlibdir + '/generate_magic.tcl')
1124 else:
1125 print("Master PDK magic startup file not found. Did you install")
1126 print("PDK tech files before PDK vendor files?")
1127
1128 # If SPICE or CDL databases were specified, then convert them to
1129 # a form that can be used by ngspice, using the cdl2spi.py script
1130
1131 if have_spice:
1132 if not os.path.isdir(targetdir + '/libs.ref/spi'):
1133 os.makedirs(targetdir + '/libs.ref/spi', exist_ok=True)
1134
1135 elif have_cdl:
1136 if not os.path.isdir(targetdir + '/libs.ref/spi'):
1137 os.makedirs(targetdir + '/libs.ref/spi', exist_ok=True)
1138
1139 print("Migrating CDL netlists to SPICE.")
1140 destdir = targetdir + '/libs.ref/spi'
1141 srcdir = targetdir + '/libs.ref/cdl'
1142 os.makedirs(destdir, exist_ok=True)
1143
1144 # For each library, create the library subdirectory
1145 for library in libraries:
1146 if len(library) == 3:
1147 destlib = library[2]
1148 else:
1149 destlib = library[1]
1150 destlibdir = destdir + '/' + destlib
1151 srclibdir = srcdir + '/' + destlib
1152 os.makedirs(destlibdir, exist_ok=True)
1153
1154 # Find CDL file names in the source
1155 cdlfiles = os.listdir(srclibdir)
1156
1157 # The directory with scripts should be in ../common with respect
1158 # to the Makefile that determines the cwd.
1159 scriptdir = os.path.split(os.getcwd())[0] + '/common/'
1160
1161 # Run cdl2spi.py script to read in the CDL file and write out SPICE
1162 for cdlfile in cdlfiles:
1163 spiname = os.path.splitext(cdlfile)[0] + '.spi'
1164 procopts = [scriptdir + 'cdl2spi.py', srclibdir + '/' + cdlfile, destlibdir + '/' + spiname]
1165 if do_cdl_scaleu:
1166 procopts.append('-dscale=u')
1167 for item in ignorelist:
1168 procopts.append('-ignore=' + item)
1169 print('Running (in ' + destlibdir + '): ' + ' '.join(procopts))
1170 subprocess.run(procopts,
1171 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
1172 stderr = subprocess.PIPE, cwd = destlibdir,
1173 universal_newlines = True)
1174
1175 sys.exit(0)