siliconcompiler 0.34.1__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 (129) hide show
  1. siliconcompiler/__init__.py +23 -4
  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 +7 -6
  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/__init__.py +17 -0
  19. siliconcompiler/constraints/asic_component.py +378 -0
  20. siliconcompiler/constraints/asic_floorplan.py +449 -0
  21. siliconcompiler/constraints/asic_pins.py +489 -0
  22. siliconcompiler/constraints/asic_timing.py +517 -0
  23. siliconcompiler/core.py +10 -35
  24. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +8 -0
  25. siliconcompiler/dependencyschema.py +96 -202
  26. siliconcompiler/design.py +327 -241
  27. siliconcompiler/filesetschema.py +250 -0
  28. siliconcompiler/flowgraph.py +298 -106
  29. siliconcompiler/fpga.py +124 -1
  30. siliconcompiler/library.py +331 -0
  31. siliconcompiler/metric.py +327 -92
  32. siliconcompiler/metrics/__init__.py +7 -0
  33. siliconcompiler/metrics/asic.py +245 -0
  34. siliconcompiler/metrics/fpga.py +220 -0
  35. siliconcompiler/package/__init__.py +391 -67
  36. siliconcompiler/package/git.py +92 -16
  37. siliconcompiler/package/github.py +114 -22
  38. siliconcompiler/package/https.py +79 -16
  39. siliconcompiler/packageschema.py +341 -16
  40. siliconcompiler/pathschema.py +255 -0
  41. siliconcompiler/pdk.py +566 -1
  42. siliconcompiler/project.py +1460 -0
  43. siliconcompiler/record.py +38 -1
  44. siliconcompiler/remote/__init__.py +5 -2
  45. siliconcompiler/remote/client.py +11 -6
  46. siliconcompiler/remote/schema.py +5 -23
  47. siliconcompiler/remote/server.py +41 -54
  48. siliconcompiler/report/__init__.py +3 -3
  49. siliconcompiler/report/dashboard/__init__.py +48 -14
  50. siliconcompiler/report/dashboard/cli/__init__.py +99 -21
  51. siliconcompiler/report/dashboard/cli/board.py +364 -179
  52. siliconcompiler/report/dashboard/web/__init__.py +90 -12
  53. siliconcompiler/report/dashboard/web/components/__init__.py +219 -240
  54. siliconcompiler/report/dashboard/web/components/flowgraph.py +49 -26
  55. siliconcompiler/report/dashboard/web/components/graph.py +139 -100
  56. siliconcompiler/report/dashboard/web/layouts/__init__.py +29 -1
  57. siliconcompiler/report/dashboard/web/layouts/_common.py +38 -2
  58. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph.py +39 -26
  59. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_node_tab.py +50 -50
  60. siliconcompiler/report/dashboard/web/layouts/vertical_flowgraph_sac_tabs.py +49 -46
  61. siliconcompiler/report/dashboard/web/state.py +141 -14
  62. siliconcompiler/report/dashboard/web/utils/__init__.py +79 -16
  63. siliconcompiler/report/dashboard/web/utils/file_utils.py +74 -11
  64. siliconcompiler/report/dashboard/web/viewer.py +25 -1
  65. siliconcompiler/report/report.py +5 -2
  66. siliconcompiler/report/summary_image.py +29 -11
  67. siliconcompiler/scheduler/__init__.py +9 -1
  68. siliconcompiler/scheduler/docker.py +81 -4
  69. siliconcompiler/scheduler/run_node.py +37 -20
  70. siliconcompiler/scheduler/scheduler.py +211 -36
  71. siliconcompiler/scheduler/schedulernode.py +394 -60
  72. siliconcompiler/scheduler/send_messages.py +77 -29
  73. siliconcompiler/scheduler/slurm.py +76 -12
  74. siliconcompiler/scheduler/taskscheduler.py +142 -21
  75. siliconcompiler/schema/__init__.py +0 -4
  76. siliconcompiler/schema/baseschema.py +338 -59
  77. siliconcompiler/schema/editableschema.py +14 -6
  78. siliconcompiler/schema/journal.py +28 -17
  79. siliconcompiler/schema/namedschema.py +22 -14
  80. siliconcompiler/schema/parameter.py +89 -28
  81. siliconcompiler/schema/parametertype.py +2 -0
  82. siliconcompiler/schema/parametervalue.py +258 -15
  83. siliconcompiler/schema/safeschema.py +25 -2
  84. siliconcompiler/schema/schema_cfg.py +23 -19
  85. siliconcompiler/schema/utils.py +2 -2
  86. siliconcompiler/schema_obj.py +24 -5
  87. siliconcompiler/tool.py +1131 -265
  88. siliconcompiler/tools/bambu/__init__.py +41 -0
  89. siliconcompiler/tools/builtin/concatenate.py +2 -2
  90. siliconcompiler/tools/builtin/minimum.py +2 -1
  91. siliconcompiler/tools/builtin/mux.py +2 -1
  92. siliconcompiler/tools/builtin/nop.py +2 -1
  93. siliconcompiler/tools/builtin/verify.py +2 -1
  94. siliconcompiler/tools/klayout/__init__.py +95 -0
  95. siliconcompiler/tools/openroad/__init__.py +289 -0
  96. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +3 -0
  97. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +7 -2
  98. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +8 -4
  99. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +9 -5
  100. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +5 -1
  101. siliconcompiler/tools/slang/__init__.py +1 -1
  102. siliconcompiler/tools/slang/elaborate.py +2 -1
  103. siliconcompiler/tools/vivado/scripts/sc_run.tcl +1 -1
  104. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +8 -1
  105. siliconcompiler/tools/vivado/syn_fpga.py +6 -0
  106. siliconcompiler/tools/vivado/vivado.py +35 -2
  107. siliconcompiler/tools/vpr/__init__.py +150 -0
  108. siliconcompiler/tools/yosys/__init__.py +369 -1
  109. siliconcompiler/tools/yosys/scripts/procs.tcl +0 -1
  110. siliconcompiler/toolscripts/_tools.json +5 -10
  111. siliconcompiler/utils/__init__.py +66 -0
  112. siliconcompiler/utils/flowgraph.py +2 -2
  113. siliconcompiler/utils/issue.py +2 -1
  114. siliconcompiler/utils/logging.py +14 -0
  115. siliconcompiler/utils/multiprocessing.py +256 -0
  116. siliconcompiler/utils/showtools.py +10 -0
  117. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/METADATA +6 -6
  118. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/RECORD +122 -115
  119. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/entry_points.txt +3 -0
  120. siliconcompiler/schema/cmdlineschema.py +0 -250
  121. siliconcompiler/schema/packageschema.py +0 -101
  122. siliconcompiler/toolscripts/rhel8/install-slang.sh +0 -40
  123. siliconcompiler/toolscripts/rhel9/install-slang.sh +0 -40
  124. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +0 -47
  125. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +0 -37
  126. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +0 -37
  127. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/WHEEL +0 -0
  128. {siliconcompiler-0.34.1.dist-info → siliconcompiler-0.34.3.dist-info}/licenses/LICENSE +0 -0
  129. {siliconcompiler-0.34.1.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,43 @@ 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 = self.__chip.logger
50
+ self.__logger: logging.Logger = chip.logger
51
+ self.__name = chip.design
25
52
 
26
53
  flow = self.__chip.get("option", "flow")
27
54
  if not flow:
@@ -36,14 +63,14 @@ class Scheduler:
36
63
  prune_nodes = self.__chip.get('option', 'prune')
37
64
 
38
65
  if not self.__flow.validate(logger=self.__logger):
39
- 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.")
40
67
  if not RuntimeFlowgraph.validate(
41
68
  self.__flow,
42
69
  from_steps=from_steps,
43
70
  to_steps=to_steps,
44
71
  prune_nodes=prune_nodes,
45
72
  logger=chip.logger):
46
- 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.")
47
74
 
48
75
  self.__flow_runtime = RuntimeFlowgraph(
49
76
  self.__flow,
@@ -51,27 +78,40 @@ class Scheduler:
51
78
  to_steps=to_steps,
52
79
  prune_nodes=self.__chip.get('option', 'prune'))
53
80
 
54
- self.__flow_runtime_no_prune = RuntimeFlowgraph(
55
- self.__flow,
56
- from_steps=from_steps,
57
- to_steps=to_steps)
58
-
59
81
  self.__flow_load_runtime = RuntimeFlowgraph(
60
82
  self.__flow,
61
83
  to_steps=from_steps,
62
84
  prune_nodes=prune_nodes)
63
85
 
64
- self.__flow_something = RuntimeFlowgraph(
65
- self.__flow,
66
- from_steps=set([step for step, _ in self.__flow.get_entry_nodes()]),
67
- prune_nodes=prune_nodes)
68
-
69
86
  self.__record = self.__chip.get("record", field="schema")
70
87
  self.__metrics = self.__chip.get("metric", field="schema")
71
88
 
72
89
  self.__tasks = {}
73
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
+
74
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
+ """
75
115
  self.__logger.debug(f"#### {header}")
76
116
  for step, index in self.__flow.get_nodes():
77
117
  self.__logger.debug(f"({step}, {index}) -> "
@@ -79,17 +119,102 @@ class Scheduler:
79
119
  self.__logger.debug("####")
80
120
 
81
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
+ """
82
128
  self.__logger.info("Checking manifest before running.")
83
129
  return self.__chip.check_manifest()
84
130
 
85
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
+ """
86
138
  self.__record.record_python_packages()
87
139
 
88
140
  task_scheduler = TaskScheduler(self.__chip, self.__tasks)
89
- task_scheduler.run()
141
+ task_scheduler.run(self.__joblog_handler)
90
142
  task_scheduler.check()
91
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
+
92
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
+
93
218
  self.__run_setup()
94
219
  self.configure_nodes()
95
220
 
@@ -103,12 +228,29 @@ class Scheduler:
103
228
  self.__chip.schema.record_history()
104
229
 
105
230
  # Record final manifest
106
- filepath = os.path.join(self.__chip.getworkdir(), f"{self.__chip.design}.pkg.json")
231
+ filepath = os.path.join(self.__chip.getworkdir(), f"{self.__name}.pkg.json")
107
232
  self.__chip.write_manifest(filepath)
108
233
 
109
234
  send_messages.send(self.__chip, 'summary', None, None)
110
235
 
236
+ self.__logger.removeHandler(self.__joblog_handler)
237
+ self.__joblog_handler = logging.NullHandler()
238
+
239
+ # Restore hook
240
+ sys.excepthook = org_excepthook
241
+
111
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
+ """
112
254
  if (step, index) not in self.__flow_runtime.get_nodes():
113
255
  return
114
256
 
@@ -121,10 +263,14 @@ class Scheduler:
121
263
  self.__record.set('status', NodeStatus.PENDING, step=next_step, index=next_index)
122
264
 
123
265
  def __run_setup(self):
124
- self.__check_display()
266
+ """
267
+ Private helper to perform initial setup for the entire run.
125
268
 
126
- org_jobname = self.__chip.get('option', 'jobname')
127
- 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()
128
274
 
129
275
  # Create tasks
130
276
  copy_from_nodes = set(self.__flow_load_runtime.get_nodes()).difference(
@@ -141,22 +287,27 @@ class Scheduler:
141
287
  if self.__flow.get(step, index, "tool") == "builtin":
142
288
  self.__tasks[(step, index)].set_builtin()
143
289
 
144
- if copy_prev_job and (step, index) in copy_from_nodes:
145
- 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)
146
292
 
147
- if copy_prev_job:
293
+ if self.__org_job_name:
148
294
  # Copy collection directory
149
- copy_from = self.__chip._getcollectdir(jobname=org_jobname)
295
+ copy_from = self.__chip._getcollectdir(jobname=self.__org_job_name)
150
296
  copy_to = self.__chip._getcollectdir()
151
297
  if os.path.exists(copy_from):
152
298
  shutil.copytree(copy_from, copy_to,
153
299
  dirs_exist_ok=True,
154
300
  copy_function=utils.link_copy)
155
301
 
156
- self.__clean_build_dir()
157
302
  self.__reset_flow_nodes()
158
303
 
159
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
+ """
160
311
  # Reset record
161
312
  for step, index in self.__flow.get_nodes():
162
313
  self.__record.clear(step, index, keep=['remoteid', 'status', 'pythonpackage'])
@@ -167,6 +318,12 @@ class Scheduler:
167
318
  self.__metrics.clear(step, index)
168
319
 
169
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
+ """
170
327
  if self.__record.get('remoteid'):
171
328
  return
172
329
 
@@ -178,6 +335,14 @@ class Scheduler:
178
335
  shutil.rmtree(cur_job_dir)
179
336
 
180
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
+ """
181
346
  from_nodes = []
182
347
  extra_setup_nodes = {}
183
348
 
@@ -206,7 +371,7 @@ class Scheduler:
206
371
 
207
372
  manifest = os.path.join(self.__chip.getworkdir(step=step, index=index),
208
373
  'outputs',
209
- f'{self.__chip.design}.pkg.json')
374
+ f'{self.__name}.pkg.json')
210
375
  if os.path.exists(manifest):
211
376
  # ensure we setup these nodes again
212
377
  try:
@@ -262,8 +427,9 @@ class Scheduler:
262
427
 
263
428
  self.__print_status("After ensure")
264
429
 
430
+ os.makedirs(self.__chip.getworkdir(), exist_ok=True)
265
431
  self.__chip.write_manifest(os.path.join(self.__chip.getworkdir(),
266
- f"{self.__chip.get('design')}.pkg.json"))
432
+ f"{self.__name}.pkg.json"))
267
433
  journal.stop()
268
434
 
269
435
  # Clean nodes marked pending
@@ -273,9 +439,13 @@ class Scheduler:
273
439
  self.__tasks[(step, index)].clean_directory()
274
440
 
275
441
  def __check_display(self):
276
- '''
277
- Automatically disable display for Linux systems without desktop environment
278
- '''
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
+ """
279
449
 
280
450
  if not self.__chip.get('option', 'nodisplay') and sys.platform == 'linux' \
281
451
  and 'DISPLAY' not in os.environ and 'WAYLAND_DISPLAY' not in os.environ:
@@ -284,11 +454,16 @@ class Scheduler:
284
454
  self.__chip.set('option', 'nodisplay', True)
285
455
 
286
456
  def __increment_job_name(self):
287
- '''
288
- Auto-update jobname if [option,jobincr] is True
289
- Do this before initializing logger so that it picks up correct jobname
290
- '''
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.
291
463
 
464
+ Returns:
465
+ bool: True if the job name was incremented, False otherwise.
466
+ """
292
467
  if not self.__chip.get('option', 'clean'):
293
468
  return False
294
469
  if not self.__chip.get('option', 'jobincr'):