siliconcompiler 0.34.2__py3-none-any.whl → 0.34.3__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.
Files changed (121) hide show
  1. siliconcompiler/__init__.py +12 -5
  2. siliconcompiler/__main__.py +1 -7
  3. siliconcompiler/_metadata.py +1 -1
  4. siliconcompiler/apps/_common.py +104 -23
  5. siliconcompiler/apps/sc.py +4 -8
  6. siliconcompiler/apps/sc_dashboard.py +6 -4
  7. siliconcompiler/apps/sc_install.py +10 -6
  8. siliconcompiler/apps/sc_issue.py +7 -5
  9. siliconcompiler/apps/sc_remote.py +1 -1
  10. siliconcompiler/apps/sc_server.py +9 -14
  11. siliconcompiler/apps/sc_show.py +6 -5
  12. siliconcompiler/apps/smake.py +130 -94
  13. siliconcompiler/apps/utils/replay.py +4 -7
  14. siliconcompiler/apps/utils/summarize.py +3 -5
  15. siliconcompiler/asic.py +420 -0
  16. siliconcompiler/checklist.py +25 -2
  17. siliconcompiler/cmdlineschema.py +534 -0
  18. siliconcompiler/constraints/asic_component.py +2 -2
  19. siliconcompiler/constraints/asic_pins.py +2 -2
  20. siliconcompiler/constraints/asic_timing.py +3 -3
  21. siliconcompiler/core.py +7 -32
  22. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
  23. siliconcompiler/dependencyschema.py +89 -31
  24. siliconcompiler/design.py +176 -207
  25. siliconcompiler/filesetschema.py +250 -0
  26. siliconcompiler/flowgraph.py +274 -95
  27. siliconcompiler/fpga.py +124 -1
  28. siliconcompiler/library.py +218 -20
  29. siliconcompiler/metric.py +233 -20
  30. siliconcompiler/package/__init__.py +271 -50
  31. siliconcompiler/package/git.py +92 -16
  32. siliconcompiler/package/github.py +108 -12
  33. siliconcompiler/package/https.py +79 -16
  34. siliconcompiler/packageschema.py +88 -7
  35. siliconcompiler/pathschema.py +31 -2
  36. siliconcompiler/pdk.py +566 -1
  37. siliconcompiler/project.py +1095 -94
  38. siliconcompiler/record.py +38 -1
  39. siliconcompiler/remote/__init__.py +5 -2
  40. siliconcompiler/remote/client.py +11 -6
  41. siliconcompiler/remote/schema.py +5 -23
  42. siliconcompiler/remote/server.py +41 -54
  43. siliconcompiler/report/__init__.py +3 -3
  44. siliconcompiler/report/dashboard/__init__.py +48 -14
  45. siliconcompiler/report/dashboard/cli/__init__.py +99 -21
  46. siliconcompiler/report/dashboard/cli/board.py +364 -179
  47. siliconcompiler/report/dashboard/web/__init__.py +90 -12
  48. siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
  49. siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
  50. siliconcompiler/report/dashboard/web/components/graph.py +139 -100
  51. siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
  52. siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
  53. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
  54. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
  55. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
  56. siliconcompiler/report/dashboard/web/state.py +141 -14
  57. siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
  58. siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
  59. siliconcompiler/report/dashboard/web/viewer.py +25 -1
  60. siliconcompiler/report/report.py +5 -2
  61. siliconcompiler/report/summary_image.py +29 -11
  62. siliconcompiler/scheduler/__init__.py +9 -1
  63. siliconcompiler/scheduler/docker.py +79 -1
  64. siliconcompiler/scheduler/run_node.py +35 -19
  65. siliconcompiler/scheduler/scheduler.py +208 -24
  66. siliconcompiler/scheduler/schedulernode.py +372 -46
  67. siliconcompiler/scheduler/send_messages.py +77 -29
  68. siliconcompiler/scheduler/slurm.py +76 -12
  69. siliconcompiler/scheduler/taskscheduler.py +140 -20
  70. siliconcompiler/schema/__init__.py +0 -2
  71. siliconcompiler/schema/baseschema.py +194 -38
  72. siliconcompiler/schema/journal.py +7 -4
  73. siliconcompiler/schema/namedschema.py +16 -10
  74. siliconcompiler/schema/parameter.py +55 -9
  75. siliconcompiler/schema/parametervalue.py +60 -0
  76. siliconcompiler/schema/safeschema.py +25 -2
  77. siliconcompiler/schema/schema_cfg.py +5 -5
  78. siliconcompiler/schema/utils.py +2 -2
  79. siliconcompiler/schema_obj.py +20 -3
  80. siliconcompiler/tool.py +979 -302
  81. siliconcompiler/tools/bambu/__init__.py +41 -0
  82. siliconcompiler/tools/builtin/concatenate.py +2 -2
  83. siliconcompiler/tools/builtin/minimum.py +2 -1
  84. siliconcompiler/tools/builtin/mux.py +2 -1
  85. siliconcompiler/tools/builtin/nop.py +2 -1
  86. siliconcompiler/tools/builtin/verify.py +2 -1
  87. siliconcompiler/tools/klayout/__init__.py +95 -0
  88. siliconcompiler/tools/openroad/__init__.py +289 -0
  89. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
  90. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
  92. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
  93. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
  94. siliconcompiler/tools/slang/__init__.py +1 -1
  95. siliconcompiler/tools/slang/elaborate.py +2 -1
  96. siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
  97. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
  98. siliconcompiler/tools/vivado/syn_fpga.py +6 -0
  99. siliconcompiler/tools/vivado/vivado.py +35 -2
  100. siliconcompiler/tools/vpr/__init__.py +150 -0
  101. siliconcompiler/tools/yosys/__init__.py +369 -1
  102. siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
  103. siliconcompiler/toolscripts/_tools.json +5 -10
  104. siliconcompiler/utils/__init__.py +66 -0
  105. siliconcompiler/utils/flowgraph.py +2 -2
  106. siliconcompiler/utils/issue.py +2 -1
  107. siliconcompiler/utils/logging.py +14 -0
  108. siliconcompiler/utils/multiprocessing.py +256 -0
  109. siliconcompiler/utils/showtools.py +10 -0
  110. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +5 -5
  111. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +115 -118
  112. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
  113. siliconcompiler/schema/cmdlineschema.py +0 -250
  114. siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
  115. siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
  116. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
  117. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
  118. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
  119. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
  120. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
  121. {siliconcompiler-0.34.2.dist-info → siliconcompiler-0.34.3.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,10 @@
1
+ import io
2
+ import logging
1
3
  import os
2
4
  import re
3
5
  import shutil
4
6
  import sys
7
+ import traceback
5
8
 
6
9
  import os.path
7
10
 
@@ -9,19 +12,42 @@ from siliconcompiler import Schema
9
12
  from siliconcompiler import NodeStatus
10
13
  from siliconcompiler.schema import Journal
11
14
  from siliconcompiler.flowgraph import RuntimeFlowgraph
12
- from siliconcompiler.scheduler.schedulernode import SchedulerNode
13
- from siliconcompiler.scheduler.slurm import SlurmSchedulerNode
14
- from siliconcompiler.scheduler.docker import DockerSchedulerNode
15
- from siliconcompiler.scheduler.taskscheduler import TaskScheduler
15
+ from siliconcompiler.scheduler import SchedulerNode
16
+ from siliconcompiler.scheduler import SlurmSchedulerNode
17
+ from siliconcompiler.scheduler import DockerSchedulerNode
18
+ from siliconcompiler.scheduler import TaskScheduler
16
19
 
17
20
  from siliconcompiler import utils
21
+ from siliconcompiler.utils.logging import SCLoggerFormatter
22
+ from siliconcompiler.utils.multiprocessing import MPManager
18
23
  from siliconcompiler.scheduler import send_messages
19
24
 
20
25
 
21
26
  class Scheduler:
27
+ """
28
+ A class for orchestrating and executing a compilation flowgraph.
29
+
30
+ The Scheduler is responsible for managing the entire lifecycle of a compilation
31
+ run. It interprets the flowgraph defined in the Chip object, determines which
32
+ nodes (steps) need to be run based on user settings (like 'from', 'to') and
33
+ the state of previous runs, and then executes the tasks in the correct order.
34
+
35
+ It handles setting up individual task nodes, managing dependencies, logging,
36
+ and reporting results.
37
+ """
38
+
22
39
  def __init__(self, chip):
40
+ """
41
+ Initializes the Scheduler.
42
+
43
+ Args:
44
+ chip (Chip): The Chip object containing the configuration and flowgraph.
45
+
46
+ Raises:
47
+ ValueError: If the specified flow is not defined or fails validation.
48
+ """
23
49
  self.__chip = chip
24
- self.__logger = chip.logger
50
+ self.__logger: logging.Logger = chip.logger
25
51
  self.__name = chip.design
26
52
 
27
53
  flow = self.__chip.get("option", "flow")
@@ -37,14 +63,14 @@ class Scheduler:
37
63
  prune_nodes = self.__chip.get('option', 'prune')
38
64
 
39
65
  if not self.__flow.validate(logger=self.__logger):
40
- raise ValueError(f"{self.__flow.name()} flowgraph contains errors and cannot be run.")
66
+ raise ValueError(f"{self.__flow.name} flowgraph contains errors and cannot be run.")
41
67
  if not RuntimeFlowgraph.validate(
42
68
  self.__flow,
43
69
  from_steps=from_steps,
44
70
  to_steps=to_steps,
45
71
  prune_nodes=prune_nodes,
46
72
  logger=chip.logger):
47
- raise ValueError(f"{self.__flow.name()} flowgraph contains errors and cannot be run.")
73
+ raise ValueError(f"{self.__flow.name} flowgraph contains errors and cannot be run.")
48
74
 
49
75
  self.__flow_runtime = RuntimeFlowgraph(
50
76
  self.__flow,
@@ -62,7 +88,30 @@ class Scheduler:
62
88
 
63
89
  self.__tasks = {}
64
90
 
91
+ # Create dummy handler
92
+ self.__joblog_handler = logging.NullHandler()
93
+ self.__org_job_name = self.__chip.get("option", "jobname")
94
+
95
+ @property
96
+ def project(self):
97
+ """
98
+ Returns the Project object associated with this scheduler.
99
+
100
+ This property provides access to the central Project object, which holds
101
+ the entire design configuration, flowgraph, and results.
102
+
103
+ Returns:
104
+ Project: The Project object for the current project.
105
+ """
106
+ return self.__chip
107
+
65
108
  def __print_status(self, header):
109
+ """
110
+ Private helper to print the current status of all nodes for debugging.
111
+
112
+ Args:
113
+ header (str): A header message to print before the status list.
114
+ """
66
115
  self.__logger.debug(f"#### {header}")
67
116
  for step, index in self.__flow.get_nodes():
68
117
  self.__logger.debug(f"({step}, {index}) -> "
@@ -70,17 +119,102 @@ class Scheduler:
70
119
  self.__logger.debug("####")
71
120
 
72
121
  def check_manifest(self):
122
+ """
123
+ Checks the validity of the Chip's manifest before a run.
124
+
125
+ Returns:
126
+ bool: True if the manifest is valid, False otherwise.
127
+ """
73
128
  self.__logger.info("Checking manifest before running.")
74
129
  return self.__chip.check_manifest()
75
130
 
76
131
  def run_core(self):
132
+ """
133
+ Executes the core task scheduling loop.
134
+
135
+ This method initializes and runs the TaskScheduler, which manages the
136
+ execution of individual nodes based on their dependencies and status.
137
+ """
77
138
  self.__record.record_python_packages()
78
139
 
79
140
  task_scheduler = TaskScheduler(self.__chip, self.__tasks)
80
- task_scheduler.run()
141
+ task_scheduler.run(self.__joblog_handler)
81
142
  task_scheduler.check()
82
143
 
144
+ def __excepthook(self, exc_type, exc_value, exc_traceback):
145
+ """
146
+ Custom exception hook to ensure all fatal errors are logged.
147
+
148
+ This captures unhandled exceptions, logs them to the job log file,
149
+ and prints a traceback for debugging before the program terminates.
150
+ """
151
+ if issubclass(exc_type, KeyboardInterrupt):
152
+ sys.__excepthook__(exc_type, exc_value, exc_traceback)
153
+ return
154
+
155
+ # Print a summary of the exception
156
+ except_msg = f"Exception raised: {exc_type.__name__}"
157
+ exc_value = str(exc_value).strip()
158
+ if exc_value:
159
+ except_msg += f" / {exc_value}"
160
+ self.__logger.error(except_msg)
161
+
162
+ trace = io.StringIO()
163
+
164
+ # Print the full traceback for debugging
165
+ self.__logger.error("Traceback (most recent call last):")
166
+ traceback.print_tb(exc_traceback, file=trace)
167
+ for line in trace.getvalue().splitlines():
168
+ self.__logger.error(line)
169
+
170
+ # Ensure dashboard receives a stop if running
171
+ if self.__chip._dash:
172
+ self.__chip._dash.stop()
173
+
174
+ # Mark error to keep logfile
175
+ MPManager.error("uncaught exception")
176
+
83
177
  def run(self):
178
+ """
179
+ The main entry point to start the compilation flow.
180
+
181
+ This method orchestrates the entire run, including:
182
+ - Setting up a custom exception hook for logging.
183
+ - Initializing the job directory and log files.
184
+ - Configuring and setting up all nodes in the flow.
185
+ - Validating the manifest.
186
+ - Executing the core run loop.
187
+ - Recording the final results and history.
188
+ """
189
+ # Install hook to ensure exception is logged
190
+ org_excepthook = sys.excepthook
191
+ sys.excepthook = self.__excepthook
192
+
193
+ # Determine job name first so we can create a log
194
+ if not self.__increment_job_name():
195
+ # No need to copy, no remove org job name
196
+ self.__org_job_name = None
197
+
198
+ # Clean the directory early if needed
199
+ self.__clean_build_dir()
200
+
201
+ # Install job file logger
202
+ os.makedirs(self.__chip.getworkdir(), exist_ok=True)
203
+ file_log = os.path.join(self.__chip.getworkdir(), "job.log")
204
+ bak_count = 0
205
+ bak_file_log = f"{file_log}.bak"
206
+ while os.path.exists(bak_file_log):
207
+ bak_count += 1
208
+ bak_file_log = f"{file_log}.bak.{bak_count}"
209
+ if os.path.exists(file_log):
210
+ os.rename(file_log, bak_file_log)
211
+ self.__joblog_handler = logging.FileHandler(file_log)
212
+ self.__joblog_handler.setFormatter(SCLoggerFormatter())
213
+ self.__logger.addHandler(self.__joblog_handler)
214
+
215
+ # Configure run
216
+ self.__chip._init_run()
217
+
84
218
  self.__run_setup()
85
219
  self.configure_nodes()
86
220
 
@@ -99,7 +233,24 @@ class Scheduler:
99
233
 
100
234
  send_messages.send(self.__chip, 'summary', None, None)
101
235
 
236
+ self.__logger.removeHandler(self.__joblog_handler)
237
+ self.__joblog_handler = logging.NullHandler()
238
+
239
+ # Restore hook
240
+ sys.excepthook = org_excepthook
241
+
102
242
  def __mark_pending(self, step, index):
243
+ """
244
+ Private helper to recursively mark a node and its dependents as PENDING.
245
+
246
+ When a node is determined to need a re-run, this function ensures that
247
+ it and all subsequent nodes in the flowgraph are marked as PENDING,
248
+ effectively queueing them for execution.
249
+
250
+ Args:
251
+ step (str): The step of the node to mark.
252
+ index (str): The index of the node to mark.
253
+ """
103
254
  if (step, index) not in self.__flow_runtime.get_nodes():
104
255
  return
105
256
 
@@ -112,10 +263,14 @@ class Scheduler:
112
263
  self.__record.set('status', NodeStatus.PENDING, step=next_step, index=next_index)
113
264
 
114
265
  def __run_setup(self):
115
- self.__check_display()
266
+ """
267
+ Private helper to perform initial setup for the entire run.
116
268
 
117
- org_jobname = self.__chip.get('option', 'jobname')
118
- copy_prev_job = self.__increment_job_name()
269
+ This includes checking for a display environment, creating SchedulerNode
270
+ objects for each task, and copying results from a previous job if one
271
+ is specified.
272
+ """
273
+ self.__check_display()
119
274
 
120
275
  # Create tasks
121
276
  copy_from_nodes = set(self.__flow_load_runtime.get_nodes()).difference(
@@ -132,22 +287,27 @@ class Scheduler:
132
287
  if self.__flow.get(step, index, "tool") == "builtin":
133
288
  self.__tasks[(step, index)].set_builtin()
134
289
 
135
- if copy_prev_job and (step, index) in copy_from_nodes:
136
- self.__tasks[(step, index)].copy_from(org_jobname)
290
+ if self.__org_job_name and (step, index) in copy_from_nodes:
291
+ self.__tasks[(step, index)].copy_from(self.__org_job_name)
137
292
 
138
- if copy_prev_job:
293
+ if self.__org_job_name:
139
294
  # Copy collection directory
140
- copy_from = self.__chip._getcollectdir(jobname=org_jobname)
295
+ copy_from = self.__chip._getcollectdir(jobname=self.__org_job_name)
141
296
  copy_to = self.__chip._getcollectdir()
142
297
  if os.path.exists(copy_from):
143
298
  shutil.copytree(copy_from, copy_to,
144
299
  dirs_exist_ok=True,
145
300
  copy_function=utils.link_copy)
146
301
 
147
- self.__clean_build_dir()
148
302
  self.__reset_flow_nodes()
149
303
 
150
304
  def __reset_flow_nodes(self):
305
+ """
306
+ Private helper to reset the status and metrics for all nodes in the flow.
307
+
308
+ This prepares the schema for a new run by clearing out results from any
309
+ previous executions.
310
+ """
151
311
  # Reset record
152
312
  for step, index in self.__flow.get_nodes():
153
313
  self.__record.clear(step, index, keep=['remoteid', 'status', 'pythonpackage'])
@@ -158,6 +318,12 @@ class Scheduler:
158
318
  self.__metrics.clear(step, index)
159
319
 
160
320
  def __clean_build_dir(self):
321
+ """
322
+ Private helper to clean the build directory if necessary.
323
+
324
+ If ['option', 'clean'] is True and the run starts from the beginning,
325
+ the entire build directory is removed to ensure a fresh start.
326
+ """
161
327
  if self.__record.get('remoteid'):
162
328
  return
163
329
 
@@ -169,6 +335,14 @@ class Scheduler:
169
335
  shutil.rmtree(cur_job_dir)
170
336
 
171
337
  def configure_nodes(self):
338
+ """
339
+ Configures all nodes before execution.
340
+
341
+ This is a critical step that determines the final state of each node
342
+ (SUCCESS, PENDING, SKIPPED) before the scheduler starts. It loads
343
+ results from previous runs, checks for any modifications to parameters
344
+ or input files, and marks nodes for re-run accordingly.
345
+ """
172
346
  from_nodes = []
173
347
  extra_setup_nodes = {}
174
348
 
@@ -253,8 +427,9 @@ class Scheduler:
253
427
 
254
428
  self.__print_status("After ensure")
255
429
 
430
+ os.makedirs(self.__chip.getworkdir(), exist_ok=True)
256
431
  self.__chip.write_manifest(os.path.join(self.__chip.getworkdir(),
257
- f"{self.__chip.get('design')}.pkg.json"))
432
+ f"{self.__name}.pkg.json"))
258
433
  journal.stop()
259
434
 
260
435
  # Clean nodes marked pending
@@ -264,9 +439,13 @@ class Scheduler:
264
439
  self.__tasks[(step, index)].clean_directory()
265
440
 
266
441
  def __check_display(self):
267
- '''
268
- Automatically disable display for Linux systems without desktop environment
269
- '''
442
+ """
443
+ Private helper to automatically disable GUI display on headless systems.
444
+
445
+ If running on Linux without a DISPLAY or WAYLAND_DISPLAY environment
446
+ variable, this sets ['option', 'nodisplay'] to True to prevent tools
447
+ from attempting to open a GUI.
448
+ """
270
449
 
271
450
  if not self.__chip.get('option', 'nodisplay') and sys.platform == 'linux' \
272
451
  and 'DISPLAY' not in os.environ and 'WAYLAND_DISPLAY' not in os.environ:
@@ -275,11 +454,16 @@ class Scheduler:
275
454
  self.__chip.set('option', 'nodisplay', True)
276
455
 
277
456
  def __increment_job_name(self):
278
- '''
279
- Auto-update jobname if [option,jobincr] is True
280
- Do this before initializing logger so that it picks up correct jobname
281
- '''
457
+ """
458
+ Private helper to auto-increment the jobname if ['option', 'jobincr'] is True.
459
+
460
+ This prevents overwriting previous job results by finding the highest
461
+ numbered existing job directory and creating a new one with an
462
+ incremented number.
282
463
 
464
+ Returns:
465
+ bool: True if the job name was incremented, False otherwise.
466
+ """
283
467
  if not self.__chip.get('option', 'clean'):
284
468
  return False
285
469
  if not self.__chip.get('option', 'jobincr'):