siliconcompiler 0.35.1__py3-none-any.whl → 0.35.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.
- siliconcompiler/_metadata.py +1 -1
- siliconcompiler/apps/sc_install.py +1 -1
- siliconcompiler/apps/sc_issue.py +8 -16
- siliconcompiler/apps/smake.py +106 -100
- siliconcompiler/checklist.py +349 -91
- siliconcompiler/design.py +8 -1
- siliconcompiler/flowgraph.py +419 -130
- siliconcompiler/flows/showflow.py +1 -2
- siliconcompiler/library.py +6 -5
- siliconcompiler/package/https.py +10 -5
- siliconcompiler/project.py +87 -37
- siliconcompiler/remote/client.py +17 -6
- siliconcompiler/scheduler/scheduler.py +284 -59
- siliconcompiler/scheduler/schedulernode.py +154 -102
- siliconcompiler/schema/__init__.py +3 -2
- siliconcompiler/schema/_metadata.py +1 -1
- siliconcompiler/schema/baseschema.py +210 -93
- siliconcompiler/schema/namedschema.py +21 -13
- siliconcompiler/schema/parameter.py +8 -1
- siliconcompiler/schema/safeschema.py +18 -7
- siliconcompiler/schema_support/dependencyschema.py +23 -3
- siliconcompiler/schema_support/filesetschema.py +10 -4
- siliconcompiler/schema_support/option.py +37 -34
- siliconcompiler/schema_support/pathschema.py +7 -2
- siliconcompiler/schema_support/record.py +5 -4
- siliconcompiler/targets/asap7_demo.py +4 -1
- siliconcompiler/tool.py +100 -8
- siliconcompiler/tools/__init__.py +10 -7
- siliconcompiler/tools/bambu/convert.py +19 -0
- siliconcompiler/tools/builtin/__init__.py +3 -2
- siliconcompiler/tools/builtin/filter.py +108 -0
- siliconcompiler/tools/builtin/importfiles.py +154 -0
- siliconcompiler/tools/execute/exec_input.py +4 -3
- siliconcompiler/tools/gtkwave/show.py +6 -2
- siliconcompiler/tools/icarus/compile.py +1 -0
- siliconcompiler/tools/klayout/scripts/klayout_show.py +1 -1
- siliconcompiler/tools/klayout/show.py +17 -5
- siliconcompiler/tools/openroad/screenshot.py +0 -1
- siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
- siliconcompiler/tools/openroad/scripts/common/write_images.tcl +2 -0
- siliconcompiler/tools/openroad/show.py +10 -0
- siliconcompiler/tools/surfer/show.py +7 -2
- siliconcompiler/tools/verilator/compile.py +2 -2
- siliconcompiler/tools/yosys/prepareLib.py +7 -2
- siliconcompiler/tools/yosys/syn_asic.py +20 -2
- siliconcompiler/toolscripts/_tools.json +5 -5
- siliconcompiler/toolscripts/rhel9/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/toolscripts/ubuntu22/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/toolscripts/ubuntu24/{install-yosys-wildebeest.sh → install-wildebeest.sh} +5 -5
- siliconcompiler/utils/__init__.py +1 -2
- siliconcompiler/utils/issue.py +38 -45
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/METADATA +4 -4
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/RECORD +57 -55
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/WHEEL +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/entry_points.txt +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/licenses/LICENSE +0 -0
- {siliconcompiler-0.35.1.dist-info → siliconcompiler-0.35.3.dist-info}/top_level.txt +0 -0
siliconcompiler/flowgraph.py
CHANGED
|
@@ -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
|
|
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,33 +64,50 @@ 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
|
-
|
|
69
|
-
|
|
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 (
|
|
81
|
-
a
|
|
82
|
-
|
|
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
|
-
>>>
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
|
92
|
-
if step in (Parameter.GLOBAL_KEY, 'default'
|
|
110
|
+
if step in (Parameter.GLOBAL_KEY, 'default') or step.startswith("sc_"):
|
|
93
111
|
raise ValueError(f"{step} is a reserved name")
|
|
94
112
|
|
|
95
113
|
index = str(index)
|
|
@@ -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
|
|
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.
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
133
|
-
|
|
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.
|
|
143
|
-
|
|
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
|
|
190
|
+
if tail_node in head_node.get_input():
|
|
191
|
+
# Edge already exists
|
|
158
192
|
return
|
|
159
193
|
|
|
160
|
-
|
|
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
|
|
180
|
-
self.remove_node(step,
|
|
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
|
-
|
|
189
|
-
node_inputs = self.
|
|
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
|
-
#
|
|
235
|
+
# Remove the node from the schema
|
|
192
236
|
self.remove(step, index)
|
|
193
237
|
|
|
194
|
-
#
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
215
|
-
|
|
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 (
|
|
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.
|
|
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.
|
|
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)]:
|
|
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)]:
|
|
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.
|
|
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)]:
|
|
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.
|
|
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,
|
|
363
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
#
|
|
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
|
-
|
|
566
|
-
logger.error(f"{' -> '.join(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
720
|
+
Returns all unique task classes used in this flowgraph.
|
|
612
721
|
|
|
613
722
|
Returns:
|
|
614
|
-
set[
|
|
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
|
-
|
|
657
|
-
|
|
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.
|
|
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.
|
|
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
|
|
902
|
-
options such as the start step (`-from`), end step (`-to`),
|
|
903
|
-
exclude (`-prune`). It computes the precise subgraph of
|
|
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
|
|
1312
|
-
define a node's properties, such as its inputs,
|
|
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 (
|
|
1583
|
+
schema (FlowgraphNodeSchema): The schema object to configure.
|
|
1317
1584
|
'''
|
|
1318
|
-
|
|
1585
|
+
edit = EditableSchema(schema)
|
|
1319
1586
|
|
|
1320
1587
|
# flowgraph input
|
|
1321
|
-
|
|
1588
|
+
edit.insert(
|
|
1322
1589
|
'input',
|
|
1323
1590
|
Parameter(
|
|
1324
1591
|
'[(str,str)]',
|
|
1325
1592
|
scope=Scope.GLOBAL,
|
|
1326
|
-
shorthelp="Flowgraph:
|
|
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.
|
|
1331
|
-
help=trim("""
|
|
1332
|
-
|
|
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
|
-
|
|
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:
|
|
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("""
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
1633
|
+
"cli: -flowgraph_goal 'asicflow cts 0 errors 0'",
|
|
1360
1634
|
"api: flow.set('flowgraph', 'asicflow', 'cts', '0', 'goal', 'errors', 0)"],
|
|
1361
|
-
help=trim("""
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
failing if the absolute value of any of its
|
|
1365
|
-
the goal for that metric
|
|
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
|
-
|
|
1645
|
+
edit.insert(
|
|
1369
1646
|
'tool',
|
|
1370
1647
|
Parameter(
|
|
1371
1648
|
'str',
|
|
1372
1649
|
scope=Scope.GLOBAL,
|
|
1373
|
-
shorthelp="Flowgraph:
|
|
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("""
|
|
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
|
-
|
|
1661
|
+
edit.insert(
|
|
1382
1662
|
'task',
|
|
1383
1663
|
Parameter(
|
|
1384
1664
|
'str',
|
|
1385
1665
|
scope=Scope.GLOBAL,
|
|
1386
|
-
shorthelp="Flowgraph:
|
|
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("""
|
|
1671
|
+
help=trim("""
|
|
1672
|
+
Name of the task (e.g., 'place', 'syn', 'join') associated
|
|
1673
|
+
with the node's tool.
|
|
1674
|
+
""")))
|
|
1392
1675
|
|
|
1393
|
-
|
|
1676
|
+
# task module
|
|
1677
|
+
edit.insert(
|
|
1394
1678
|
'taskmodule',
|
|
1395
1679
|
Parameter(
|
|
1396
1680
|
'str',
|
|
1397
1681
|
scope=Scope.GLOBAL,
|
|
1398
|
-
shorthelp="Flowgraph:
|
|
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
|
|
1686
|
+
"siliconcompiler.tools.openroad/Place'",
|
|
1403
1687
|
"api: flow.set('flowgraph', 'asicflow', 'place', '0', 'taskmodule', "
|
|
1404
|
-
"'siliconcompiler.tools.openroad
|
|
1688
|
+
"'siliconcompiler.tools.openroad/Place')"],
|
|
1405
1689
|
help=trim("""
|
|
1406
|
-
Full
|
|
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
|
-
|
|
1696
|
+
edit.insert(
|
|
1411
1697
|
'args',
|
|
1412
1698
|
Parameter(
|
|
1413
1699
|
'[str]',
|
|
1414
1700
|
scope=Scope.GLOBAL,
|
|
1415
|
-
shorthelp="Flowgraph:
|
|
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
|
|
1419
|
-
"api: flow.add('flowgraph', 'asicflow', 'cts', '0', 'args', '
|
|
1420
|
-
help=trim("""
|
|
1421
|
-
|
|
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
|
+
""")))
|