siliconcompiler 0.35.2__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.
Files changed (86) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/apps/sc_issue.py +18 -2
  3. siliconcompiler/apps/smake.py +106 -100
  4. siliconcompiler/checklist.py +2 -1
  5. siliconcompiler/constraints/asic_component.py +49 -11
  6. siliconcompiler/constraints/asic_floorplan.py +23 -21
  7. siliconcompiler/constraints/asic_pins.py +55 -17
  8. siliconcompiler/constraints/asic_timing.py +53 -22
  9. siliconcompiler/constraints/fpga_timing.py +5 -6
  10. siliconcompiler/data/templates/replay/replay.sh.j2 +27 -14
  11. siliconcompiler/flowgraph.py +418 -129
  12. siliconcompiler/library.py +5 -4
  13. siliconcompiler/package/__init__.py +17 -6
  14. siliconcompiler/package/https.py +10 -5
  15. siliconcompiler/project.py +92 -33
  16. siliconcompiler/remote/client.py +17 -6
  17. siliconcompiler/scheduler/docker.py +24 -25
  18. siliconcompiler/scheduler/scheduler.py +284 -121
  19. siliconcompiler/scheduler/schedulernode.py +196 -90
  20. siliconcompiler/scheduler/slurm.py +113 -29
  21. siliconcompiler/scheduler/taskscheduler.py +0 -7
  22. siliconcompiler/schema/__init__.py +3 -2
  23. siliconcompiler/schema/_metadata.py +1 -1
  24. siliconcompiler/schema/baseschema.py +205 -93
  25. siliconcompiler/schema/editableschema.py +29 -0
  26. siliconcompiler/schema/namedschema.py +21 -13
  27. siliconcompiler/schema/parametervalue.py +14 -2
  28. siliconcompiler/schema/safeschema.py +18 -7
  29. siliconcompiler/schema_support/dependencyschema.py +4 -3
  30. siliconcompiler/schema_support/option.py +82 -1
  31. siliconcompiler/schema_support/pathschema.py +14 -15
  32. siliconcompiler/schema_support/record.py +5 -4
  33. siliconcompiler/targets/asap7_demo.py +4 -1
  34. siliconcompiler/tool.py +56 -29
  35. siliconcompiler/tools/builtin/__init__.py +2 -0
  36. siliconcompiler/tools/builtin/filter.py +8 -1
  37. siliconcompiler/tools/builtin/importfiles.py +2 -0
  38. siliconcompiler/tools/klayout/__init__.py +3 -0
  39. siliconcompiler/tools/klayout/scripts/klayout_convert_drc_db.py +1 -0
  40. siliconcompiler/tools/klayout/scripts/klayout_export.py +1 -0
  41. siliconcompiler/tools/klayout/scripts/klayout_operations.py +1 -0
  42. siliconcompiler/tools/klayout/scripts/klayout_show.py +2 -1
  43. siliconcompiler/tools/klayout/scripts/klayout_utils.py +3 -4
  44. siliconcompiler/tools/klayout/show.py +17 -5
  45. siliconcompiler/tools/openroad/__init__.py +27 -1
  46. siliconcompiler/tools/openroad/_apr.py +81 -4
  47. siliconcompiler/tools/openroad/clock_tree_synthesis.py +1 -0
  48. siliconcompiler/tools/openroad/global_placement.py +1 -0
  49. siliconcompiler/tools/openroad/init_floorplan.py +116 -7
  50. siliconcompiler/tools/openroad/power_grid_analysis.py +174 -0
  51. siliconcompiler/tools/openroad/repair_design.py +1 -0
  52. siliconcompiler/tools/openroad/repair_timing.py +1 -0
  53. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +1 -1
  54. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +42 -4
  55. siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +146 -0
  56. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +1 -1
  57. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -6
  58. siliconcompiler/tools/openroad/scripts/common/procs.tcl +1 -1
  59. siliconcompiler/tools/openroad/scripts/common/reports.tcl +1 -1
  60. siliconcompiler/tools/openroad/scripts/rcx/sc_rcx_bench.tcl +2 -4
  61. siliconcompiler/tools/opensta/__init__.py +1 -1
  62. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +17 -12
  63. siliconcompiler/tools/vivado/scripts/sc_bitstream.tcl +11 -0
  64. siliconcompiler/tools/vivado/scripts/sc_place.tcl +11 -0
  65. siliconcompiler/tools/vivado/scripts/sc_route.tcl +11 -0
  66. siliconcompiler/tools/vivado/scripts/sc_syn_fpga.tcl +10 -0
  67. siliconcompiler/tools/vpr/__init__.py +28 -0
  68. siliconcompiler/tools/yosys/prepareLib.py +7 -2
  69. siliconcompiler/tools/yosys/scripts/sc_screenshot.tcl +1 -1
  70. siliconcompiler/tools/yosys/scripts/sc_synth_asic.tcl +40 -4
  71. siliconcompiler/tools/yosys/scripts/sc_synth_fpga.tcl +15 -5
  72. siliconcompiler/tools/yosys/syn_asic.py +62 -2
  73. siliconcompiler/tools/yosys/syn_fpga.py +8 -0
  74. siliconcompiler/toolscripts/_tools.json +6 -6
  75. siliconcompiler/utils/__init__.py +243 -51
  76. siliconcompiler/utils/curation.py +89 -56
  77. siliconcompiler/utils/issue.py +6 -1
  78. siliconcompiler/utils/multiprocessing.py +35 -2
  79. siliconcompiler/utils/paths.py +21 -0
  80. siliconcompiler/utils/settings.py +141 -0
  81. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/METADATA +5 -4
  82. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/RECORD +86 -83
  83. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/WHEEL +0 -0
  84. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/entry_points.txt +0 -0
  85. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/licenses/LICENSE +0 -0
  86. {siliconcompiler-0.35.2.dist-info → siliconcompiler-0.35.4.dist-info}/top_level.txt +0 -0
@@ -24,7 +24,6 @@ class Flowgraph(NamedSchema, DocsSchema):
24
24
 
25
25
  A flowgraph is a directed acyclic graph (DAG) that represents the
26
26
  compilation flow. Each node in the graph is a step/index pair that
27
-
28
27
  maps to a specific tool task, and edges represent dependencies between
29
28
  these tasks.
30
29
  '''
@@ -48,7 +47,9 @@ class Flowgraph(NamedSchema, DocsSchema):
48
47
  '''
49
48
  Clears the internal cache for memoized flowgraph properties.
50
49
 
51
- This should be called any time the graph structure is modified.
50
+ This should be called any time the graph structure (nodes or edges)
51
+ is modified to ensure subsequent calls to graph traversal methods
52
+ (like get_nodes, get_execution_order, etc.) compute fresh results.
52
53
  '''
53
54
 
54
55
  self.__cache_nodes = None
@@ -63,29 +64,46 @@ class Flowgraph(NamedSchema, DocsSchema):
63
64
 
64
65
  def node(self, step: str, task: "Task", index: Optional[Union[str, int]] = 0) -> None:
65
66
  '''
66
- Creates a flowgraph node.
67
+ Creates or updates a flowgraph node.
68
+
69
+ Creates a flowgraph node by binding a step/index pair to a specific
70
+ tool task. A tool can be an external executable or one of the built-in
71
+ functions in the SiliconCompiler framework (e.g., minimum, maximum, join).
67
72
 
68
- Creates a flowgraph node by binding a step to a tool-specific task.
69
- A tool can be an external executable or one of the built-in functions
70
- in the SiliconCompiler framework (e.g., minimum, maximum, join).
73
+ If the node (step, index) already exists, its task and tool information
74
+ will be updated.
71
75
 
72
- The method modifies the following schema parameters:
76
+ The method modifies the following schema parameters for the given
77
+ step and index:
73
78
 
74
79
  * `['<step>', '<index>', 'tool']`
75
80
  * `['<step>', '<index>', 'task']`
76
81
  * `['<step>', '<index>', 'taskmodule']`
77
82
 
78
83
  Args:
79
- step (str): Step name for the node.
80
- task (module or str): The task to associate with this node. Can be
81
- a module object or a string in the format '<tool>.<task>'.
82
- index (int or str): Index for the step. Defaults to 0.
84
+ step (str): Step name for the node. Must not contain '/'.
85
+ task (Task or str or Type[Task]): The task to associate with this
86
+ node. Can be a task instance, a string in the format
87
+ '<module_path>/<ClassName>', or a Task class type.
88
+ index (int or str, optional): Index for the step. Defaults to 0.
89
+ Must not contain '/'.
90
+
91
+ Raises:
92
+ ValueError: If 'step' or 'index' are reserved names (like
93
+ 'default' or 'global') or contain invalid characters ('/').
94
+ ValueError: If 'task' is not a valid Task object, string, or class.
83
95
 
84
96
  Examples:
85
97
  >>> import siliconcompiler.tools.openroad as openroad
86
- >>> flow.node('place', openroad.place, index=0)
87
- # Creates a node for the 'place' task in the 'openroad' tool,
88
- # identified by step='place' and index=0.
98
+ >>> # Using a Task class
99
+ >>> flow.node('place', openroad.Place, index=0)
100
+ >>>
101
+ >>> # Using a string identifier
102
+ >>> flow.node('cts', 'siliconcompiler.tools.openroad/Cts', index=0)
103
+ >>>
104
+ >>> # Using a Task instance
105
+ >>> from siliconcompiler.tools.builtin import Join
106
+ >>> flow.node('join', Join(), index=0)
89
107
  '''
90
108
  from siliconcompiler import Task
91
109
 
@@ -104,6 +122,7 @@ class Flowgraph(NamedSchema, DocsSchema):
104
122
  # Determine task name and module
105
123
  task_module = None
106
124
  if inspect.isclass(task) and issubclass(task, Task):
125
+ # Instantiate the task class
107
126
  task = task()
108
127
 
109
128
  if isinstance(task, str):
@@ -113,13 +132,14 @@ class Flowgraph(NamedSchema, DocsSchema):
113
132
  elif isinstance(task, Task):
114
133
  task_module = task.__class__.__module__ + "/" + task.__class__.__name__
115
134
  else:
116
- raise ValueError(f"{task} is not a string or module and cannot be used to "
117
- "setup a task.")
135
+ raise ValueError(f"{task} is not a string, Task class, or Task instance and "
136
+ "cannot be used to setup a task.")
118
137
 
119
138
  # bind tool to node
120
- self.set(step, index, 'tool', task.tool())
121
- self.set(step, index, 'task', task.task())
122
- self.set(step, index, 'taskmodule', task_module)
139
+ graph_node: "FlowgraphNodeSchema" = self.get(step, index, field="schema")
140
+ graph_node.set('tool', task.tool())
141
+ graph_node.set('task', task.task())
142
+ graph_node.set('taskmodule', task_module)
123
143
 
124
144
  self.__clear_cache()
125
145
 
@@ -129,20 +149,31 @@ class Flowgraph(NamedSchema, DocsSchema):
129
149
  '''
130
150
  Creates a directed edge from a tail node to a head node.
131
151
 
132
- Connects the output of a tail node with the input of a head node by
133
- setting the 'input' field of the head node in the schema flowgraph.
152
+ Connects the output of a tail node (tail, tail_index) with the
153
+ input of a head node (head, head_index) by adding the tail node
154
+ to the 'input' list of the head node in the schema.
155
+
156
+ If the edge already exists, this method does nothing.
134
157
 
135
158
  The method modifies the following parameter:
136
159
 
137
160
  * `['<head>', '<head_index>', 'input']`
138
161
 
139
162
  Args:
140
- tail (str): Step name of the tail node.
141
- head (str): Step name of the head node.
142
- tail_index (int or str): Index of the tail node. Defaults to 0.
143
- head_index (int or str): Index of the head node. Defaults to 0.
163
+ tail (str): Step name of the tail node (source).
164
+ head (str): Step name of the head node (destination).
165
+ tail_index (int or str, optional): Index of the tail node.
166
+ Defaults to 0.
167
+ head_index (int or str, optional): Index of the head node.
168
+ Defaults to 0.
169
+
170
+ Raises:
171
+ ValueError: If either the head or tail node is not defined in
172
+ the flowgraph before calling this method.
144
173
 
145
174
  Examples:
175
+ >>> flow.node('place', 'openroad/Place')
176
+ >>> flow.node('cts', 'openroad/Cts')
146
177
  >>> flow.edge('place', 'cts')
147
178
  # Creates a directed edge from ('place', '0') to ('cts', '0').
148
179
  '''
@@ -153,11 +184,14 @@ class Flowgraph(NamedSchema, DocsSchema):
153
184
  if not self.valid(step, index):
154
185
  raise ValueError(f"{step}/{index} is not a defined node in {self.name}.")
155
186
 
187
+ head_node = self.get_graph_node(head, head_index)
188
+
156
189
  tail_node = (tail, tail_index)
157
- if tail_node in self.get(head, head_index, 'input'):
190
+ if tail_node in head_node.get_input():
191
+ # Edge already exists
158
192
  return
159
193
 
160
- self.add(head, head_index, 'input', tail_node)
194
+ head_node.add('input', tail_node)
161
195
 
162
196
  self.__clear_cache()
163
197
 
@@ -165,42 +199,58 @@ class Flowgraph(NamedSchema, DocsSchema):
165
199
  '''
166
200
  Removes a flowgraph node and reconnects its inputs to its outputs.
167
201
 
202
+ This operation effectively "stitches" the graph back together by
203
+ creating new edges from all inputs of the removed node to all
204
+ outputs of the removed node.
205
+
206
+ If `index` is None, all nodes for the given `step` are removed.
207
+
168
208
  Args:
169
209
  step (str): Step name of the node to remove.
170
210
  index (int or str, optional): Index of the node to remove. If None,
171
211
  all nodes for the given step are removed. Defaults to None.
212
+
213
+ Raises:
214
+ ValueError: If the specified `step` or `(step, index)` is not
215
+ a valid node in the flowgraph.
172
216
  '''
173
217
 
174
218
  if step not in self.getkeys():
175
219
  raise ValueError(f'{step} is not a valid step in {self.name}')
176
220
 
177
221
  if index is None:
178
- # Iterate over all indexes
179
- for index in self.getkeys(step):
180
- self.remove_node(step, index)
222
+ # Iterate over all indexes for the step
223
+ for index_key in self.getkeys(step):
224
+ self.remove_node(step, index_key)
181
225
  return
182
226
 
183
227
  index = str(index)
184
228
  if index not in self.getkeys(step):
185
229
  raise ValueError(f'{index} is not a valid index for {step} in {self.name}')
186
230
 
187
- # Save input edges
188
- node = (step, index)
189
- node_inputs = self.get(step, index, 'input')
231
+ # Save input edges of the node being removed
232
+ node_to_remove = (step, index)
233
+ node_inputs = self.get_graph_node(step, index).get_input()
190
234
 
191
- # remove node
235
+ # Remove the node from the schema
192
236
  self.remove(step, index)
193
237
 
194
- # remove step if all nodes a gone
238
+ # Remove the step if all its nodes are gone
195
239
  if len(self.getkeys(step)) == 0:
196
240
  self.remove(step)
197
241
 
242
+ # Re-wire: Find all nodes that had the removed node as an input
198
243
  for flow_step in self.getkeys():
199
244
  for flow_index in self.getkeys(flow_step):
200
- inputs = self.get(flow_step, flow_index, 'input')
201
- if node in inputs:
202
- inputs = [inode for inode in inputs if inode != node]
245
+ current_node = self.get_graph_node(flow_step, flow_index)
246
+ inputs = current_node.get_input()
247
+
248
+ if node_to_remove in inputs:
249
+ # Remove the old edge
250
+ inputs = [inode for inode in inputs if inode != node_to_remove]
251
+ # Add new edges from the removed node's inputs
203
252
  inputs.extend(node_inputs)
253
+ # Set the new list of inputs, removing duplicates
204
254
  self.set(flow_step, flow_index, 'input', sorted(set(inputs)))
205
255
 
206
256
  self.__clear_cache()
@@ -209,17 +259,25 @@ class Flowgraph(NamedSchema, DocsSchema):
209
259
  index: Optional[Union[str, int]] = 0,
210
260
  before_index: Optional[Union[str, int]] = 0) -> None:
211
261
  '''
212
- Inserts a new node in the graph before a specified node.
262
+ Inserts a new node in the graph immediately before a specified node.
213
263
 
214
- The new node is placed between the `before` node and all of its
215
- original inputs.
264
+ The new node (`step`, `index`) is placed between the `before` node
265
+ (`before_step`, `before_index`) and all of the `before` node's
266
+ original inputs. The `before` node's inputs are cleared, and it
267
+ is given a single input: the new node. The new node inherits all
268
+ the original inputs of the `before` node.
216
269
 
217
270
  Args:
218
271
  step (str): Step name for the new node.
219
- task (module or str): Task to associate with the new node.
272
+ task (Task or str or Type[Task]): Task to associate with the new node.
220
273
  before_step (str): Step name of the existing node to insert before.
221
- index (int or str): Index for the new node. Defaults to 0.
222
- before_index (int or str): Index of the existing node. Defaults to 0.
274
+ index (int or str, optional): Index for the new node. Defaults to 0.
275
+ before_index (int or str, optional): Index of the existing node.
276
+ Defaults to 0.
277
+
278
+ Raises:
279
+ ValueError: If the `before` node (`before_step`, `before_index`)
280
+ is not a valid node in the flowgraph.
223
281
  '''
224
282
 
225
283
  index = str(index)
@@ -246,6 +304,14 @@ class Flowgraph(NamedSchema, DocsSchema):
246
304
  '''
247
305
  Instantiates a sub-flowgraph within the current flowgraph.
248
306
 
307
+ This method copies all nodes and their internal connections from
308
+ `subflow` into the current flowgraph.
309
+
310
+ If `name` is provided, it is used as a prefix (e.g., "core.")
311
+ for all step names from the `subflow` to ensure they are unique
312
+ within the current flowgraph. This prefix is also applied to the
313
+ internal edges to maintain the sub-flowgraph's structure.
314
+
249
315
  Args:
250
316
  subflow (Flowgraph): The flowgraph to instantiate.
251
317
  name (str, optional): A prefix to add to the names of the
@@ -271,13 +337,14 @@ class Flowgraph(NamedSchema, DocsSchema):
271
337
  # forward information
272
338
  for keys in subflow.allkeys(step):
273
339
  self.set(newstep, *keys, subflow.get(step, *keys))
340
+ self.__clear_cache()
274
341
 
275
342
  if name is None:
276
343
  continue
277
344
 
278
345
  # rename inputs
279
346
  for index in self.getkeys(newstep):
280
- all_inputs = self.get(newstep, index, 'input')
347
+ all_inputs = self.get_graph_node(newstep, index).get_input()
281
348
  self.set(newstep, index, 'input', [])
282
349
  for in_step, in_index in all_inputs:
283
350
  newin = name + "." + in_step
@@ -289,10 +356,12 @@ class Flowgraph(NamedSchema, DocsSchema):
289
356
  '''
290
357
  Returns a sorted tuple of all nodes defined in this flowgraph.
291
358
 
292
- A node is represented as a `(step, index)` tuple.
359
+ A node is represented as a `(step, index)` tuple. The result is
360
+ memoized for efficiency.
293
361
 
294
362
  Returns:
295
- tuple[tuple(str,str)]: All nodes in the graph.
363
+ tuple[tuple(str,str)]: A sorted tuple of all (step, index)
364
+ nodes in the graph.
296
365
  '''
297
366
  if self.__cache_nodes is not None:
298
367
  return self.__cache_nodes
@@ -310,10 +379,12 @@ class Flowgraph(NamedSchema, DocsSchema):
310
379
  '''
311
380
  Collects all nodes that are entry points to the flowgraph.
312
381
 
313
- Entry nodes are those with no inputs.
382
+ Entry nodes are those with no inputs defined. The result is
383
+ memoized.
314
384
 
315
385
  Returns:
316
- tuple[tuple(str,str)]: All entry nodes in the graph.
386
+ tuple[tuple(str,str)]: A sorted tuple of all entry nodes
387
+ in the graph.
317
388
  '''
318
389
 
319
390
  if self.__cache_nodes_entry is not None:
@@ -321,7 +392,7 @@ class Flowgraph(NamedSchema, DocsSchema):
321
392
 
322
393
  nodes = []
323
394
  for step, index in self.get_nodes():
324
- if not self.get(step, index, 'input'):
395
+ if not self.get_graph_node(step, index).has_input():
325
396
  nodes.append((step, index))
326
397
 
327
398
  self.__cache_nodes_entry = tuple(sorted(set(nodes)))
@@ -332,10 +403,12 @@ class Flowgraph(NamedSchema, DocsSchema):
332
403
  '''
333
404
  Collects all nodes that are exit points of the flowgraph.
334
405
 
335
- Exit nodes are those that are not inputs to any other node.
406
+ Exit nodes are those that are not inputs to any other node in
407
+ the graph. The result is memoized.
336
408
 
337
409
  Returns:
338
- tuple[tuple(str,str)]: All exit nodes in the graph.
410
+ tuple[tuple(str,str)]: A sorted tuple of all exit nodes
411
+ in the graph.
339
412
  '''
340
413
 
341
414
  if self.__cache_nodes_exit is not None:
@@ -343,7 +416,7 @@ class Flowgraph(NamedSchema, DocsSchema):
343
416
 
344
417
  inputnodes = []
345
418
  for step, index in self.get_nodes():
346
- inputnodes.extend(self.get(step, index, 'input'))
419
+ inputnodes.extend(self.get_graph_node(step, index).get_input())
347
420
  nodes = []
348
421
  for step, index in self.get_nodes():
349
422
  if (step, index) not in inputnodes:
@@ -358,13 +431,21 @@ class Flowgraph(NamedSchema, DocsSchema):
358
431
  '''
359
432
  Generates a topologically sorted list of nodes for execution.
360
433
 
434
+ This method performs a topological sort of the graph. The result is
435
+ a tuple of tuples, where each inner tuple represents a "level" of
436
+ nodes that can be executed in parallel (as their dependencies are
437
+ met).
438
+
439
+ The result is memoized for both forward and reverse orders.
440
+
361
441
  Args:
362
- reverse (bool): If True, the order is reversed, from exit nodes
363
- to entry nodes. Defaults to False.
442
+ reverse (bool, optional): If True, the order is reversed,
443
+ starting from the exit nodes and working backwards to the
444
+ entry nodes. Defaults to False.
364
445
 
365
446
  Returns:
366
447
  tuple[tuple[tuple(str,str)]]: A tuple of tuples, where each inner
367
- tuple represents a level of nodes that can be executed in parallel.
448
+ tuple represents a level of nodes.
368
449
  '''
369
450
 
370
451
  if reverse:
@@ -377,7 +458,7 @@ class Flowgraph(NamedSchema, DocsSchema):
377
458
  # Generate execution edges lookup map
378
459
  ex_map = {}
379
460
  for step, index in self.get_nodes():
380
- for istep, iindex in self.get(step, index, 'input'):
461
+ for istep, iindex in self.get_graph_node(step, index).get_input():
381
462
  if reverse:
382
463
  ex_map.setdefault((step, index), set()).add((istep, iindex))
383
464
  else:
@@ -440,14 +521,21 @@ class Flowgraph(NamedSchema, DocsSchema):
440
521
 
441
522
  def get_node_outputs(self, step: str, index: Union[str, int]) -> Tuple[Tuple[str, str], ...]:
442
523
  '''
443
- Returns the nodes that the given node provides input to.
524
+ Returns the nodes that the given node provides input to (its children).
525
+
526
+ This is the reverse of `get_graph_node(step, index).get_input()`.
527
+ The results are computed for all nodes and memoized on the first call.
444
528
 
445
529
  Args:
446
530
  step (str): Step name of the source node.
447
531
  index (str or int): Index of the source node.
448
532
 
449
533
  Returns:
450
- tuple[tuple(str,str)]: A tuple of destination nodes.
534
+ tuple[tuple(str,str)]: A sorted tuple of destination nodes
535
+ (step, index) that take the given node as an input.
536
+
537
+ Raises:
538
+ ValueError: If the specified `(step, index)` is not a valid node.
451
539
  '''
452
540
 
453
541
  index = str(index)
@@ -462,7 +550,7 @@ class Flowgraph(NamedSchema, DocsSchema):
462
550
 
463
551
  input_map = {}
464
552
  for istep, iindex in self.get_nodes():
465
- input_map[(istep, iindex)] = self.get(istep, iindex, 'input')
553
+ input_map[(istep, iindex)] = self.get_graph_node(istep, iindex).get_input()
466
554
  self.__cache_node_outputs[(istep, iindex)] = set()
467
555
 
468
556
  for src_node, dst_nodes in input_map.items():
@@ -471,6 +559,7 @@ class Flowgraph(NamedSchema, DocsSchema):
471
559
  self.__cache_node_outputs[dst_node] = set()
472
560
  self.__cache_node_outputs[dst_node].add(src_node)
473
561
 
562
+ # Convert sets to sorted tuples for consistent output
474
563
  self.__cache_node_outputs = {
475
564
  node: tuple(sorted(outputs)) for node, outputs in self.__cache_node_outputs.items()
476
565
  }
@@ -482,13 +571,19 @@ class Flowgraph(NamedSchema, DocsSchema):
482
571
  '''
483
572
  Internal helper to search for loops in the graph via depth-first search.
484
573
 
574
+ This method is used by `validate()`.
575
+
485
576
  Args:
486
- step (str): Step name to start from.
487
- index (str): Index name to start from.
488
- path (list, optional): The path taken so far. Defaults to None.
577
+ step (str): Step name to start/continue from.
578
+ index (str): Index name to start/continue from.
579
+ path (list, optional): The current path (nodes in recursion stack).
580
+ Defaults to None.
581
+ visited (set, optional): All nodes visited so far (to avoid
582
+ re-checking branches). Defaults to None.
489
583
 
490
584
  Returns:
491
- list: A list of nodes forming a loop, or None if no loop is found.
585
+ list[tuple(str,str)]: A list of nodes forming a loop, or
586
+ None if no loop is found from this path.
492
587
  '''
493
588
  if path is None:
494
589
  path = []
@@ -529,7 +624,7 @@ class Flowgraph(NamedSchema, DocsSchema):
529
624
  check_nodes = set()
530
625
  for step, index in self.get_nodes():
531
626
  check_nodes.add((step, index))
532
- input_nodes = self.get(step, index, 'input')
627
+ input_nodes = self.get_graph_node(step, index).get_input()
533
628
  check_nodes.update(input_nodes)
534
629
 
535
630
  for node in set(input_nodes):
@@ -556,20 +651,33 @@ class Flowgraph(NamedSchema, DocsSchema):
556
651
  f'{self.name} flowgraph')
557
652
  error = True
558
653
 
559
- # detect loops
654
+ # Detect loops
560
655
  for start_step, start_index in self.get_entry_nodes():
561
656
  loop_path = self.__find_loops(start_step, start_index)
562
657
  if loop_path:
563
658
  error = True
564
659
  if logger:
565
- loop_path = [f"{step}/{index}" for step, index in loop_path]
566
- logger.error(f"{' -> '.join(loop_path)} forms a loop in {self.name}")
660
+ loop_path_str = [f"{step}/{index}" for step, index in loop_path]
661
+ logger.error(f"Loop detected in {self.name}: {' -> '.join(loop_path_str)}")
567
662
 
568
663
  return not error
569
664
 
570
665
  def __get_task_module(self, name: str) -> Type["Task"]:
571
666
  '''
572
667
  Internal helper to import and cache a task module by name.
668
+
669
+ The name is expected in the format '<full.module.path>/<ClassName>'.
670
+
671
+ Args:
672
+ name (str): The fully qualified task module name.
673
+
674
+ Returns:
675
+ Type[Task]: The imported Task class.
676
+
677
+ Raises:
678
+ ValueError: If the name is not in the expected format.
679
+ ImportError: If the module cannot be imported.
680
+ AttributeError: If the class is not found in the module.
573
681
  '''
574
682
  # Create cache
575
683
  if self.__cache_tasks is None:
@@ -581,7 +689,8 @@ class Flowgraph(NamedSchema, DocsSchema):
581
689
  try:
582
690
  module_name, cls = name.split("/")
583
691
  except (ValueError, AttributeError):
584
- raise ValueError(f"task is not correctly formatted as <module>/<class>: {name}")
692
+ raise ValueError("Task module name is not correctly formatted as "
693
+ f"<full.module.path>/<ClassName>: {name}")
585
694
  module = importlib.import_module(module_name)
586
695
 
587
696
  self.__cache_tasks[name] = getattr(module, cls)
@@ -589,29 +698,29 @@ class Flowgraph(NamedSchema, DocsSchema):
589
698
 
590
699
  def get_task_module(self, step: str, index: Union[str, int]) -> Type["Task"]:
591
700
  """
592
- Returns the imported Python module for a given task node.
701
+ Returns the imported Python Task class for a given task node.
593
702
 
594
703
  Args:
595
704
  step (str): Step name of the node.
596
705
  index (int or str): Index of the node.
597
706
 
598
707
  Returns:
599
- module: The imported task module.
600
- """
708
+ Type[Task]: The imported Task class associated with the node.
601
709
 
710
+ Raises:
711
+ ValueError: If the node is not valid.
712
+ """
602
713
  index = str(index)
603
-
604
714
  if (step, index) not in self.get_nodes():
605
715
  raise ValueError(f"{step}/{index} is not a valid node in {self.name}.")
606
-
607
- return self.__get_task_module(self.get(step, index, 'taskmodule'))
716
+ return self.__get_task_module(self.get_graph_node(step, index).get_taskmodule())
608
717
 
609
718
  def get_all_tasks(self) -> Set[Type["Task"]]:
610
719
  '''
611
- Returns all unique task modules used in this flowgraph.
720
+ Returns all unique task classes used in this flowgraph.
612
721
 
613
722
  Returns:
614
- set[module]: A set of all imported task modules.
723
+ set[Type[Task]]: A set of all imported task classes.
615
724
  '''
616
725
  tasks = set()
617
726
  for step, index in self.get_nodes():
@@ -623,10 +732,18 @@ class Flowgraph(NamedSchema, DocsSchema):
623
732
  """
624
733
  Returns the metadata type for `getdict` serialization.
625
734
  """
626
-
627
735
  return Flowgraph.__name__
628
736
 
629
737
  def __get_graph_information(self):
738
+ '''
739
+ Internal helper to gather all node and edge info for graphviz.
740
+
741
+ Returns:
742
+ Tuple containing:
743
+ - set: All graph-level inputs (currently empty).
744
+ - dict: Information about each node.
745
+ - list: Information about each edge.
746
+ '''
630
747
  # Setup nodes
631
748
  node_exec_order = self.get_execution_order()
632
749
 
@@ -635,6 +752,7 @@ class Flowgraph(NamedSchema, DocsSchema):
635
752
  for step, index in rank_nodes:
636
753
  node_rank[f'{step}/{index}'] = rank
637
754
 
755
+ # TODO: This appears to be unused, legacy from when files were nodes
638
756
  all_graph_inputs = set()
639
757
 
640
758
  exit_nodes = [f'{step}/{index}' for step, index in self.get_exit_nodes()]
@@ -653,8 +771,9 @@ class Flowgraph(NamedSchema, DocsSchema):
653
771
  runtime_flow = RuntimeFlowgraph(self)
654
772
 
655
773
  for step, index in all_nodes:
656
- tool = self.get(step, index, "tool")
657
- task = self.get(step, index, "task")
774
+ graph_node = self.get_graph_node(step, index)
775
+ tool = graph_node.get("tool")
776
+ task = graph_node.get("task")
658
777
 
659
778
  inputs = []
660
779
  outputs = []
@@ -684,7 +803,7 @@ class Flowgraph(NamedSchema, DocsSchema):
684
803
  for step, index in all_nodes:
685
804
  node = f'{step}/{index}'
686
805
  all_inputs = []
687
- for in_step, in_index in self.get(step, index, 'input'):
806
+ for in_step, in_index in self.get_graph_node(step, index).get_input():
688
807
  all_inputs.append(f'{in_step}/{in_index}')
689
808
  for item in all_inputs:
690
809
  edges.append((item, node, 1 if node in exit_nodes else 2))
@@ -762,6 +881,7 @@ class Flowgraph(NamedSchema, DocsSchema):
762
881
  for node, info in nodes.items():
763
882
  subgraph_temp = subgraphs
764
883
 
884
+ # Create nested cluster subgraphs for steps like "a.b.c"
765
885
  for key in node.split(".")[0:-1]:
766
886
  if key not in subgraph_temp["graphs"]:
767
887
  subgraph_temp["graphs"][key] = {
@@ -770,6 +890,7 @@ class Flowgraph(NamedSchema, DocsSchema):
770
890
  }
771
891
  subgraph_temp = subgraph_temp["graphs"][key]
772
892
 
893
+ # Special cluster for input nodes
773
894
  if info['is_input']:
774
895
  if "sc-inputs" not in subgraph_temp["graphs"]:
775
896
  subgraph_temp["graphs"]["sc-inputs"] = {
@@ -792,6 +913,7 @@ class Flowgraph(NamedSchema, DocsSchema):
792
913
  penwidth=penwidth, fillcolor=fillcolor, shape="box")
793
914
 
794
915
  def make_node(graph, node, prefix):
916
+ '''Helper function to create a node in the graphviz object.'''
795
917
  info = nodes[node]
796
918
 
797
919
  shape = "oval"
@@ -829,6 +951,7 @@ class Flowgraph(NamedSchema, DocsSchema):
829
951
  if subgraph == "sc-inputs":
830
952
  graph.attr(style='invis')
831
953
  else:
954
+ # Style the cluster box
832
955
  graph.attr(color=fontcolor)
833
956
  graph.attr(style='rounded')
834
957
  graph.attr(shape='oval')
@@ -844,16 +967,33 @@ class Flowgraph(NamedSchema, DocsSchema):
844
967
  if graph is not parent:
845
968
  parent.subgraph(graph)
846
969
 
970
+ # Add all nodes at this level to the parent graph
847
971
  for subnode in graph_info["nodes"]:
848
972
  make_node(parent, subnode, prefix)
849
973
 
974
+ # Start building the graph from the root
850
975
  build_graph(subgraphs, dot, "")
851
976
 
977
+ # Add all the edges
852
978
  for edge0, edge1, weight in edges:
853
979
  dot.edge(f'{edge0}{out_label_suffix}', f'{edge1}{in_label_suffix}', weight=str(weight))
854
980
 
855
981
  dot.render(filename=fileroot, cleanup=True)
856
982
 
983
+ def get_graph_node(self, step: str, index: Optional[Union[int, str]] = None) \
984
+ -> "FlowgraphNodeSchema":
985
+ """
986
+ Get the flowgraph node for this step and index
987
+ """
988
+ if index is None:
989
+ index = "0"
990
+ index = str(index)
991
+
992
+ if (step, index) not in self.get_nodes():
993
+ raise ValueError(f"{step}/{index} is not a valid node in {self.name}.")
994
+
995
+ return self.get(step, index, field="schema")
996
+
857
997
  def _generate_doc(self, doc,
858
998
  ref_root: str = "",
859
999
  key_offset: Optional[Tuple[str, ...]] = None,
@@ -883,7 +1023,7 @@ class Flowgraph(NamedSchema, DocsSchema):
883
1023
  sec = build_section(f"{step}/{index}",
884
1024
  f"{ref_root}-flow-{self.name}-nodes-{step}-{index}")
885
1025
  sec += BaseSchema._generate_doc(
886
- self.get(step, index, field="schema"),
1026
+ self.get_graph_node(step, index),
887
1027
  doc,
888
1028
  ref_root=f"{ref_root}-flow-{self.name}-nodes-{step}-{index}",
889
1029
  key_offset=(*key_offset, "flowgraph", self.name),
@@ -898,10 +1038,10 @@ class RuntimeFlowgraph:
898
1038
  '''
899
1039
  A runtime representation of a flowgraph for a specific execution.
900
1040
 
901
- This class creates a "view" of a base flowgraph that considers runtime
902
- options such as the start step (`-from`), end step (`-to`), and nodes to
903
- exclude (`-prune`). It computes the precise subgraph of nodes that need
904
- to be executed for a given run.
1041
+ This class creates a "view" of a base `Flowgraph` that considers
1042
+ runtime options such as the start step (`-from`), end step (`-to`),
1043
+ and nodes to exclude (`-prune`). It computes the precise subgraph of
1044
+ nodes that need to be executed for a given run.
905
1045
  '''
906
1046
 
907
1047
  def __init__(self, base: Flowgraph,
@@ -1073,8 +1213,12 @@ class RuntimeFlowgraph:
1073
1213
  '''
1074
1214
  Returns the entry nodes for this runtime graph.
1075
1215
 
1216
+ This includes user-defined `-from` nodes (if they are part of
1217
+ the graph) and any nodes whose inputs are pruned or outside the
1218
+ computed graph.
1219
+
1076
1220
  Returns:
1077
- tuple[tuple(str,str)]: A tuple of all entry nodes.
1221
+ tuple[tuple(str,str)]: A sorted tuple of all entry nodes.
1078
1222
  '''
1079
1223
  return self.__from
1080
1224
 
@@ -1282,6 +1426,9 @@ class RuntimeFlowgraph:
1282
1426
  class FlowgraphNodeSchema(BaseSchema):
1283
1427
  '''
1284
1428
  Schema definition for a single node within a flowgraph.
1429
+
1430
+ This class defines the parameters that can be set on a per-node
1431
+ basis, such as inputs, weights, goals, and the task to execute.
1285
1432
  '''
1286
1433
 
1287
1434
  def __init__(self):
@@ -1289,7 +1436,6 @@ class FlowgraphNodeSchema(BaseSchema):
1289
1436
  Initializes a new FlowgraphNodeSchema.
1290
1437
  '''
1291
1438
  super().__init__()
1292
-
1293
1439
  schema_flowgraph(self)
1294
1440
 
1295
1441
  @classmethod
@@ -1297,9 +1443,130 @@ class FlowgraphNodeSchema(BaseSchema):
1297
1443
  """
1298
1444
  Returns the metadata type for `getdict` serialization.
1299
1445
  """
1300
-
1301
1446
  return FlowgraphNodeSchema.__name__
1302
1447
 
1448
+ def add_args(self, arg: Union[List[str], str], clobber: bool = False):
1449
+ '''
1450
+ Adds command-line arguments specific to this node.
1451
+
1452
+ Args:
1453
+ arg (list[str] or str): The argument or list of arguments to add.
1454
+ clobber (bool, optional): If True, replaces all existing args
1455
+ with the new ones. If False, appends. Defaults to False.
1456
+ '''
1457
+ if clobber:
1458
+ self.set("args", arg)
1459
+ else:
1460
+ self.add("args", arg)
1461
+
1462
+ def get_args(self) -> List[str]:
1463
+ '''
1464
+ Gets the list of command-line arguments for this node.
1465
+
1466
+ Returns:
1467
+ list[str]: The list of arguments.
1468
+ '''
1469
+ return self.get("args")
1470
+
1471
+ def add_weight(self, metric: str, weight: float):
1472
+ '''
1473
+ Sets a weight for a specific metric for this node.
1474
+
1475
+ Weights are used in optimization tasks to define the "cost" of a
1476
+ particular metric.
1477
+
1478
+ Args:
1479
+ metric (str): The name of the metric (e.g., 'errors', 'area').
1480
+ weight (float): The weight value.
1481
+ '''
1482
+ self.set("weight", metric, weight)
1483
+
1484
+ def get_weight(self, metric: str) -> Optional[float]:
1485
+ '''
1486
+ Gets the weight for a specific metric for this node.
1487
+
1488
+ Args:
1489
+ metric (str): The name of the metric.
1490
+
1491
+ Returns:
1492
+ float or None: The weight value, or None if not set.
1493
+ '''
1494
+ if metric not in self.getkeys("weight"):
1495
+ return None
1496
+ return self.get("weight", metric)
1497
+
1498
+ def add_goal(self, metric: str, weight: float):
1499
+ '''
1500
+ Sets a goal for a specific metric for this node.
1501
+
1502
+ Goals are used to determine if a task run is acceptable.
1503
+
1504
+ Args:
1505
+ metric (str): The name of the metric (e.g., 'errors', 'setupwns').
1506
+ weight (float): The goal value (e.g., 0 for 'errors').
1507
+ '''
1508
+ self.set("goal", metric, weight)
1509
+
1510
+ def get_goal(self, metric: str) -> Optional[float]:
1511
+ '''
1512
+ Gets the goal for a specific metric for this node.
1513
+
1514
+ Args:
1515
+ metric (str): The name of the metric.
1516
+
1517
+ Returns:
1518
+ float or None: The goal value, or None if not set.
1519
+ '''
1520
+ if metric not in self.getkeys("goal"):
1521
+ return None
1522
+ return self.get("goal", metric)
1523
+
1524
+ def get_tool(self) -> str:
1525
+ '''
1526
+ Gets the tool associated with this node.
1527
+
1528
+ Returns:
1529
+ str: The name of the tool (e.g., 'openroad').
1530
+ '''
1531
+ return self.get("tool")
1532
+
1533
+ def get_task(self) -> str:
1534
+ '''
1535
+ Gets the task associated with this node.
1536
+
1537
+ Returns:
1538
+ str: The name of the task (e.g., 'place').
1539
+ '''
1540
+ return self.get("task")
1541
+
1542
+ def get_taskmodule(self) -> str:
1543
+ '''
1544
+ Gets the fully qualified Python module/class for this node's task.
1545
+
1546
+ Returns:
1547
+ str: The task module string (e.g.,
1548
+ 'siliconcompiler.tools.openroad/Place').
1549
+ '''
1550
+ return self.get("taskmodule")
1551
+
1552
+ def get_input(self) -> List[Tuple[str, str]]:
1553
+ '''
1554
+ Gets the list of input nodes (dependencies) for this node.
1555
+
1556
+ Returns:
1557
+ list[tuple(str,str)]: A list of (step, index) tuples.
1558
+ '''
1559
+ return self.get("input")
1560
+
1561
+ def has_input(self) -> bool:
1562
+ '''
1563
+ Checks if this node has any inputs.
1564
+
1565
+ Returns:
1566
+ bool: True if the node has one or more inputs, False otherwise.
1567
+ '''
1568
+ return bool(self.get_input())
1569
+
1303
1570
 
1304
1571
  ###############################################################################
1305
1572
  # Flow Configuration
@@ -1308,114 +1575,136 @@ def schema_flowgraph(schema: FlowgraphNodeSchema):
1308
1575
  '''
1309
1576
  Defines the schema parameters for a flowgraph node.
1310
1577
 
1311
- This function is called to populate a schema with parameters that
1312
- define a node's properties, such as its inputs, weights, goals, and the
1313
- tool/task it executes.
1578
+ This function is called to populate a `FlowgraphNodeSchema` with
1579
+ parameters that define a node's properties, such as its inputs,
1580
+ weights, goals, and the tool/task it executes.
1314
1581
 
1315
1582
  Args:
1316
- schema (Schema): The schema object to configure.
1583
+ schema (FlowgraphNodeSchema): The schema object to configure.
1317
1584
  '''
1318
- schema = EditableSchema(schema)
1585
+ edit = EditableSchema(schema)
1319
1586
 
1320
1587
  # flowgraph input
1321
- schema.insert(
1588
+ edit.insert(
1322
1589
  'input',
1323
1590
  Parameter(
1324
1591
  '[(str,str)]',
1325
1592
  scope=Scope.GLOBAL,
1326
- shorthelp="Flowgraph: step input",
1593
+ shorthelp="Flowgraph: Node inputs",
1327
1594
  switch="-flowgraph_input 'flow step index <(str,str)>'",
1328
1595
  example=[
1329
1596
  "cli: -flowgraph_input 'asicflow cts 0 (place,0)'",
1330
- "api: flow.set('flowgraph', 'asicflow', 'cts', '0', 'input', ('place', '0'))"],
1331
- help=trim("""A list of inputs for the current step and index, specified as a
1332
- (step, index) tuple.""")))
1597
+ "api: flow.add('flowgraph', 'asicflow', 'cts', '0', 'input', ('place', '0'))"],
1598
+ help=trim("""
1599
+ A list of inputs for this flowgraph node, where each input is
1600
+ specified as a (step, index) tuple. This defines the dependencies
1601
+ of this node.
1602
+ """)))
1333
1603
 
1334
1604
  # flowgraph metric weights
1335
1605
  metric = 'default'
1336
- schema.insert(
1606
+ edit.insert(
1337
1607
  'weight', metric,
1338
1608
  Parameter(
1339
1609
  'float',
1340
1610
  scope=Scope.GLOBAL,
1341
1611
  defvalue=0.0,
1342
- shorthelp="Flowgraph: metric weights",
1612
+ shorthelp="Flowgraph: Metric weights",
1343
1613
  switch="-flowgraph_weight 'flow step index metric <float>'",
1344
1614
  example=[
1345
1615
  "cli: -flowgraph_weight 'asicflow cts 0 area_cells 1.0'",
1346
1616
  "api: flow.set('flowgraph', 'asicflow', 'cts', '0', 'weight', 'area_cells', 1.0)"],
1347
- help=trim("""Weights specified on a per step and per metric basis used to give
1348
- effective "goodness" score for a step by calculating the sum all step
1349
- real metrics results by the corresponding per step weights.""")))
1617
+ help=trim("""
1618
+ Weights specified on a per-node and per-metric basis, used by
1619
+ optimization tasks (like 'minimum') to calculate a "goodness"
1620
+ score for a run. The score is typically a weighted sum of
1621
+ metric results.
1622
+ """)))
1350
1623
 
1351
- schema.insert(
1624
+ # flowgraph metric goals
1625
+ edit.insert(
1352
1626
  'goal', metric,
1353
1627
  Parameter(
1354
1628
  'float',
1355
1629
  scope=Scope.GLOBAL,
1356
- shorthelp="Flowgraph: metric goals",
1630
+ shorthelp="Flowgraph: Metric goals",
1357
1631
  switch="-flowgraph_goal 'flow step index metric <float>'",
1358
1632
  example=[
1359
- "cli: -flowgraph_goal 'asicflow cts 0 area_cells 1.0'",
1633
+ "cli: -flowgraph_goal 'asicflow cts 0 errors 0'",
1360
1634
  "api: flow.set('flowgraph', 'asicflow', 'cts', '0', 'goal', 'errors', 0)"],
1361
- help=trim("""Goals specified on a per step and per metric basis used to
1362
- determine whether a certain task can be considered when merging
1363
- multiple tasks at a minimum or maximum node. A task is considered
1364
- failing if the absolute value of any of its metrics are larger than
1365
- the goal for that metric, if set.""")))
1635
+ help=trim("""
1636
+ Goals specified on a per-node and per-metric basis used to
1637
+ determine whether a task run is considered successful. A task run
1638
+ may be considered failing if the absolute value of any of its
1639
+ reported metrics is larger than the goal for that metric (if set).
1640
+ This is often used for metrics like 'errors' or 'setupwns'
1641
+ where the goal is 0.
1642
+ """)))
1366
1643
 
1367
1644
  # flowgraph tool
1368
- schema.insert(
1645
+ edit.insert(
1369
1646
  'tool',
1370
1647
  Parameter(
1371
1648
  'str',
1372
1649
  scope=Scope.GLOBAL,
1373
- shorthelp="Flowgraph: tool selection",
1650
+ shorthelp="Flowgraph: Tool selection",
1374
1651
  switch="-flowgraph_tool 'flow step index <str>'",
1375
1652
  example=[
1376
1653
  "cli: -flowgraph_tool 'asicflow place 0 openroad'",
1377
1654
  "api: flow.set('flowgraph', 'asicflow', 'place', '0', 'tool', 'openroad')"],
1378
- help=trim("""Name of the tool name used for task execution.""")))
1655
+ help=trim("""
1656
+ Name of the tool (e.g., 'openroad', 'yosys', 'builtin') that
1657
+ this node will execute.
1658
+ """)))
1379
1659
 
1380
1660
  # task (belonging to tool)
1381
- schema.insert(
1661
+ edit.insert(
1382
1662
  'task',
1383
1663
  Parameter(
1384
1664
  'str',
1385
1665
  scope=Scope.GLOBAL,
1386
- shorthelp="Flowgraph: task selection",
1666
+ shorthelp="Flowgraph: Task selection",
1387
1667
  switch="-flowgraph_task 'flow step index <str>'",
1388
1668
  example=[
1389
1669
  "cli: -flowgraph_task 'asicflow myplace 0 place'",
1390
1670
  "api: flow.set('flowgraph', 'asicflow', 'myplace', '0', 'task', 'place')"],
1391
- help=trim("""Name of the tool associated task used for step execution.""")))
1671
+ help=trim("""
1672
+ Name of the task (e.g., 'place', 'syn', 'join') associated
1673
+ with the node's tool.
1674
+ """)))
1392
1675
 
1393
- schema.insert(
1676
+ # task module
1677
+ edit.insert(
1394
1678
  'taskmodule',
1395
1679
  Parameter(
1396
1680
  'str',
1397
1681
  scope=Scope.GLOBAL,
1398
- shorthelp="Flowgraph: task module",
1682
+ shorthelp="Flowgraph: Task module",
1399
1683
  switch="-flowgraph_taskmodule 'flow step index <str>'",
1400
1684
  example=[
1401
1685
  "cli: -flowgraph_taskmodule 'asicflow place 0 "
1402
- "siliconcompiler.tools.openroad.place'",
1686
+ "siliconcompiler.tools.openroad/Place'",
1403
1687
  "api: flow.set('flowgraph', 'asicflow', 'place', '0', 'taskmodule', "
1404
- "'siliconcompiler.tools.openroad.place')"],
1688
+ "'siliconcompiler.tools.openroad/Place')"],
1405
1689
  help=trim("""
1406
- Full python module name of the task module used for task setup and execution.
1690
+ Full Python module path and class name of the task, formatted as
1691
+ '<full.module.path>/<ClassName>'. This is used to import and
1692
+ instantiate the correct Task class for setup and execution.
1407
1693
  """)))
1408
1694
 
1409
1695
  # flowgraph arguments
1410
- schema.insert(
1696
+ edit.insert(
1411
1697
  'args',
1412
1698
  Parameter(
1413
1699
  '[str]',
1414
1700
  scope=Scope.GLOBAL,
1415
- shorthelp="Flowgraph: setup arguments",
1701
+ shorthelp="Flowgraph: Node-specific arguments",
1416
1702
  switch="-flowgraph_args 'flow step index <str>'",
1417
1703
  example=[
1418
- "cli: -flowgraph_args 'asicflow cts 0 0'",
1419
- "api: flow.add('flowgraph', 'asicflow', 'cts', '0', 'args', '0')"],
1420
- help=trim("""User specified flowgraph string arguments specified on a per
1421
- step and per index basis.""")))
1704
+ "cli: -flowgraph_args 'asicflow cts 0 buffer_cells'",
1705
+ "api: flow.add('flowgraph', 'asicflow', 'cts', '0', 'args', 'buffer_cells')"],
1706
+ help=trim("""
1707
+ User-specified arguments passed to the task's `setup()` method.
1708
+ This allows for customizing a specific node's behavior without
1709
+ affecting other nodes running the same task.
1710
+ """)))