siliconcompiler 0.35.3__py3-none-any.whl → 0.35.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_issue.py +18 -2
- siliconcompiler/checklist.py +2 -1
- siliconcompiler/constraints/asic_component.py +49 -11
- siliconcompiler/constraints/asic_floorplan.py +23 -21
- siliconcompiler/constraints/asic_pins.py +55 -17
- siliconcompiler/constraints/asic_timing.py +53 -22
- siliconcompiler/constraints/fpga_timing.py +5 -6
- siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
- siliconcompiler/package/__init__.py +17 -6
- siliconcompiler/project.py +9 -1
- siliconcompiler/scheduler/docker.py +24 -25
- siliconcompiler/scheduler/scheduler.py +82 -68
- siliconcompiler/scheduler/schedulernode.py +133 -20
- siliconcompiler/scheduler/slurm.py +113 -29
- siliconcompiler/scheduler/taskscheduler.py +0 -7
- siliconcompiler/schema/editableschema.py +29 -0
- siliconcompiler/schema/parametervalue.py +14 -2
- siliconcompiler/schema_support/option.py +82 -1
- siliconcompiler/schema_support/pathschema.py +7 -13
- siliconcompiler/tool.py +47 -25
- siliconcompiler/tools/klayout/__init__.py +3 -0
- siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_export.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_utils.py +3 -4
- siliconcompiler/tools/openroad/__init__.py +27 -1
- siliconcompiler/tools/openroad/_apr.py +81 -4
- siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
- siliconcompiler/tools/openroad/global_placement.py +1 -0
- siliconcompiler/tools/openroad/init_floorplan.py +116 -7
- siliconcompiler/tools/openroad/power_grid_analysis.py +174 -0
- siliconcompiler/tools/openroad/repair_design.py +1 -0
- siliconcompiler/tools/openroad/repair_timing.py +1 -0
- siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +42 -4
- siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +146 -0
- siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -6
- siliconcompiler/tools/openroad/scripts/common/procs.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/reports.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/rcx/sc_rcx_bench.tcl +2 -4
- siliconcompiler/tools/opensta/__init__.py +1 -1
- siliconcompiler/tools/opensta/scripts/sc_timing.tcl +17 -12
- siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_place.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_route.tcl +11 -0
- siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +10 -0
- siliconcompiler/tools/vpr/__init__.py +28 -0
- siliconcompiler/tools/yosys/scripts/sc_screenshot.tcl +1 -1
- siliconcompiler/tools/yosys/scripts/sc_synth_asic.tcl +40 -4
- siliconcompiler/tools/yosys/scripts/sc_synth_fpga.tcl +15 -5
- siliconcompiler/tools/yosys/syn_asic.py +42 -0
- siliconcompiler/tools/yosys/syn_fpga.py +8 -0
- siliconcompiler/toolscripts/_tools.json +6 -6
- siliconcompiler/utils/__init__.py +243 -51
- siliconcompiler/utils/curation.py +89 -56
- siliconcompiler/utils/issue.py +6 -1
- siliconcompiler/utils/multiprocessing.py +35 -2
- siliconcompiler/utils/paths.py +21 -0
- siliconcompiler/utils/settings.py +141 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/METADATA +4 -3
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/RECORD +68 -65
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.3.dist-info → siliconcompiler-0.35.4.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"openroad": {
|
|
3
3
|
"git-url": "https://github.com/The-OpenROAD-Project/OpenROAD.git",
|
|
4
|
-
"git-commit": "
|
|
4
|
+
"git-commit": "dd56d50c413ecd215117898437c57bec68b59a87",
|
|
5
5
|
"docker-cmds": [
|
|
6
6
|
"# Remove OR-Tools files",
|
|
7
7
|
"RUN rm -f $SC_PREFIX/Makefile $SC_PREFIX/README.md",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"opensta": {
|
|
19
19
|
"git-url": "https://github.com/parallaxsw/OpenSTA.git",
|
|
20
|
-
"git-commit": "
|
|
20
|
+
"git-commit": "bd3efdc322c0677eb8e3d76f22ab297f7a6048b9",
|
|
21
21
|
"auto-update": true
|
|
22
22
|
},
|
|
23
23
|
"netgen": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"auto-update": false
|
|
42
42
|
},
|
|
43
43
|
"klayout": {
|
|
44
|
-
"version": "0.30.
|
|
44
|
+
"version": "0.30.5",
|
|
45
45
|
"git-url": "https://github.com/KLayout/klayout.git",
|
|
46
46
|
"auto-update": true,
|
|
47
47
|
"run-version": "source version.sh && echo $KLAYOUT_VERSION",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"sv2v": {
|
|
57
57
|
"git-url": "https://github.com/zachjs/sv2v.git",
|
|
58
|
-
"git-commit": "
|
|
58
|
+
"git-commit": "d3812098d8feb2d04cc4cb55e034a1f2338db02f",
|
|
59
59
|
"auto-update": true
|
|
60
60
|
},
|
|
61
61
|
"verilator": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
},
|
|
102
102
|
"yosys": {
|
|
103
103
|
"git-url": "https://github.com/YosysHQ/yosys.git",
|
|
104
|
-
"git-commit": "v0.
|
|
104
|
+
"git-commit": "v0.60",
|
|
105
105
|
"version-prefix": "",
|
|
106
106
|
"auto-update": true
|
|
107
107
|
},
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
},
|
|
146
146
|
"yosys-slang": {
|
|
147
147
|
"git-url": "https://github.com/povik/yosys-slang.git",
|
|
148
|
-
"git-commit": "
|
|
148
|
+
"git-commit": "875539b8cae5a2ac7c86fee43b1e38a743ee8659",
|
|
149
149
|
"docker-depends": "yosys",
|
|
150
150
|
"auto-update": true
|
|
151
151
|
},
|
|
@@ -28,6 +28,14 @@ if TYPE_CHECKING:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def link_symlink_copy(srcfile, dstfile):
|
|
31
|
+
"""
|
|
32
|
+
Attempts to link a source file to a destination using hard link, symbolic link,
|
|
33
|
+
or copy, in that order.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
srcfile (str): Path to the source file.
|
|
37
|
+
dstfile (str): Path to the destination file.
|
|
38
|
+
"""
|
|
31
39
|
# first try hard linking, then symbolic linking,
|
|
32
40
|
# and finally just copy the file
|
|
33
41
|
for method in [os.link, os.symlink, shutil.copy2]:
|
|
@@ -40,6 +48,14 @@ def link_symlink_copy(srcfile, dstfile):
|
|
|
40
48
|
|
|
41
49
|
|
|
42
50
|
def link_copy(srcfile, dstfile):
|
|
51
|
+
"""
|
|
52
|
+
Attempts to link a source file to a destination using hard link or copy,
|
|
53
|
+
in that order.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
srcfile (str): Path to the source file.
|
|
57
|
+
dstfile (str): Path to the destination file.
|
|
58
|
+
"""
|
|
43
59
|
# first try hard linking or just copy the file
|
|
44
60
|
for method in [os.link, shutil.copy2]:
|
|
45
61
|
try:
|
|
@@ -51,7 +67,15 @@ def link_copy(srcfile, dstfile):
|
|
|
51
67
|
|
|
52
68
|
|
|
53
69
|
def get_file_ext(filename: str) -> str:
|
|
54
|
-
|
|
70
|
+
"""
|
|
71
|
+
Get base file extension for a given path, disregarding .gz.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
filename (str): The filename to extract the extension from.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: The file extension (e.g., 'v', 'py') without the dot.
|
|
78
|
+
"""
|
|
55
79
|
if filename.lower().endswith('.gz'):
|
|
56
80
|
filename = os.path.splitext(filename)[0]
|
|
57
81
|
filetype = os.path.splitext(filename)[1].lower().lstrip('.')
|
|
@@ -60,7 +84,10 @@ def get_file_ext(filename: str) -> str:
|
|
|
60
84
|
|
|
61
85
|
def get_default_iomap() -> Dict[str, str]:
|
|
62
86
|
"""
|
|
63
|
-
|
|
87
|
+
Returns the default input file map for SC with filesets and extensions.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict[str, str]: A dictionary mapping file extensions to their SiliconCompiler type.
|
|
64
91
|
"""
|
|
65
92
|
|
|
66
93
|
# Record extensions:
|
|
@@ -175,26 +202,71 @@ def get_default_iomap() -> Dict[str, str]:
|
|
|
175
202
|
return default_iomap
|
|
176
203
|
|
|
177
204
|
|
|
205
|
+
def default_sc_dir() -> str:
|
|
206
|
+
"""
|
|
207
|
+
Returns the default SiliconCompiler configuration directory.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
str: The absolute path to the .sc directory in the user's home folder.
|
|
211
|
+
"""
|
|
212
|
+
return os.path.join(Path.home(), '.sc')
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def default_sc_path(path: str) -> str:
|
|
216
|
+
"""
|
|
217
|
+
Returns a path relative to the default SiliconCompiler configuration directory.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
path (str): The relative path to append to the default SC directory.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
str: The absolute path joined with the default SC directory.
|
|
224
|
+
"""
|
|
225
|
+
return os.path.join(default_sc_dir(), path)
|
|
226
|
+
|
|
227
|
+
|
|
178
228
|
def default_credentials_file() -> str:
|
|
179
|
-
|
|
229
|
+
"""
|
|
230
|
+
Returns the default path for the SiliconCompiler credentials file.
|
|
180
231
|
|
|
181
|
-
|
|
232
|
+
Returns:
|
|
233
|
+
str: The absolute path to the credentials file.
|
|
234
|
+
"""
|
|
235
|
+
return default_sc_path('credentials')
|
|
182
236
|
|
|
183
237
|
|
|
184
238
|
def default_cache_dir() -> str:
|
|
185
|
-
|
|
239
|
+
"""
|
|
240
|
+
Returns the default path for the SiliconCompiler cache directory.
|
|
186
241
|
|
|
187
|
-
|
|
242
|
+
Returns:
|
|
243
|
+
str: The absolute path to the cache directory.
|
|
244
|
+
"""
|
|
245
|
+
return default_sc_path('cache')
|
|
188
246
|
|
|
189
247
|
|
|
190
248
|
def default_email_credentials_file() -> str:
|
|
191
|
-
|
|
249
|
+
"""
|
|
250
|
+
Returns the default path for the SiliconCompiler email credentials file.
|
|
192
251
|
|
|
193
|
-
|
|
252
|
+
Returns:
|
|
253
|
+
str: The absolute path to the email.json file.
|
|
254
|
+
"""
|
|
255
|
+
return default_sc_path('email.json')
|
|
194
256
|
|
|
195
257
|
|
|
196
258
|
@contextlib.contextmanager
|
|
197
259
|
def sc_open(path: str, *args, **kwargs):
|
|
260
|
+
"""
|
|
261
|
+
A context manager for opening files with default settings convenient for SC.
|
|
262
|
+
|
|
263
|
+
Sets ``errors='ignore'`` and ``newline='\n'`` by default unless overridden.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
path (str): The file path to open.
|
|
267
|
+
*args: Positional arguments passed to the builtin open().
|
|
268
|
+
**kwargs: Keyword arguments passed to the builtin open().
|
|
269
|
+
"""
|
|
198
270
|
if 'errors' not in kwargs:
|
|
199
271
|
kwargs['errors'] = 'ignore'
|
|
200
272
|
kwargs["newline"] = "\n"
|
|
@@ -212,6 +284,17 @@ def get_file_template(path: str,
|
|
|
212
284
|
os.path.dirname(os.path.abspath(__file__))),
|
|
213
285
|
'data',
|
|
214
286
|
'templates')) -> Template:
|
|
287
|
+
"""
|
|
288
|
+
Retrieves a Jinja2 template object for the specified file.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
path (str): The name of the template file or an absolute path to it.
|
|
292
|
+
root (str, optional): The root directory to search for templates if path
|
|
293
|
+
is relative. Defaults to the SiliconCompiler data/templates directory.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Template: The loaded Jinja2 Template object.
|
|
297
|
+
"""
|
|
215
298
|
if os.path.isabs(path):
|
|
216
299
|
root = os.path.dirname(path)
|
|
217
300
|
path = os.path.basename(path)
|
|
@@ -225,6 +308,20 @@ def get_file_template(path: str,
|
|
|
225
308
|
|
|
226
309
|
#######################################
|
|
227
310
|
def safecompare(value: Union[int, float], op: str, goal: Union[int, float]) -> bool:
|
|
311
|
+
"""
|
|
312
|
+
Compares a value against a goal using a string operator.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
value (Union[int, float]): The value to compare.
|
|
316
|
+
op (str): The comparison operator ('>', '>=', '<', '<=', '==', '!=').
|
|
317
|
+
goal (Union[int, float]): The target value to compare against.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
bool: The result of the comparison.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
ValueError: If the provided operator is not supported.
|
|
324
|
+
"""
|
|
228
325
|
# supported relational operations
|
|
229
326
|
# >, >=, <=, <. ==, !=
|
|
230
327
|
if op == ">":
|
|
@@ -244,75 +341,127 @@ def safecompare(value: Union[int, float], op: str, goal: Union[int, float]) -> b
|
|
|
244
341
|
|
|
245
342
|
|
|
246
343
|
###########################################################################
|
|
247
|
-
def grep(
|
|
344
|
+
def grep(logger: logging.Logger, args: str, line: str) -> Union[None, str]:
|
|
248
345
|
"""
|
|
249
346
|
Emulates the Unix grep command on a string.
|
|
250
347
|
|
|
251
|
-
Emulates the behavior of the Unix grep command that is etched into
|
|
252
|
-
our muscle memory. Partially implemented, not all features supported.
|
|
253
|
-
The function returns None if no match is found.
|
|
254
|
-
|
|
255
348
|
Args:
|
|
256
|
-
|
|
257
|
-
|
|
349
|
+
logger (logging.Logger): used for logging errors.
|
|
350
|
+
args (str): Command line arguments for grep command.
|
|
351
|
+
line (str): Line to process.
|
|
258
352
|
|
|
259
353
|
Returns:
|
|
260
|
-
Result of grep command (string).
|
|
261
|
-
|
|
354
|
+
Union[None, str]: Result of grep command (string) or None if no match.
|
|
262
355
|
"""
|
|
263
356
|
|
|
264
357
|
# Quick return if input is None
|
|
265
358
|
if line is None:
|
|
266
359
|
return None
|
|
267
360
|
|
|
268
|
-
#
|
|
361
|
+
# --- 1. Initialize Options and Parse Arguments ---
|
|
269
362
|
options = {
|
|
270
363
|
'-v': False, # Invert the sense of matching
|
|
271
|
-
'-i': False, # Ignore case distinctions
|
|
272
|
-
'-E': False, #
|
|
273
|
-
'-e': False, #
|
|
274
|
-
'-x': False, #
|
|
275
|
-
'-o': False, # Print only the match
|
|
276
|
-
'-w': False} #
|
|
364
|
+
'-i': False, # Ignore case distinctions
|
|
365
|
+
'-E': False, # Extended regular expressions (Python's 're' module is ERE by default)
|
|
366
|
+
'-e': False, # Pattern starts with '-' (simplified logic)
|
|
367
|
+
'-x': False, # Exact line match
|
|
368
|
+
'-o': False, # Print only the match
|
|
369
|
+
'-w': False} # Whole word match
|
|
370
|
+
|
|
371
|
+
parts = args.split()
|
|
372
|
+
pattern = ""
|
|
373
|
+
pattern_start_index = -1
|
|
374
|
+
|
|
375
|
+
# Identify switches and the start of the pattern
|
|
376
|
+
for i, part in enumerate(parts):
|
|
377
|
+
# Check for switch starting with '-' and not just '-'
|
|
378
|
+
if part.startswith('-') and len(part) > 1 and part != '-e':
|
|
379
|
+
# Handle concatenated switches (e.g., -vi)
|
|
380
|
+
is_valid_switch_group = True
|
|
381
|
+
for char in part[1:]:
|
|
382
|
+
switch = f"-{char}"
|
|
383
|
+
if switch in options:
|
|
384
|
+
options[switch] = True
|
|
385
|
+
else:
|
|
386
|
+
logger.error(f"Unknown switch: {switch}")
|
|
387
|
+
is_valid_switch_group = False
|
|
388
|
+
break
|
|
389
|
+
if not is_valid_switch_group:
|
|
390
|
+
# If an invalid switch was found, the rest must be the pattern
|
|
391
|
+
pattern_start_index = i
|
|
392
|
+
break
|
|
393
|
+
elif part == '-e':
|
|
394
|
+
# The next part is the pattern, regardless of what it looks like
|
|
395
|
+
options['-e'] = True
|
|
396
|
+
if i + 1 < len(parts):
|
|
397
|
+
pattern_start_index = i + 1
|
|
398
|
+
break
|
|
399
|
+
elif not pattern.strip():
|
|
400
|
+
# First non-switch part is the start of the pattern
|
|
401
|
+
pattern_start_index = i
|
|
402
|
+
break
|
|
277
403
|
|
|
278
|
-
#
|
|
279
|
-
|
|
404
|
+
# Assemble the pattern from the determined starting index
|
|
405
|
+
if pattern_start_index != -1:
|
|
406
|
+
pattern = " ".join(parts[pattern_start_index:])
|
|
280
407
|
|
|
281
|
-
if not
|
|
408
|
+
if not pattern:
|
|
282
409
|
return None
|
|
283
410
|
|
|
284
|
-
|
|
411
|
+
# --- 2. Prepare Regex Flags and Pattern Modifiers ---
|
|
285
412
|
|
|
286
|
-
|
|
287
|
-
|
|
413
|
+
regex_flags = 0
|
|
414
|
+
if options['-i']:
|
|
415
|
+
regex_flags |= re.IGNORECASE
|
|
288
416
|
|
|
289
|
-
#
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
417
|
+
# Apply Whole Word (-w)
|
|
418
|
+
pattern_to_search = pattern
|
|
419
|
+
if options['-w']:
|
|
420
|
+
# Apply word boundaries
|
|
421
|
+
pattern_to_search = rf"\b({pattern})\b"
|
|
422
|
+
|
|
423
|
+
# Apply Whole Line (-x)
|
|
424
|
+
if options['-x']:
|
|
425
|
+
# Exact line match, using the prepared pattern (which may have \b already)
|
|
426
|
+
pattern_to_search = rf"^{pattern_to_search}$"
|
|
427
|
+
|
|
428
|
+
# --- 3. Perform Search ---
|
|
429
|
+
try:
|
|
430
|
+
# re.search is used to find the pattern anywhere in the line
|
|
431
|
+
match = re.search(pattern_to_search, line, regex_flags)
|
|
432
|
+
except re.error as e:
|
|
433
|
+
# Handle cases where the pattern itself is invalid regex
|
|
434
|
+
logger.error(f"Invalid regex pattern '{pattern}': {e}")
|
|
306
435
|
return None
|
|
436
|
+
|
|
437
|
+
# --- 4. Handle Inversion (-v) and Final Return ---
|
|
438
|
+
|
|
439
|
+
# Check if a result should be returned: (Match found) XOR (Invert is on)
|
|
440
|
+
should_return = bool(match) != options["-v"]
|
|
441
|
+
|
|
442
|
+
if should_return:
|
|
443
|
+
if options['-o'] and match:
|
|
444
|
+
# Print only the match part (which is match.group(0))
|
|
445
|
+
return match.group(0)
|
|
446
|
+
else:
|
|
447
|
+
# Return the whole line (standard behavior)
|
|
448
|
+
return line
|
|
307
449
|
else:
|
|
308
|
-
|
|
450
|
+
# Match found AND inverted, OR Match NOT found AND not inverted
|
|
451
|
+
return None
|
|
309
452
|
|
|
310
453
|
|
|
311
454
|
def get_plugins(system: str, name: Optional[str] = None) -> List[Callable]:
|
|
312
455
|
'''
|
|
313
|
-
Search for python modules with a specific function
|
|
314
|
-
'''
|
|
456
|
+
Search for python modules with a specific function.
|
|
315
457
|
|
|
458
|
+
Args:
|
|
459
|
+
system (str): The system/group to search within (e.g. 'siliconcompiler.metrics').
|
|
460
|
+
name (str, optional): Specific plugin name to filter by.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
List[Callable]: A list of loaded plugin functions.
|
|
464
|
+
'''
|
|
316
465
|
plugins = []
|
|
317
466
|
discovered_plugins = entry_points(group=f'siliconcompiler.{system}')
|
|
318
467
|
for plugin in discovered_plugins:
|
|
@@ -326,6 +475,19 @@ def get_plugins(system: str, name: Optional[str] = None) -> List[Callable]:
|
|
|
326
475
|
|
|
327
476
|
|
|
328
477
|
def truncate_text(text: str, width: int) -> str:
|
|
478
|
+
"""
|
|
479
|
+
Truncates text to a specific width, replacing the middle with '...'.
|
|
480
|
+
|
|
481
|
+
Attempts to preserve the end of the string if it appears to be a version number
|
|
482
|
+
or numeric index.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
text (str): The text to truncate.
|
|
486
|
+
width (int): The maximum desired width of the text.
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
str: The truncated text.
|
|
490
|
+
"""
|
|
329
491
|
width = max(width, 5)
|
|
330
492
|
|
|
331
493
|
if len(text) <= width:
|
|
@@ -349,7 +511,10 @@ def get_cores(physical: bool = False) -> int:
|
|
|
349
511
|
Get max number of cores for this machine.
|
|
350
512
|
|
|
351
513
|
Args:
|
|
352
|
-
physical (
|
|
514
|
+
physical (bool): if true, only count physical cores. Defaults to False.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
int: The number of available cores. Defaults to 1 if detection fails.
|
|
353
518
|
'''
|
|
354
519
|
|
|
355
520
|
cores = psutil.cpu_count(logical=not physical)
|
|
@@ -367,6 +532,13 @@ def get_cores(physical: bool = False) -> int:
|
|
|
367
532
|
|
|
368
533
|
|
|
369
534
|
def print_traceback(logger: logging.Logger, exception: Exception):
|
|
535
|
+
"""
|
|
536
|
+
Prints the full traceback of an exception to the provided logger.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
logger (logging.Logger): The logger instance to write the traceback to.
|
|
540
|
+
exception (Exception): The exception to log.
|
|
541
|
+
"""
|
|
370
542
|
logger.error(f'{exception}')
|
|
371
543
|
trace = StringIO()
|
|
372
544
|
traceback.print_tb(exception.__traceback__, file=trace)
|
|
@@ -376,6 +548,13 @@ def print_traceback(logger: logging.Logger, exception: Exception):
|
|
|
376
548
|
|
|
377
549
|
|
|
378
550
|
class FilterDirectories:
|
|
551
|
+
"""
|
|
552
|
+
A helper class to filter directories and files during file collection.
|
|
553
|
+
|
|
554
|
+
This class prevents collecting files from the home directory, the build directory,
|
|
555
|
+
and hidden files (on Linux, Windows, and macOS). It also enforces a limit on the
|
|
556
|
+
number of files collected.
|
|
557
|
+
"""
|
|
379
558
|
def __init__(self, project: "Project"):
|
|
380
559
|
self.file_count = 0
|
|
381
560
|
self.directory_file_limit = None
|
|
@@ -391,6 +570,19 @@ class FilterDirectories:
|
|
|
391
570
|
return builddir(self.project)
|
|
392
571
|
|
|
393
572
|
def filter(self, path: str, files: List[str]) -> List[str]:
|
|
573
|
+
"""
|
|
574
|
+
Filters a list of files based on the current directory path.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
path (str): The current directory path being traversed.
|
|
578
|
+
files (List[str]): The list of filenames in that directory.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
List[str]: A list of filenames that should be excluded (i.e., hidden files)
|
|
582
|
+
or handled specifically. Note that despite the name returning 'hidden_files',
|
|
583
|
+
the logic is essentially identifying what to *exclude* from the main processing
|
|
584
|
+
loop in some contexts, or identifying hidden files to ignore.
|
|
585
|
+
"""
|
|
394
586
|
if pathlib.Path(path) == pathlib.Path.home():
|
|
395
587
|
# refuse to collect home directory
|
|
396
588
|
self.logger.error(f'Cannot collect user home directory: {path}')
|
|
@@ -5,9 +5,10 @@ import os.path
|
|
|
5
5
|
|
|
6
6
|
from typing import List, Optional, TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from siliconcompiler.schema import BaseSchema, Parameter
|
|
8
9
|
from siliconcompiler.schema.parametervalue import NodeListValue, NodeSetValue
|
|
9
10
|
from siliconcompiler.utils import FilterDirectories
|
|
10
|
-
from siliconcompiler.utils.paths import collectiondir
|
|
11
|
+
from siliconcompiler.utils.paths import collectiondir, cwdir
|
|
11
12
|
from siliconcompiler.scheduler import SchedulerNode
|
|
12
13
|
from siliconcompiler.flowgraph import RuntimeFlowgraph
|
|
13
14
|
|
|
@@ -43,16 +44,42 @@ def collect(project: "Project",
|
|
|
43
44
|
|
|
44
45
|
if not directory:
|
|
45
46
|
directory = collectiondir(project)
|
|
47
|
+
if not directory:
|
|
48
|
+
raise ValueError("unable to determine collection directory")
|
|
49
|
+
|
|
46
50
|
directory = os.path.abspath(directory)
|
|
47
51
|
|
|
48
|
-
#
|
|
52
|
+
# Move existing directory
|
|
53
|
+
prev_dir = None
|
|
49
54
|
if os.path.exists(directory):
|
|
50
|
-
|
|
55
|
+
prev_dir = os.path.join(os.path.dirname(directory), "sc_previous_collection")
|
|
56
|
+
os.rename(directory, prev_dir)
|
|
51
57
|
os.makedirs(directory)
|
|
52
58
|
|
|
53
59
|
if verbose:
|
|
54
60
|
project.logger.info(f'Collecting files to: {directory}')
|
|
55
61
|
|
|
62
|
+
cwd = cwdir(project)
|
|
63
|
+
|
|
64
|
+
def find_files(*key, step: Optional[str] = None, index: Optional[str] = None):
|
|
65
|
+
"""
|
|
66
|
+
Find the files in the filesystem, otherwise look in previous collection
|
|
67
|
+
"""
|
|
68
|
+
e = None
|
|
69
|
+
try:
|
|
70
|
+
return BaseSchema._find_files(project, *key, step=step, index=index,
|
|
71
|
+
cwd=cwd,
|
|
72
|
+
collection_dir=directory)
|
|
73
|
+
except FileNotFoundError as err:
|
|
74
|
+
e = err
|
|
75
|
+
if prev_dir:
|
|
76
|
+
# Try previous location next
|
|
77
|
+
return BaseSchema._find_files(project, *key, step=step, index=index,
|
|
78
|
+
cwd=cwd,
|
|
79
|
+
collection_dir=prev_dir)
|
|
80
|
+
if e:
|
|
81
|
+
raise e from None
|
|
82
|
+
|
|
56
83
|
dirs = {}
|
|
57
84
|
files = {}
|
|
58
85
|
|
|
@@ -75,17 +102,18 @@ def collect(project: "Project",
|
|
|
75
102
|
# skip flow files files from builds
|
|
76
103
|
continue
|
|
77
104
|
|
|
78
|
-
|
|
105
|
+
param: Parameter = project.get(*key, field=None)
|
|
106
|
+
leaftype: str = param.get(field='type')
|
|
79
107
|
is_dir = "dir" in leaftype
|
|
80
108
|
is_file = "file" in leaftype
|
|
81
109
|
|
|
82
110
|
if not is_dir and not is_file:
|
|
83
111
|
continue
|
|
84
112
|
|
|
85
|
-
if not
|
|
113
|
+
if not param.get(field='copy'):
|
|
86
114
|
continue
|
|
87
115
|
|
|
88
|
-
for values, step, index in
|
|
116
|
+
for values, step, index in param.getvalues(return_values=False):
|
|
89
117
|
if not values.has_value:
|
|
90
118
|
continue
|
|
91
119
|
|
|
@@ -99,73 +127,78 @@ def collect(project: "Project",
|
|
|
99
127
|
else:
|
|
100
128
|
files[(key, step, index)] = values
|
|
101
129
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
130
|
+
try:
|
|
131
|
+
path_filter = FilterDirectories(project)
|
|
132
|
+
for key, step, index in sorted(dirs.keys()):
|
|
133
|
+
abs_paths = find_files(*key, step=step, index=index)
|
|
105
134
|
|
|
106
|
-
|
|
135
|
+
new_paths = set()
|
|
107
136
|
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
138
|
+
abs_paths = [abs_paths]
|
|
110
139
|
|
|
111
|
-
|
|
112
|
-
|
|
140
|
+
abs_paths = zip(abs_paths, dirs[(key, step, index)])
|
|
141
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
113
142
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
143
|
+
for abs_path, value in abs_paths:
|
|
144
|
+
if not abs_path:
|
|
145
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
117
146
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
if abs_path.startswith(directory):
|
|
148
|
+
# File already imported in directory
|
|
149
|
+
continue
|
|
121
150
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
151
|
+
imported = False
|
|
152
|
+
for new_path in new_paths:
|
|
153
|
+
if abs_path.startswith(new_path):
|
|
154
|
+
imported = True
|
|
155
|
+
break
|
|
156
|
+
if imported:
|
|
157
|
+
continue
|
|
129
158
|
|
|
130
|
-
|
|
159
|
+
new_paths.add(abs_path)
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
162
|
+
if os.path.exists(import_path):
|
|
163
|
+
continue
|
|
135
164
|
|
|
136
|
-
|
|
137
|
-
|
|
165
|
+
if whitelist is not None and abs_path not in whitelist:
|
|
166
|
+
raise RuntimeError(f'{abs_path} is not on the approved collection list.')
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
if verbose:
|
|
169
|
+
project.logger.info(f" Collecting directory: {abs_path}")
|
|
170
|
+
path_filter.abspath = abs_path
|
|
171
|
+
shutil.copytree(abs_path, import_path, ignore=path_filter.filter)
|
|
172
|
+
path_filter.abspath = None
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
174
|
+
for key, step, index in sorted(files.keys()):
|
|
175
|
+
abs_paths = find_files(*key, step=step, index=index)
|
|
147
176
|
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
if not isinstance(abs_paths, (list, tuple, set)):
|
|
178
|
+
abs_paths = [abs_paths]
|
|
150
179
|
|
|
151
|
-
|
|
152
|
-
|
|
180
|
+
abs_paths = zip(abs_paths, files[(key, step, index)])
|
|
181
|
+
abs_paths = sorted(abs_paths, key=lambda p: p[0])
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
for abs_path, value in abs_paths:
|
|
184
|
+
if not abs_path:
|
|
185
|
+
raise FileNotFoundError(f"{value.get()} could not be copied")
|
|
157
186
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
187
|
+
if abs_path.startswith(directory):
|
|
188
|
+
# File already imported in directory
|
|
189
|
+
continue
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
191
|
+
import_path = os.path.join(directory, value.get_hashed_filename())
|
|
192
|
+
if os.path.exists(import_path):
|
|
193
|
+
continue
|
|
165
194
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
195
|
+
if verbose:
|
|
196
|
+
project.logger.info(f" Collecting file: {abs_path}")
|
|
197
|
+
shutil.copy2(abs_path, import_path)
|
|
198
|
+
finally:
|
|
199
|
+
if prev_dir:
|
|
200
|
+
# Delete existing directory
|
|
201
|
+
shutil.rmtree(prev_dir)
|
|
169
202
|
|
|
170
203
|
|
|
171
204
|
def archive(project: "Project",
|