CIRCT 22.0.0git
Loading...
Searching...
No Matches
synth.py
Go to the documentation of this file.
1# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2# See https://llvm.org/LICENSE.txt for license information.
3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5from . import synth, hw
6from ._synth_ops_gen import *
7from .._mlir_libs._circt._synth import _LongestPathAnalysis, _LongestPathCollection, _LongestPathDataflowPath, _LongestPathHistory, _LongestPathObject
8
9from dataclasses import dataclass
10from typing import Any, Dict, List, Union
11
12# ============================================================================
13# Core Data Structures for Synth Longest Path Analysis
14# ============================================================================
15
16
17@dataclass
19 """
20 Represents a single element in a hierarchical instance path.
21 In hardware design, modules are instantiated hierarchically. This class
22 represents one level in that hierarchy, containing both the module type
23 and the specific instance name.
24 Attributes:
25 instance_name: The name of this specific instance
26 module_name: The type/name of the module being instantiated
27 """
28 _instance: hw.InstanceOp
29
30 def __init__(self, instance: hw.InstanceOp):
31 self._instance_instance = instance
32
33 @property
34 def instance_name(self) -> str:
35 return self._instance_instance.attributes["instanceName"].value
36
37 @property
38 def module_name(self) -> str:
39 return self._instance_instance.attributes["moduleName"].value
40
41
42@dataclass
43class Object:
44 """
45 Represents a signal or port object in the dataflow graph.
46 This class encapsulates a specific signal within the hardware hierarchy,
47 including its location in the instance hierarchy, signal name, and bit position
48 for multi-bit signals.
49 Attributes:
50 instance_path: Hierarchical path to the module containing this object
51 name: The signal/port name within the module
52 bit_pos: Bit position for multi-bit signals (0 for single-bit)
53 """
54
55 _object: _LongestPathObject
56
57 # TODO: Associate with an MLIR value/op
58
59 def __str__(self) -> str:
60 """
61 Generate a human-readable string representation of this object.
62 Format: "module1:instance1/module2:instance2 signal_name[bit_pos]"
63 """
64 path = "/".join(f"{elem.module_name}:{elem.instance_name}"
66 return f"{path} {self.name}[{self.bit_pos}]"
67
68 def __repr__(self) -> str:
69 return f"Object({self.instance_path}, {self.name}, {self.bit_pos})"
70
71 @property
72 def instance_path(self) -> List[Instance]:
73 """Get the hierarchical instance path to this object."""
74 operations = self._object.instance_path
75
76 return [Instance(op) for op in operations]
77
78 @property
79 def name(self) -> str:
80 """Get the name of this signal/port."""
81 return self._object.name
82
83 @property
84 def bit_pos(self) -> int:
85 """Get the bit position for multi-bit signals."""
86 return self._object.bit_pos
87
88
89@dataclass
91 """
92 Represents a debug point in the timing path history.
93 Debug points are intermediate points along a timing path that provide
94 insight into the delay accumulation and signal propagation through
95 the circuit. Each point captures the state at a specific location.
96 Attributes:
97 object: The signal/object at this debug point
98 delay: Accumulated delay up to this point (in timing units)
99 comment: Optional descriptive comment about this point
100 """
101
102 object: Object
103 delay: int
104 comment: str
105
106 @classmethod
107 def from_dict(cls, data: Dict[str, Any]) -> "DebugPoint":
108 """Create a DebugPoint from a dictionary representation."""
109 return cls(
110 object=Object.from_dict(data["object"]),
111 delay=data["delay"],
112 comment=data["comment"],
113 )
114
115
116@dataclass
118 """
119 Represents a complete dataflow path from end point to start point.
120 A dataflow path captures the complete timing path through a circuit,
121 from an output point (end-point) back to an input point (start-point), including
122 all intermediate debug points and the total delay.
123 Attributes:
124 end_point: The output signal/object where this path ends
125 path: The OpenPath containing the detailed path information
126 root: The root module name for this analysis
127 """
128
129 _path: _LongestPathDataflowPath
130
131 @property
132 def delay(self) -> int:
133 """Get the total delay of this path in timing units."""
134 return self._path.delay
135
136 @property
137 def start_point(self) -> Object:
138 """Get the input signal/object where this path begins."""
139 return Object(self._path.start_point)
140
141 @property
142 def end_point(self) -> Object:
143 """Get the output signal/object where this path ends."""
144 return Object(self._path.end_point)
145
146 @property
147 def history(self) -> List[DebugPoint]:
148 """Get the history of debug points along this path."""
149 return [i for i in LongestPathHistory(self._path.history)]
150
151 @property
152 def root(self) -> str:
153 """Get the root module name for this analysis."""
154 return self._path.root.attributes["sym_name"].value
155
156 # ========================================================================
157 # Visualization and Analysis Methods
158 # ========================================================================
159
160 def to_flamegraph(self) -> str:
161 """
162 Convert this path to FlameGraph format for visualization.
163 FlameGraphs are a visualization technique that shows call stacks or
164 in this case, timing paths through the circuit hierarchy. Each line
165 represents a segment of the path with its associated delay.
166 The format is: "hierarchy_path delay_increment"
167 where hierarchy_path uses semicolons to separate hierarchy levels.
168 Returns:
169 String in FlameGraph format showing the timing path progression
170 """
171 trace = []
172 prefix = f"top:{self.root}"
173
174 # Build hierarchy strings for start and end points
175 start_point_hierarchy = self._build_hierarchy_string(
177 end_point_hierarchy = self._build_hierarchy_string(self.end_point, prefix)
178
179 # Track current position and delay for incremental output
180 current_hierarchy = start_point_hierarchy
181 current_delay = 0
182
183 # Process debug history points in reverse order (from input to output)
184 for debug_point in self.history[::-1]:
185 history_hierarchy = self._build_hierarchy_string(debug_point.object,
186 prefix)
187 if history_hierarchy:
188 # Add segment from current position to this debug point
189 delay_increment = debug_point.delay - current_delay
190 trace.append(f"{current_hierarchy} {delay_increment}")
191
192 # Update current position
193 current_hierarchy = history_hierarchy
194 current_delay = debug_point.delay
195
196 # Add final segment to end-point if there's remaining delay
197 if current_delay != self.delay:
198 final_delay = self.delay - current_delay
199 trace.append(f"{end_point_hierarchy} {final_delay}")
200
201 return "\n".join(trace)
202
203 def _build_hierarchy_string(self, obj: Object, prefix: str = "") -> str:
204 """
205 Build a hierarchical string representation of an Object for FlameGraph format.
206 This method constructs a semicolon-separated hierarchy string that represents
207 the full path from the top-level module down to the specific signal. This
208 format is compatible with FlameGraph visualization tools.
209 Args:
210 obj: Object to represent in the hierarchy
211 prefix: Top-level prefix (typically "top:module_name")
212 Returns:
213 Hierarchical string in format: "top:root;module1:inst1;module2:inst2;signal[bit]"
214 """
215 parts = [prefix]
216
217 # Add each level of the instance hierarchy
218 for elem in obj.instance_path:
219 parts.append(f"{elem.instance_name}:{elem.module_name}")
220
221 # Add the signal name with bit position if applicable
222 signal_part = obj.name
223 signal_part += f"[{obj.bit_pos}]"
224 parts.append(signal_part)
225
226 return ";".join(parts)
227
228
229# ============================================================================
230# Analysis Collection Classes
231# ============================================================================
232
233
235 """
236 A collection of timing paths sorted by delay (longest first).
237 This class provides a Python wrapper around the C++ LongestPathCollection,
238 offering convenient access to timing paths with caching for performance.
239 The paths are pre-sorted by delay in descending order.
240 Attributes:
241 collection: The underlying C++ collection object
242 length: Number of paths in the collection
243 """
244
245 def __init__(self, collection):
246 """
247 Initialize the collection wrapper.
248 Args:
249 collection: The underlying C++ LongestPathCollection object
250 """
251 self.collection = collection
252 self.length = self.collection.get_size()
253
254 # ========================================================================
255 # Collection Interface Methods
256 # ========================================================================
257
258 def __len__(self) -> int:
259 """Get the number of paths in the collection."""
260 return self.length
261
263 self, index: Union[slice,
264 int]) -> Union[DataflowPath, List[DataflowPath]]:
265 """
266 Get a specific path from the collection by index.
267 Supports both integer and slice indexing. Integer indices can be negative.
268
269 Args:
270 index: Integer index or slice object to access paths
271
272 Returns:
273 DataflowPath or list of DataflowPaths for slice access
274
275 Raises:
276 IndexError: If index is out of range
277 """
278 if isinstance(index, slice):
279 return [self[i] for i in range(*index.indices(len(self)))]
280
281 # Handle negative indexing
282 if index < 0:
283 index += self.length
284 if index < 0 or index >= self.length:
285 raise IndexError("Index out of range")
286
287 return DataflowPath(self.collection.get_path(index))
288
289 # ========================================================================
290 # Analysis and Query Methods
291 # ========================================================================
292
293 @property
294 def longest_path(self) -> DataflowPath:
295 """Get the path with the maximum delay (first element since sorted)."""
296 return self[0]
297
298 def get_by_delay_ratio(self, ratio: float) -> DataflowPath:
299 """
300 Get the path at the specified position in the delay-sorted collection.
301 Since paths are sorted by delay in descending order, higher ratios
302 correspond to paths with higher delays (closer to the critical path).
303 Args:
304 ratio: Position ratio between 0.0 and 1.0
305 (e.g., 1.0 = longest delay path, 0.0 = shortest delay path,
306 0.95 = path among the top 5% slowest paths)
307 Returns:
308 DataflowPath at the specified position ratio
309 """
310 assert ratio >= 0.0 and ratio <= 1.0, "Ratio must be between 0.0 and 1.0"
311 index = int(len(self) * (1 - ratio))
312 return self[index]
313
314 def print_summary(self) -> None:
315 """Print a statistical summary of path delays in the collection."""
316 print(f"Total paths: {len(self)}")
317 print(f"Max delay: {self.longest_path.delay}")
318 print(f"Min delay: {self[-1].delay}")
319 print(f"50th percentile delay: {self.get_by_delay_ratio(0.5).delay}")
320 print(f"90th percentile delay: {self.get_by_delay_ratio(0.9).delay}")
321 print(f"95th percentile delay: {self.get_by_delay_ratio(0.95).delay}")
322 print(f"99th percentile delay: {self.get_by_delay_ratio(0.99).delay}")
323 print(f"99.9th percentile delay: {self.get_by_delay_ratio(0.999).delay}")
324
325 def merge(self, src: "LongestPathCollection"):
326 """
327 Merge another collection into this one.
328 Args:
329 src: The collection to merge into this one
330 """
331 self.collection.merge(src.collection)
332 # Re-initialize to reflect the merged collection
333 self.__init__(self.collection)
334
335 def drop_non_critical_paths(self, per_end_point: bool = True):
336 """
337 Drop all paths except the longest path per end point or start point.
338 Args:
339 per_end_point: Whether to keep only the longest path per end point
340 (True) or per start point (False)
341 """
342 self.collection.drop_non_critical_paths(per_end_point)
343 # Re-initialize to reflect the dropped paths
344 self.__init__(self.collection)
345
346
347# ============================================================================
348# Main Analysis Interface
349# ============================================================================
350
351
353 """
354 Main interface for performing longest path analysis on Synth dialect.
355 This class provides a Python wrapper around the C++ LongestPathAnalysis,
356 enabling timing analysis of Synth dialect. It can identify critical timing
357 paths and provide detailed path information.
358 Attributes:
359 analysis: The underlying C++ analysis object
360 """
361
362 def __init__(self,
363 module,
364 collect_debug_info: bool = True,
365 keep_only_max_delay_paths: bool = False,
366 lazy_computation: bool = False,
367 top_module_name: str = ""):
368 """
369 Initialize the longest path analysis for a given module.
370 Args:
371 module: The MLIR module to analyze
372 collect_debug_info: Whether to include debug points in the analysis.
373 Debug points provide additional information about the path,
374 but increase analysis time and memory usage.
375 keep_only_max_delay_paths: Keep only maximum-delay paths in collections.
376 lazy_computation: Enable lazy (on-demand) computation.
377 """
378 self.analysis = synth._LongestPathAnalysis(module, collect_debug_info,
379 keep_only_max_delay_paths,
380 lazy_computation,
381 top_module_name)
382
383 def get_paths(self,
384 value,
385 bit_pos: int,
386 elaborate_paths: bool = True) -> LongestPathCollection:
387 """
388 Perform longest path analysis and return all timing paths to the
389 specified value and bit position.
390 Args:
391 value: The value to analyze
392 bit_pos: The bit position to analyze
393 elaborate_paths: Whether to elaborate the paths with detailed information
394 Returns:
395 LongestPathCollection containing all paths sorted by delay
396 """
398 self.analysis.get_paths(value, bit_pos, elaborate_paths))
399
401 module_name: str,
402 elaborate_paths: bool = True) -> LongestPathCollection:
403 """
404 Get internal paths within the module.
405
406 Internal paths are paths that start and end at sequential elements
407 (registers/flip-flops), forming complete paths through combinational
408 logic. These paths may cross module boundaries but both endpoints are
409 sequential elements, not ports.
410
411 Args:
412 module_name: Name of the module to analyze
413 elaborate_paths: Whether to include full hierarchical instance information
414
415 Returns:
416 LongestPathCollection containing all internal paths sorted by delay
417 """
419 self.analysis.get_internal_paths(module_name, elaborate_paths))
420
422 self, module_name: str) -> LongestPathCollection:
423 """
424 Get external paths from module input ports to internal sequential elements.
425
426 These are input-to-register paths that start at module input ports and end
427 at internal sequential elements (registers/flip-flops).
428
429 Args:
430 module_name: Name of the module to analyze
431
432 Returns:
433 LongestPathCollection containing input-to-internal paths sorted by delay
434 """
437
439 self, module_name: str) -> LongestPathCollection:
440 """
441 Get external paths from internal sequential elements to module output ports.
442
443 These are register-to-output paths that start at internal sequential elements
444 (registers/flip-flops) and end at module output ports.
445
446 Args:
447 module_name: Name of the module to analyze
448
449 Returns:
450 LongestPathCollection containing internal-to-output paths sorted by delay
451 """
454
456 module_name: str,
457 elaborate_paths: bool = True) -> LongestPathCollection:
458 """
459 Get all paths in the module (internal and external paths combined).
460
461 Args:
462 module_name: Name of the module to analyze
463 elaborate_paths: Whether to include full hierarchical instance information
464 (only applies to internal paths)
465
466 Returns:
467 LongestPathCollection containing all paths sorted by delay
468 """
469 collection = self.get_internal_paths(module_name, elaborate_paths)
470 collection.merge(self.get_paths_from_input_ports_to_internal(module_name))
471 collection.merge(self.get_paths_from_internal_to_output_ports(module_name))
472 return collection
473
474
475@dataclass
477 """
478 Represents the history of a timing path, including intermediate debug points.
479 This class provides a Python wrapper around the C++ LongestPathHistory,
480 enabling iteration over the path's history and access to debug points.
481 Attributes:
482 history: The underlying C++ history object
483 """
484 history: _LongestPathHistory
485
486 def __iter__(self):
487 """Iterate over the debug points in the history."""
488 while not self.historyhistory.empty:
489 object, delay, comment = self.historyhistory.head
490 yield DebugPoint(Object(object), delay, comment)
static void print(TypedAttr val, llvm::raw_ostream &os)
List[DebugPoint] history(self)
Definition synth.py:147
Object start_point(self)
Definition synth.py:137
Object end_point(self)
Definition synth.py:142
_LongestPathDataflowPath _path
Definition synth.py:129
str root(self)
Definition synth.py:152
str _build_hierarchy_string(self, Object obj, str prefix="")
Definition synth.py:203
int delay(self)
Definition synth.py:132
str to_flamegraph(self)
Definition synth.py:160
"DebugPoint" from_dict(cls, Dict[str, Any] data)
Definition synth.py:107
str instance_name(self)
Definition synth.py:34
str module_name(self)
Definition synth.py:38
__init__(self, hw.InstanceOp instance)
Definition synth.py:30
LongestPathCollection get_paths_from_internal_to_output_ports(self, str module_name)
Definition synth.py:439
LongestPathCollection get_paths_from_input_ports_to_internal(self, str module_name)
Definition synth.py:422
LongestPathCollection get_paths(self, value, int bit_pos, bool elaborate_paths=True)
Definition synth.py:386
LongestPathCollection get_all_paths(self, str module_name, bool elaborate_paths=True)
Definition synth.py:457
LongestPathCollection get_internal_paths(self, str module_name, bool elaborate_paths=True)
Definition synth.py:402
__init__(self, module, bool collect_debug_info=True, bool keep_only_max_delay_paths=False, bool lazy_computation=False, str top_module_name="")
Definition synth.py:367
merge(self, "LongestPathCollection" src)
Definition synth.py:325
Union[DataflowPath, List[DataflowPath]] __getitem__(self, Union[slice, int] index)
Definition synth.py:264
DataflowPath longest_path(self)
Definition synth.py:294
DataflowPath get_by_delay_ratio(self, float ratio)
Definition synth.py:298
drop_non_critical_paths(self, bool per_end_point=True)
Definition synth.py:335
__init__(self, collection)
Definition synth.py:245
_LongestPathHistory history
Definition synth.py:484
str name(self)
Definition synth.py:79
str __str__(self)
Definition synth.py:59
List[Instance] instance_path(self)
Definition synth.py:72
int bit_pos(self)
Definition synth.py:84
_LongestPathObject _object
Definition synth.py:55
str __repr__(self)
Definition synth.py:68