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