forked from RTimothyEdwards/open_pdks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfoundry_install.py
executable file
·2197 lines (1869 loc) · 96.5 KB
/
foundry_install.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3
#
# foundry_install.py
#
# This file generates the local directory structure and populates the
# directories with foundry vendor data. The local directory (target)
# should be a staging area, not a place where files are kept permanently.
#
# Options:
# -ef_format Use efabless naming (libs.ref/techLEF),
# otherwise use generic naming (libs.tech/lef)
# -clean Clear out and remove target directory before starting
# -source <path> Path to source data top level directory
# -target <path> Path to target (staging) top level directory
#
# All other options represent paths to vendor files. They may all be
# wildcarded with "*", or with specific escapes like "%l" for library
# name or "%v" for version number (see below for a complete list of escape
# sequences).
#
# Note only one of "-spice" or "-cdl" need be specified. Since the
# open source tools use ngspice, CDL files are converted to ngspice
# syntax when needed.
#
# -techlef <path> Path to technology LEF file
# -doc <path> Path to technology documentation
# -lef <path> Path to LEF file
# -spice <path> Path to SPICE netlists
# -cdl <path> Path to CDL netlists
# -models <path> Path to SPICE (primitive device) models
# -liberty <path> Path to Liberty timing files
# -gds <path> Path to GDS data
# -verilog <path> Path to verilog models
#
# -library <type> <name> [<target>] See below
#
# For the "-library" option, any number of libraries may be supported, and
# one "-library" option should be provided for each supported library.
# <type> is one of: "digital", "primitive", or "general". Analog and I/O
# libraries fall under the category "general", as they are all treated the
# same way. <name> is the vendor name of the library. [<target>] is the
# (optional) local name of the library. If omitted, then the vendor name
# is used for the target (there is no particular reason to specify a
# different local name for a library).
#
# In special cases using options (see below), path may be "-", indicating
# that there are no source files, but only to run compilations or conversions
# on the files in the target directory.
#
# All options "-lef", "-spice", etc., can take the additional arguments
# up <number>
#
# to indicate that the source hierarchy should be copied from <number>
# levels above the files. For example, if liberty files are kept in
# multiple directories according to voltage level, then
#
# -liberty x/y/z/PVT_*/*.lib
#
# would install all .lib files directly into libs.ref/<libname>/liberty/*.lib
# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/*.lib)
# while
#
# -liberty x/y/z/PVT_*/*.lib up 1
#
# would install all .lib files into libs.ref/liberty/<libname>/PVT_*/*.lib
# (if "-ef_format" option specified, then: libs.ref/<libname>/liberty/PVT_*/*.lib)
#
# Please note that the INSTALL variable in the Makefile starts with "set -f"
# to suppress the OS from doing wildcard substitution; otherwise the
# wildcards in the install options will get expanded by the OS before
# being passed to the install script.
#
# Other library-specific arguments are:
#
# nospec : Remove timing specification before installing
# (used with verilog files; needs to be extended to
# liberty files)
#
# compile : Create a single library from all components. Used
# when a foundry library has inconveniently split
# an IP library (LEF, CDL, verilog, etc.) into
# individual files.
#
# compile-only: Like "compile" except that the individual
# files are removed after the library file has been
# created.
#
# stub : Remove contents of subcircuits from CDL or SPICE
# netlist files.
#
# priv : Mark the contents being installed as privleged, and
# put them in a separate root directory libs.priv
# where they can be given additional read/write
# restrictions.
#
# exclude : Followed by "=" and a comma-separated list of names.
# exclude these files/modules/subcircuits. Names may
# also be wildcarded in "glob" format. Cell names are
# excluded from the copy and also from any libraries
# that are generated. If the pattern contains a directory
# path, then patterns are added from files in the
# specified directory instead of the source.
#
# no-copy : Followed by "=" and a comma-separated list of names.
# exclude these files/modules/subcircuits. Names may
# also be wildcarded in "glob" format. Cell names are
# excluded from the copy only. If the pattern contains
# a directory path, then patterns are added from files
# in the specified directory instead of the source.
#
# include : Followed by "=" and a comma-separated list of names.
# include these files/modules/subcircuits. Names may
# also be wildcarded in "glob" format. Cell names are
# included in any libraries that are generated if they
# exist in the target directory, even if they were not
# in the source directory being copied. If the pattern
# contains a directory path, then patterns are added from
# files in the specified directory instead of the source.
#
# rename : Followed by "=" and an alternative name. For any
# file that is a single entry, change the name of
# the file in the target directory to this (To-do:
# take regexps for multiple files). When used with
# "compile" or "compile-only", this refers to the
# name of the target compiled file.
#
# filter: Followed by "=" and the name of a script.
# Each file is passed through the filter script
# before writing into the staging area.
#
# sort: Followed by "=" and the name of a script.
# The list of files to process (after applying items
# from "exclude") will be written to a file
# "filelist.txt", which will be used by the
# library compile routines, if present. The sort
# script will rewrite the file with the order in
# which entries should appear in the compiled library.
# Only useful when used with "compile" or "compile-only".
# If not specified, files are sorted by "natural sort"
# order.
#
# annotate: When used with "-lef", the LEF files are used only
# for annotation of pins (use, direction, etc.), but
# the LEF files should not be used and LEF should be
# generated from layout.
#
# noconvert: Install only; do not attempt to convert to other
# formats (applies only to GDS, CDL, and LEF).
#
# options: Followed by "=" and the name of a script. Behavior
# is dependent on the mode; if applied to "-gds",
# then the script is inserted before the GDS read
# in the Tcl generate script passed to magic. If
# what follows the "=" is not a file, then it is
# Tcl code to be inserted verbatim.
#
# NOTE: This script can be called once for all libraries if all file
# types (gds, cdl, lef, etc.) happen to all work with the same wildcards.
# However, it is more likely that it will be called several times for the
# same PDK, once to install I/O cells, once to install digital, and so
# forth, as made possible by the wild-carding.
import re
import os
import sys
import glob
import stat
import shutil
import fnmatch
import subprocess
# Import local routines
from create_gds_library import create_gds_library
from create_spice_library import create_spice_library
from create_lef_library import create_lef_library
from create_lib_library import create_lib_library
from create_verilog_library import create_verilog_library
def usage():
print("foundry_install.py [options...]")
print(" -copy Copy files from source to target (default)")
print(" -ef_format Use efabless naming conventions for local directories")
print("")
print(" -source <path> Path to top of source directory tree")
print(" -target <path> Path to top of target directory tree")
print("")
print(" -techlef <path> Path to technology LEF file")
print(" -doc <path> Path to technology documentation")
print(" -lef <path> Path to LEF file")
print(" -spice <path> Path to SPICE netlists")
print(" -cdl <path> Path to CDL netlists")
print(" -models <path> Path to SPICE (primitive device) models")
print(" -lib <path> Path to Liberty timing files")
print(" -liberty <path> Path to Liberty timing files")
print(" -gds <path> Path to GDS data")
print(" -verilog <path> Path to verilog models")
print(" -library <type> <name> [<target>] See below")
print("")
print(" All <path> names may be wild-carded with '*' ('glob'-style wild-cards)")
print("")
print(" All options with <path> other than source and target may take the additional")
print(" arguments 'up <number>', where <number> indicates the number of levels of")
print(" hierarchy of the source path to include when copying to the target.")
print("")
print(" Library <type> may be one of:")
print(" digital Digital standard cell library")
print(" primitive Primitive device library")
print(" general All other library types (I/O, analog, etc.)")
print("")
print(" If <target> is unspecified then <name> is used for the target.")
# Return a list of files after glob-style substituting into pathname. This
# mostly relies on glob.glob(), but uses the additional substitutions with
# escape strings:
#
# %v : Match a version number in the form "major[.minor[.rev]]"
# %l : substitute the library name
# %% : substitute the percent character verbatim
from distutils.version import LooseVersion
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def makeuserwritable(filepath):
if os.path.exists(filepath):
st = os.stat(filepath)
os.chmod(filepath, st.st_mode | stat.S_IWUSR)
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def substitute(pathname, library):
if library:
# Do %l substitution
newpathname = re.sub('%l', library, pathname)
else:
newpathname = pathname
if '%v' in newpathname:
vglob = re.sub('%v.*', '*', newpathname)
vlibs = glob.glob(vglob)
try:
vstr = vlibs[0][len(vglob)-1:]
except IndexError:
pass
else:
for vlib in vlibs[1:]:
vtest = vlib[len(vglob)-1:]
if LooseVersion(vtest) > LooseVersion(vstr):
vstr = vtest
newpathname = re.sub('%v', vstr, newpathname)
if '%%' in newpathname:
newpathname = re.sub('%%', '%', newpathname)
return newpathname
#----------------------------------------------------------------------------
#----------------------------------------------------------------------------
def get_gds_properties(magfile):
proprex = re.compile('^[ \t]*string[ \t]+(GDS_[^ \t]+)[ \t]+([^ \t]+)$')
proplines = []
if os.path.isfile(magfile):
with open(magfile, 'r') as ifile:
magtext = ifile.read().splitlines()
for line in magtext:
lmatch = proprex.match(line)
if lmatch:
propline = lmatch.group(1) + ' ' + lmatch.group(2)
proplines.append(propline)
return proplines
#----------------------------------------------------------------------------
# Read subcircuit ports from a CDL file, given a subcircuit name that should
# appear in the file as a subcircuit entry, and return a dictionary of ports
# and their indexes in the subcircuit line.
#----------------------------------------------------------------------------
def get_subckt_ports(cdlfile, subname):
portdict = {}
pidx = 1
portrex = re.compile('^\.subckt[ \t]+([^ \t]+)[ \t]+(.*)$', flags=re.IGNORECASE)
with open(cdlfile, 'r') as ifile:
cdltext = ifile.read()
cdllines = cdltext.replace('\n+', ' ').splitlines()
for line in cdllines:
lmatch = portrex.match(line)
if lmatch:
if lmatch.group(1).lower() == subname.lower():
ports = lmatch.group(2).split()
for port in ports:
portdict[port.lower()] = pidx
pidx += 1
break
return portdict
#----------------------------------------------------------------------------
# Filter a verilog file to remove any backslash continuation lines, which
# iverilog does not parse. If targetroot is a directory, then find and
# process all files in the path of targetroot. If any file to be processed
# is unmodified (has no backslash continuation lines), then ignore it. If
# any file is a symbolic link and gets modified, then remove the symbolic
# link before overwriting with the modified file.
#----------------------------------------------------------------------------
def vfilefilter(vfile):
modified = False
with open(vfile, 'r') as ifile:
vtext = ifile.read()
# Remove backslash-followed-by-newline and absorb initial whitespace. It
# is unclear what initial whitespace means in this context, as the use-
# case that has been seen seems to work under the assumption that leading
# whitespace is ignored up to the amount used by the last indentation.
vlines = re.sub('\\\\\n[ \t]*', '', vtext)
if vlines != vtext:
# File contents have been modified, so if this file was a symbolic
# link, then remove it. Otherwise, overwrite the file with the
# modified contents.
if os.path.islink(vfile):
os.unlink(vfile)
with open(vfile, 'w') as ofile:
ofile.write(vlines)
#----------------------------------------------------------------------------
# Run a filter on verilog files that cleans up known syntax issues.
# This is embedded in the foundry_install script and is not a custom
# filter largely because the issue is in the tool, not the PDK.
#----------------------------------------------------------------------------
def vfilter(targetroot):
if os.path.isfile(targetroot):
vfilefilter(targetroot)
else:
vlist = glob.glob(targetroot + '/*')
for vfile in vlist:
if os.path.isfile(vfile):
vfilefilter(vfile)
#----------------------------------------------------------------------------
# For issues that are PDK-specific, a script can be written and put in
# the PDK's custom/scripts/ directory, and passed to the foundry_install
# script using the "filter" option.
#----------------------------------------------------------------------------
def tfilter(targetroot, filterscript, ef_format=False, outfile=[]):
filterroot = os.path.split(filterscript)[1]
if os.path.isfile(targetroot):
print(' Filtering file ' + targetroot + ' with ' + filterroot)
sys.stdout.flush()
if not outfile:
outfile = targetroot
else:
# Make sure this file is writable (as the original may not be)
makeuserwritable(outfile)
if ef_format:
arguments = [filterscript, targetroot, outfile, '-ef_format']
else:
arguments = [filterscript, targetroot, outfile]
fproc = subprocess.run(arguments,
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, universal_newlines = True)
if fproc.stdout:
for line in fproc.stdout.splitlines():
print(line)
if fproc.stderr:
print('Error message output from filter script:')
for line in fproc.stderr.splitlines():
print(line)
else:
tlist = glob.glob(targetroot + '/*')
for tfile in tlist:
if os.path.isfile(tfile):
print(' Filtering file ' + tfile + ' with ' + filterroot)
sys.stdout.flush()
fproc = subprocess.run([filterscript, tfile, tfile],
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, universal_newlines = True)
if fproc.stdout:
for line in fproc.stdout.splitlines():
print(line)
if fproc.stderr:
print('Error message output from filter script:')
for line in fproc.stderr.splitlines():
print(line)
#----------------------------------------------------------------------------
# This is the main entry point for the foundry install script.
#----------------------------------------------------------------------------
if __name__ == '__main__':
if len(sys.argv) == 1:
print("No options given to foundry_install.py.")
usage()
sys.exit(0)
optionlist = []
newopt = []
sourcedir = None
targetdir = None
ef_format = False
do_clean = False
have_lef = False
have_techlef = False
have_lefanno = False
have_gds = False
have_spice = False
have_cdl = False
have_verilog = False
have_lib = False
# Break arguments into groups where the first word begins with "-".
# All following words not beginning with "-" are appended to the
# same list (optionlist). Then each optionlist is processed.
# Note that the first entry in optionlist has the '-' removed.
for option in sys.argv[1:]:
if option.find('-', 0) == 0:
if newopt != []:
optionlist.append(newopt)
newopt = []
newopt.append(option[1:])
else:
newopt.append(option)
if newopt != []:
optionlist.append(newopt)
# Pull library names from optionlist
libraries = []
for option in optionlist[:]:
if option[0] == 'library':
optionlist.remove(option)
libraries.append(option[1:])
# Check for option "ef_format" or "std_format" or "clean"
for option in optionlist[:]:
if option[0] == 'ef_naming' or option[0] == 'ef_names' or option[0] == 'ef_format':
optionlist.remove(option)
ef_format = True
elif option[0] == 'std_naming' or option[0] == 'std_names' or option[0] == 'std_format':
optionlist.remove(option)
ef_format = False
elif option[0] == 'clean':
do_clean = True
# Check for options "source" and "target"
for option in optionlist[:]:
if option[0] == 'source':
optionlist.remove(option)
if len(option) > 1:
sourcedir = option[1]
else:
print('Error: Option "source" used with no value.')
elif option[0] == 'target':
optionlist.remove(option)
if len(option) > 1:
targetdir = option[1]
else:
print('Error: Option "target" used with no value.')
if not targetdir:
print("No target directory specified. Exiting.")
sys.exit(1)
# Take the target PDK name from the target path last component
pdkname = os.path.split(targetdir)[1]
# If targetdir (the staging area) exists, make sure it's empty.
if os.path.isdir(targetdir):
# Error if targetdir exists but is not writeable
if not os.access(targetdir, os.W_OK):
print("Target installation directory " + targetdir + " is not writable.")
sys.exit(1)
# Clear out the staging directory if specified
if do_clean:
shutil.rmtree(targetdir)
elif os.path.exists(targetdir):
print("Target installation directory " + targetdir + " is not a directory.")
sys.exit(1)
# Error if no source or dest specified unless "-clean" was specified
if not sourcedir:
if do_clean:
print("Done removing staging area.")
sys.exit(0)
else:
print("No source directory specified. Exiting.")
sys.exit(1)
# Create the target directory
os.makedirs(targetdir, exist_ok=True)
# Here's where common scripts are found:
scriptdir = os.path.split(os.getcwd())[0] + '/common'
#----------------------------------------------------------------
# Installation part 1: Install files into the staging directory
#----------------------------------------------------------------
# Diagnostic
print("Installing in target (staging) directory " + targetdir)
# Create the top-level directories
os.makedirs(targetdir + '/libs.tech', exist_ok=True)
os.makedirs(targetdir + '/libs.ref', exist_ok=True)
# Path to magic techfile depends on ef_format
if ef_format == True:
mag_current = '/libs.tech/magic/current/'
else:
mag_current = '/libs.tech/magic/'
# Check for magic version and set flag if it does not exist or if
# it has the wrong version.
have_mag_8_2 = False
try:
mproc = subprocess.run(['magic', '--version'],
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
universal_newlines = True)
if mproc.stdout:
mag_version = mproc.stdout.splitlines()[0]
mag_version_info = mag_version.split('.')
try:
if int(mag_version_info[0]) > 8:
have_mag_8_2 = True
elif int(mag_version_info[0]) == 8:
if int(mag_version_info[1]) >= 2:
have_mag_8_2 = True
print('Magic version 8.2 (or better) available on the system.')
except ValueError:
print('Error: "magic --version" did not return valid version number.')
except FileNotFoundError:
print('Error: Failed to find executable for magic in standard search path.')
if not have_mag_8_2:
print('WARNING: Magic version 8.2 (or beter) cannot be executed ')
print('from the standard executable search path.')
print('Please install or correct the search path.')
print('Magic database files will not be created, and other missing file formats may not be generated.')
# Populate any targets that do not specify a library, or where the library is
# specified as "primitive".
# Populate the techLEF and SPICE models, if specified. Also, this section can add
# to any directory in libs.tech/ as given by the option; e.g., "-ngspice" will
# install into libs.tech/ngspice/.
if libraries == [] or 'primitive' in libraries[0]:
for option in optionlist[:]:
# Legacy behavior is to put libs.tech models and techLEF files in
# the same grouping as files for the primdev library (which go in
# libs.ref). Current behavior is to put all libs.tech files in
# a grouping with no library, with unrestricted ability to write
# into any subdirectory of libs.tech/. Therefore, need to restrict
# legacy use to just 'techlef' and 'models'.
if len(libraries) > 0 and 'primitive' in libraries[0]:
if option[0] != 'techlef' and option[0] != 'techLEF' and option[0] != 'models':
continue
# Normally technology LEF files are associated with IP libraries.
# However, if no library is specified or the library is 'primitive'
# (legacy behavior), then put in the techLEF directory with no subdirectory.
filter_scripts = []
if option[0] == 'techlef' or option[0] == 'techLEF':
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
if ef_format:
techlefdir = targetdir + '/libs.ref/' + 'techLEF'
else:
techlefdir = targetdir + '/libs.tech/lef'
os.makedirs(techlefdir, exist_ok=True)
# All techlef files should be copied, so use "glob" on the wildcards
techlist = glob.glob(substitute(sourcedir + '/' + option[1], None))
for lefname in techlist:
leffile = os.path.split(lefname)[1]
targname = techlefdir + '/' + leffile
if os.path.isfile(lefname):
shutil.copy(lefname, targname)
else:
shutil.copytree(lefname, targname)
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script, ef_format)
optionlist.remove(option)
# All remaining options will refer to specific tools (e.g., -ngspice, -magic)
# although generic names (.e.g, -models) are acceptable if the tools know
# where to find the files. Currently, most tools have their own formats
# and standards for setup, and so generally each install directory will be
# unique to one EDA tool.
else:
filter_scripts = []
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
print('Diagnostic: installing to ' + option[0] + '.')
tooldir = targetdir + '/libs.tech/' + option[0]
os.makedirs(tooldir, exist_ok=True)
# All files should be linked or copied, so use "glob" on
# the wildcards. Copy each file and recursively copy each
# directory.
toollist = glob.glob(substitute(sourcedir + '/' + option[1], None))
for toolname in toollist:
toolfile = os.path.split(toolname)[1]
targname = tooldir + '/' + toolfile
print(' installing from ' + toolfile + ' to ' + targname)
if os.path.isdir(toolname):
# Remove any existing directory, and its contents
if os.path.isdir(targname):
shutil.rmtree(targname)
os.makedirs(targname)
# Recursively find and copy or link the whole directory
# tree from this point.
alltoollist = glob.glob(toolname + '/**', recursive=True)
commonpart = os.path.commonpath(alltoollist)
for subtoolname in alltoollist:
# Get the path part that is not common between toollist and
# alltoollist.
subpart = os.path.relpath(subtoolname, commonpart)
subtargname = targname + '/' + subpart
if os.path.isfile(subtoolname):
os.makedirs(os.path.split(subtargname)[0], exist_ok=True)
shutil.copy(subtoolname, subtargname)
else:
print(' copy tree from ' + subtoolname + ' to ' + subtargname)
# emulate Python3.8 dirs_exist_ok option
try:
shutil.copytree(subtoolname, subtargname)
except FileExistsError:
pass
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(subtargname, filter_script, ef_format)
else:
# Remove any existing file
if os.path.isfile(targname):
os.remove(targname)
elif os.path.isdir(targname):
shutil.rmtree(targname)
if os.path.isfile(toolname):
shutil.copy(toolname, targname)
else:
shutil.copytree(toolname, targname)
for filter_script in filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script, ef_format)
optionlist.remove(option)
# Do an initial pass through all of the options and determine what is being
# installed, so that we know in advance which file formats are missing and
# need to be generated.
for option in optionlist[:]:
if option[0] == 'lef':
have_lefanno = True if 'annotate' in option or 'anno' in option else False
have_lef = True if not have_lefanno else False
if option[0] == 'techlef' or option[0] == 'techLEF':
have_techlef = True
elif option[0] == 'gds':
have_gds = True
elif option[0] == 'spice' or option[0] == 'spi':
have_spice = True
elif option[0] == 'cdl':
have_cdl = True
elif option[0] == 'verilog':
have_verilog = True
elif option[0] == 'lib' or option[0] == 'liberty':
have_lib = True
# The remaining options in optionlist should all be types like 'lef' or 'liberty'
# and there should be a corresponding library list specified by '-library'
for option in optionlist[:]:
# Ignore if no library list---should have been taken care of above.
if libraries == []:
break
# Diagnostic
print("Install option: " + str(option[0]))
if option[0] == 'lef' and have_lefanno:
print("LEF files used for annotation only. Temporary install.")
# For ef_format: always make techlef -> techLEF and spice -> spi
if ef_format:
if option[0] == 'techlef':
option[0] = 'techLEF'
elif option[0] == 'spice':
option[0] = 'spi'
destdir = targetdir + '/libs.ref/' + option[0]
os.makedirs(destdir, exist_ok=True)
# If the option is followed by the keyword "up" and a number, then
# the source should be copied (or linked) from <number> levels up
# in the hierarchy (see below).
hier_up = 0
for item in option:
if item.split('=')[0] == 'up':
hier_up = int(item.split('=')[1])
break
filter_scripts = []
for item in option:
if item.split('=')[0] == 'filter':
filter_scripts.append(item.split('=')[1])
break
# Option 'stub' applies to netlists ('cdl' or 'spice') and generates
# a file with only stub entries.
do_stub = 'stub' in option
# Option 'compile' is a standalone keyword ('comp' may be used).
do_compile = 'compile' in option or 'comp' in option
do_compile_only = 'compile-only' in option or 'comp-only' in option
# Option 'nospecify' is a standalone keyword ('nospec' may be used).
do_remove_spec = 'nospecify' in option or 'nospec' in option
# Option 'exclude' has an argument
excludelist = []
try:
excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
except IndexError:
pass
if len(excludelist) > 0:
print('Excluding files: ' + (',').join(excludelist))
# Option 'no-copy' has an argument
nocopylist = []
try:
nocopylist = list(item.split('=')[1].split(',') for item in option if item.startswith('no-copy'))[0]
except IndexError:
pass
if len(nocopylist) > 0:
print('Not copying files: ' + (',').join(nocopylist))
# Option 'include' has an argument
includelist = []
try:
includelist = list(item.split('=')[1].split(',') for item in option if item.startswith('incl'))[0]
except IndexError:
pass
if len(includelist) > 0:
print('Including files: ' + (',').join(includelist))
# Option 'rename' has an argument
try:
newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
except IndexError:
newname = None
else:
print('Renaming file to: ' + newname)
# Option 'sort' has an argument. . .
try:
sortscript = list(item.split('=')[1] for item in option if item.startswith('sort'))[0]
except IndexError:
# If option 'sort' is not specified, then use the "natural sort" script
sortscript = scriptdir + '/sort_pdkfiles.py'
else:
print('Sorting files with script ' + sortscript)
# For each library, create the library subdirectory
for library in libraries:
if len(library) == 3:
destlib = library[2]
else:
destlib = library[1]
if ef_format:
destlibdir = destdir + '/' + destlib
else:
destdir = targetdir + '/libs.ref/' + destlib + '/' + option[0]
destlibdir = destdir
os.makedirs(destlibdir, exist_ok=True)
# Populate the library subdirectory
# Parse the option and replace each '/*/' with the library name,
# and check if it is a valid directory name. Then glob the
# resulting option name. Warning: This assumes that all
# occurences of the text '/*/' match a library name. It should
# be possible to wild-card the directory name in such a way that
# this is always true.
testpath = substitute(sourcedir + '/' + option[1], library[1])
liblist = glob.glob(testpath)
# Create a file "sources.txt" (or append to it if it exists)
# and add the source directory name so that the staging install
# script can know where the files came from.
with open(destlibdir + '/sources.txt', 'a') as ofile:
print(testpath, file=ofile)
# Create exclude list with glob-style matching using fnmatch
if len(liblist) > 0:
liblistnames = list(os.path.split(item)[1] for item in liblist)
notliblist = []
for exclude in excludelist:
if '/' in exclude:
# Names come from files in a path that is not the source
excludefiles = os.listdir(os.path.split(exclude)[0])
pattern = os.path.split(exclude)[1]
notliblist.extend(fnmatch.filter(excludefiles, pattern))
else:
notliblist.extend(fnmatch.filter(liblistnames, exclude))
# Apply exclude list
if len(notliblist) > 0:
for file in liblist[:]:
if os.path.split(file)[1] in notliblist:
liblist.remove(file)
if len(excludelist) > 0 and len(notliblist) == 0:
print('Warning: Nothing from the exclude list found in sources.')
print('excludelist = ' + str(excludelist))
print('destlibdir = ' + destlibdir)
# Create a list of cell names not to be copied from "nocopylist"
nocopynames = []
for nocopy in nocopylist:
if '/' in nocopy:
# Names come from files in a path that is not the source
nocopyfiles = os.listdir(os.path.split(nocopy)[0])
pattern = os.path.split(nocopy)[1]
nocopynames.extend(fnmatch.filter(nocopyfiles, pattern))
else:
nocopynames.extend(fnmatch.filter(liblistnames, nocopy))
# Diagnostic
print('Collecting files from ' + testpath)
print('Files to install:')
if len(liblist) < 10:
for item in liblist:
print(' ' + item)
else:
for item in liblist[0:4]:
print(' ' + item)
print(' .')
print(' .')
print(' .')
for item in liblist[-6:-1]:
print(' ' + item)
print('(' + str(len(liblist)) + ' files total)')
destfilelist = []
for libname in liblist:
# Note that there may be a hierarchy to the files in option[1],
# say for liberty timing files under different conditions, so
# make sure directories have been created as needed.
libfile = os.path.split(libname)[1]
libfilepath = os.path.split(libname)[0]
destpathcomp = []
for i in range(hier_up):
destpathcomp.append('/' + os.path.split(libfilepath)[1])
libfilepath = os.path.split(libfilepath)[0]
destpathcomp.reverse()
destpath = ''.join(destpathcomp)
dontcopy = True if libfile in nocopynames else False
if option[0] == 'verilog':
fileext = '.v'
elif option[0] == 'liberty' or option[0] == 'lib':
fileext = '.lib'
elif option[0] == 'spice' or option[0] == 'spi':
fileext = '.spice' if not ef_format else '.spi'
elif option[0] == 'techlef':
fileext = '.lef'
else:
fileext = '.' + option[0]
if newname:
if os.path.splitext(newname)[1] == '':
newname = newname + fileext
if len(liblist) == 1:
destfile = newname
else:
if not do_compile and not do_compile_only:
print('Error: rename specified but more than one file found!')
destfile = libfile
else:
destfile = libfile
targname = destlibdir + destpath + '/' + destfile
# NOTE: When using "up" with link_from, could just make
# destpath itself a symbolic link; this way is more flexible
# but adds one symbolic link per file.
if destpath != '':
if not os.path.isdir(destlibdir + destpath):
os.makedirs(destlibdir + destpath, exist_ok=True)
# Remove any existing file
if os.path.isfile(targname):
if not dontcopy:
os.remove(targname)
elif os.path.isdir(targname):
if not dontcopy:
shutil.rmtree(targname)
# NOTE: Diagnostic, probably much too much output.
print(' Install:' + libname + ' to ' + targname)
if os.path.isfile(libname):
if not dontcopy:
shutil.copy(libname, targname)
else:
if not dontcopy:
shutil.copytree(libname, targname)
# File filtering options: Two options 'stub' and 'nospec' are
# handled by scripts in ../common/. Custom filters can also be
# specified.
local_filter_scripts = filter_scripts[:]
if option[0] == 'verilog':
# Internally handle syntactical issues with verilog and iverilog
vfilter(targname)
if do_remove_spec:
local_filter_scripts.append(scriptdir + '/remove_specify.py')
elif option[0] == 'cdl' or option[0] == 'spi' or option[0] == 'spice':
if do_stub:
local_filter_scripts.append(scriptdir + '/makestub.py')
for filter_script in local_filter_scripts:
# Apply filter script to all files in the target directory
tfilter(targname, filter_script, ef_format)
destfilelist.append(os.path.split(targname)[1])
# Add names from "include" list to destfilelist before writing
# filelist.txt for library file compiling.
includenames = []
for incname in includelist:
if '/' in incname:
# Names come from files in a path that is not the source
incfiles = os.listdir(os.path.split(incname)[0])
pattern = os.path.split(incname)[1]
destfilelist.extend(fnmatch.filter(incfiles, pattern))
else: