CIRCT 24.0.0git
Loading...
Searching...
No Matches
support.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 ir
6
7from ._mlir_libs._circt._support import _walk_with_filter
8from .ir import Operation
9from contextlib import AbstractContextManager
10from contextvars import ContextVar
11from typing import List
12
13_current_backedge_builder = ContextVar("current_bb")
14
15
17 pass
18
19
21
22 def __init__(self, module: str, port_names: List[str]):
23 super().__init__(
24 f"Ports {port_names} unconnected in design module {module}.")
25
26
27def get_value(obj) -> ir.Value:
28 """Resolve a Value from a few supported types."""
29
30 if isinstance(obj, ir.Value):
31 return obj
32 if hasattr(obj, "result"):
33 return obj.result
34 if hasattr(obj, "value"):
35 return obj.value
36 return None
37
38
39def connect(destination, source):
40 """A convenient way to use BackedgeBuilder."""
41 if not isinstance(destination, OpOperand):
42 raise TypeError(
43 f"cannot connect to destination of type {type(destination)}. "
44 "Must be OpOperand.")
45 value = get_value(source)
46 if value is None:
47 raise TypeError(f"cannot connect from source of type {type(source)}")
48
49 index = destination.index
50 destination.operation.operands[index] = value
51 if destination.backedge_owner and \
52 index in destination.backedge_owner.backedges:
53 destination.backedge_owner.backedges[index].erase()
54 del destination.backedge_owner.backedges[index]
55
56
57def var_to_attribute(obj, none_on_fail: bool = False) -> ir.Attribute:
58 """Create an MLIR attribute from a Python object for a few common cases."""
59 if isinstance(obj, ir.Attribute):
60 return obj
61 if isinstance(obj, bool):
62 return ir.BoolAttr.get(obj)
63 if isinstance(obj, int):
64 attrTy = ir.IntegerType.get_signless(64)
65 return ir.IntegerAttr.get(attrTy, obj)
66 if isinstance(obj, str):
67 return ir.StringAttr.get(obj)
68 if isinstance(obj, list):
69 arr = [var_to_attribute(x, none_on_fail) for x in obj]
70 if all(arr):
71 return ir.ArrayAttr.get(arr)
72 return None
73 if none_on_fail:
74 return None
75 raise TypeError(f"Cannot convert type '{type(obj)}' to MLIR attribute")
76
77
78# There is currently no support in MLIR for querying type types. The
79# conversation regarding how to achieve this is ongoing and I expect it to be a
80# long one. This is a way that works for now.
81def type_to_pytype(t) -> ir.Type:
82
83 if not isinstance(t, ir.Type):
84 raise TypeError("type_to_pytype only accepts MLIR Type objects")
85
86 # If it's not the root type, assume it's already been downcasted and don't do
87 # the expensive probing below.
88 if t.__class__ != ir.Type:
89 return t
90
91 from .dialects import esi, hw, seq, rtg, rtgtest, sv
92 if isinstance(t, ir.IntegerType):
93 return ir.IntegerType(t)
94 if isinstance(t, ir.NoneType):
95 return ir.NoneType(t)
96 if isinstance(t, ir.TupleType):
97 return ir.TupleType(t)
98 if hw.ArrayType.isinstance(t):
99 return hw.ArrayType(t)
100 if hw.StructType.isinstance(t):
101 return hw.StructType(t)
102 if hw.UnionType.isinstance(t):
103 return hw.UnionType(t)
104 if hw.TypeAliasType.isinstance(t):
105 return hw.TypeAliasType(t)
106 if hw.InOutType.isinstance(t):
107 return hw.InOutType(t)
108 if sv.NetType.isinstance(t):
109 return sv.NetType(t)
110 if sv.VarType.isinstance(t):
111 return sv.VarType(t)
112 if seq.ClockType.isinstance(t):
113 return seq.ClockType(t)
114 if esi.ChannelType.isinstance(t):
115 return esi.ChannelType(t)
116 if esi.AnyType.isinstance(t):
117 return esi.AnyType(t)
118 if esi.BundleType.isinstance(t):
119 return esi.BundleType(t)
120 if esi.ListType.isinstance(t):
121 return esi.ListType(t)
122 if esi.WindowType.isinstance(t):
123 return esi.WindowType(t)
124 if esi.WindowFrameType.isinstance(t):
125 return esi.WindowFrameType(t)
126 if esi.WindowFieldType.isinstance(t):
127 return esi.WindowFieldType(t)
128 if rtg.LabelType.isinstance(t):
129 return rtg.LabelType(t)
130 if rtg.SetType.isinstance(t):
131 return rtg.SetType(t)
132 if rtg.BagType.isinstance(t):
133 return rtg.BagType(t)
134 if rtg.SequenceType.isinstance(t):
135 return rtg.SequenceType(t)
136 if rtg.RandomizedSequenceType.isinstance(t):
137 return rtg.RandomizedSequenceType(t)
138 if rtg.DictType.isinstance(t):
139 return rtg.DictType(t)
140 if rtg.ArrayType.isinstance(t):
141 return rtg.ArrayType(t)
142 if rtg.MemoryType.isinstance(t):
143 return rtg.MemoryType(t)
144 if rtg.MemoryBlockType.isinstance(t):
145 return rtg.MemoryBlockType(t)
146 if rtg.TupleType.isinstance(t):
147 return rtg.TupleType(t)
148 if rtg.StringType.isinstance(t):
149 return rtg.StringType(t)
150 if rtgtest.IntegerRegisterType.isinstance(t):
151 return rtgtest.IntegerRegisterType(t)
152 if rtgtest.FloatRegisterType.isinstance(t):
153 return rtgtest.FloatRegisterType(t)
154 if rtgtest.CPUType.isinstance(t):
155 return rtgtest.CPUType(t)
156
157 raise TypeError(f"Cannot convert {repr(t)} to python type")
158
159
160# There is currently no support in MLIR for querying attribute types. The
161# conversation regarding how to achieve this is ongoing and I expect it to be a
162# long one. This is a way that works for now.
164
165 if attr is None:
166 return None
167 if not isinstance(attr, ir.Attribute):
168 raise TypeError("attribute_to_var only accepts MLIR Attributes")
169
170 # If it's not the root type, assume it's already been downcasted and don't do
171 # the expensive probing below.
172 if attr.__class__ != ir.Attribute and hasattr(attr, "value"):
173 return attr.value
174
175 from .dialects import hw, om
176 if isinstance(attr, ir.BoolAttr):
177 return ir.BoolAttr(attr).value
178 if isinstance(attr, ir.IntegerAttr):
179 return ir.IntegerAttr(attr).value
180 if hw.InnerSymAttr.isinstance(attr):
181 return ir.StringAttr(hw.InnerSymAttr(attr).symName).value
182 if isinstance(attr, ir.StringAttr):
183 return ir.StringAttr(attr).value
184 if isinstance(attr, ir.FlatSymbolRefAttr):
185 return ir.FlatSymbolRefAttr(attr).value
186 if isinstance(attr, ir.TypeAttr):
187 return ir.TypeAttr(attr).value
188 if isinstance(attr, ir.ArrayAttr):
189 arr = ir.ArrayAttr(attr)
190 return [attribute_to_var(x) for x in arr]
191 if isinstance(attr, ir.DictAttr):
192 dict = ir.DictAttr(attr)
193 return {i.name: attribute_to_var(i.attr) for i in dict}
194 if om.ReferenceAttr.isinstance(attr):
195 return attribute_to_var(om.ReferenceAttr(attr).inner_ref)
196 if hw.InnerRefAttr.isinstance(attr):
197 ref = hw.InnerRefAttr(attr)
198 return (ir.StringAttr(ref.module).value, ir.StringAttr(ref.name).value)
199 if om.ListAttr.isinstance(attr):
200 return list(map(attribute_to_var, om.ListAttr(attr)))
201 if om.OMIntegerAttr.isinstance(attr):
202 return int(str(om.OMIntegerAttr(attr)))
203 if om.PathAttr.isinstance(attr):
204 return om.PathAttr(attr).value
205
206 raise TypeError(f"Cannot convert {repr(attr)} to python value")
207
208
209def get_canonical_type(mlir_type):
210 """Return `mlir_type` with all type aliases stripped."""
211 from .dialects import hw
212 if hw.TypeAliasType.isinstance(mlir_type):
213 return hw.TypeAliasType(mlir_type).canonical_type
214 return mlir_type
215
216
217def get_self_or_inner(mlir_type):
218 from .dialects import hw
219 if type(mlir_type) is ir.Type:
220 mlir_type = type_to_pytype(mlir_type)
221 if isinstance(mlir_type, hw.TypeAliasType):
222 return type_to_pytype(mlir_type.inner_type)
223 return mlir_type
224
225
227
228 class Edge:
229
230 def __init__(self,
231 creator,
232 type: ir.Type,
233 backedge_name: str,
234 op_view,
235 instance_of: ir.Operation,
236 loc: ir.Location = None):
237 self.creator: BackedgeBuilder = creator
238 self.dummy_op = ir.Operation.create("builtin.unrealized_conversion_cast",
239 [type],
240 loc=loc)
241 self.instance_of = instance_of
242 self.op_view = op_view
243 self.port_name = backedge_name
244 self.loc = loc
245 self.erased = False
246
247 @property
248 def result(self):
249 return self.dummy_op.result
250
251 def erase(self):
252 if self.erased:
253 return
254 if self in self.creator.edges:
255 self.creator.edges.remove(self)
256 self.dummy_op.operation.erase()
257
258 def __init__(self, circuit_name: str = ""):
259 self.circuit_name = circuit_name
260 self.edges = set()
261
262 @staticmethod
263 def current():
264 bb = _current_backedge_builder.get(None)
265 if bb is None:
266 raise RuntimeError("No backedge builder found in context!")
267 return bb
268
269 @staticmethod
270 def create(*args, **kwargs):
271 return BackedgeBuilder.current()._create(*args, **kwargs)
272
273 def _create(self,
274 type: ir.Type,
275 port_name: str,
276 op_view,
277 instance_of: ir.Operation = None,
278 loc: ir.Location = None):
279 edge = BackedgeBuilder.Edge(self, type, port_name, op_view, instance_of,
280 loc)
281 self.edges.add(edge)
282 return edge
283
284 def __enter__(self):
285 self.old_bb_token = _current_backedge_builder.set(self)
286
287 def __exit__(self, exc_type, exc_value, traceback):
288 if exc_value is not None:
289 return
290 _current_backedge_builder.reset(self.old_bb_token)
291 errors = []
292 for edge in list(self.edges):
293 # TODO: Make this use `UnconnectedSignalError`.
294 msg = "Backedge: " + edge.port_name + "\n"
295 if edge.instance_of is not None:
296 msg += "InstanceOf: " + str(edge.instance_of).split(" {")[0] + "\n"
297 if edge.op_view is not None:
298 op = edge.op_view.operation
299 msg += "Instance: " + str(op)
300 if edge.loc is not None:
301 msg += "Location: " + str(edge.loc)
302 errors.append(msg)
303
304 if errors:
305 errors.insert(
306 0, f"Uninitialized backedges remain in module '{self.circuit_name}'")
307 raise RuntimeError("\n".join(errors))
308
309
311 __slots__ = ["index", "operation", "value", "backedge_owner"]
312
313 def __init__(self,
314 operation: ir.Operation,
315 index: int,
316 value,
317 backedge_owner=None):
318 if not isinstance(index, int):
319 raise TypeError("Index must be int")
320 self.index = index
321
322 if not hasattr(operation, "operands"):
323 raise TypeError("Operation must be have 'operands' attribute")
324 self.operation = operation
325
326 self.value = value
327 self.backedge_owner = backedge_owner
328
329 @property
330 def type(self):
331 return self.value.type
332
333
335 """Helper class to incrementally construct an instance of an operation that
336 names its operands and results"""
337
338 def __init__(self,
339 cls,
340 data_type=None,
341 input_port_mapping=None,
342 pre_args=None,
343 post_args=None,
344 needs_result_type=False,
345 **kwargs):
346 # Set defaults
347 if input_port_mapping is None:
348 input_port_mapping = {}
349 if pre_args is None:
350 pre_args = []
351 if post_args is None:
352 post_args = []
353
354 # Set result_indices to name each result.
355 result_names = self.result_names()
356 result_indices = {}
357 for i in range(len(result_names)):
358 result_indices[result_names[i]] = i
359
360 # Set operand_indices to name each operand. Give them an initial value,
361 # either from input_port_mapping or a default value.
362 backedges = {}
363 operand_indices = {}
364 operand_values = []
365 operand_names = self.operand_names()
366 for i in range(len(operand_names)):
367 arg_name = operand_names[i]
368 operand_indices[arg_name] = i
369 if arg_name in input_port_mapping:
370 value = get_value(input_port_mapping[arg_name])
371 operand = value
372 else:
373 backedge = self.create_default_value(i, data_type, arg_name)
374 backedges[i] = backedge
375 operand = backedge.result
376 operand_values.append(operand)
377
378 # Some ops take a list of operand values rather than splatting them out.
379 if isinstance(data_type, list):
380 operand_values = [operand_values]
381
382 # In many cases, result types are inferred, and we do not need to pass
383 # data_type to the underlying constructor. It must be provided to
384 # NamedValueOpView in cases where we need to build backedges, but should
385 # generally not be passed to the underlying constructor in this case. There
386 # are some oddball ops that must pass it, even when building backedges, and
387 # these set needs_result_type=True.
388 if data_type is not None and (needs_result_type or len(backedges) == 0):
389 pre_args.insert(0, data_type)
390
391 self.opview = cls(*pre_args, *operand_values, *post_args, **kwargs)
392 self.operand_indices = operand_indices
393 self.result_indices = result_indices
394 self.backedges = backedges
395
396 def __getattr__(self, name):
397 # Check for the attribute in the arg name set.
398 if "operand_indices" in dir(self) and name in self.operand_indices:
399 index = self.operand_indices[name]
400 value = self.opview.operands[index]
401 return OpOperand(self.opview.operation, index, value, self)
402
403 # Check for the attribute in the result name set.
404 if "result_indices" in dir(self) and name in self.result_indices:
405 index = self.result_indices[name]
406 value = self.opview.results[index]
407 return OpOperand(self.opview.operation, index, value, self)
408
409 # Forward "attributes" attribute from the operation.
410 if name == "attributes":
411 return self.opview.operation.attributes
412
413 # If we fell through to here, the name isn't a result.
414 raise AttributeError(f"unknown port name {name}")
415
416 def create_default_value(self, index, data_type, arg_name):
417 return BackedgeBuilder.create(data_type, arg_name, self)
418
419 @property
420 def operation(self):
421 """Get the operation associated with this builder."""
422 return self.opview.operation
423
424
425# Helper function to walk operation with a filter on operation names.
426# `op_views` is a list of operation views to visit. This is a wrapper
427# around the C++ implementation of walk_with_filter.
428def walk_with_filter(operation: Operation, op_views: List[ir.OpView], callback,
429 walk_order):
430 op_names_identifiers = [name.OPERATION_NAME for name in op_views]
431 return _walk_with_filter(operation, op_names_identifiers, callback,
432 walk_order)
__init__(self, creator, ir.Type type, str backedge_name, op_view, ir.Operation instance_of, ir.Location loc=None)
Definition support.py:236
__init__(self, str circuit_name="")
Definition support.py:258
create(*args, **kwargs)
Definition support.py:270
_create(self, ir.Type type, str port_name, op_view, ir.Operation instance_of=None, ir.Location loc=None)
Definition support.py:278
__exit__(self, exc_type, exc_value, traceback)
Definition support.py:287
__init__(self, cls, data_type=None, input_port_mapping=None, pre_args=None, post_args=None, needs_result_type=False, **kwargs)
Definition support.py:345
create_default_value(self, index, data_type, arg_name)
Definition support.py:416
__init__(self, ir.Operation operation, int index, value, backedge_owner=None)
Definition support.py:317
__init__(self, str module, List[str] port_names)
Definition support.py:22
The "any" type is a special type which can be used to represent any type, as identified by the type i...
Definition Types.h:159
Bundles represent a collection of channels.
Definition Types.h:104
Channels are the basic communication primitives.
Definition Types.h:125
Lists represent variable-length sequences of elements of a single type.
Definition Types.h:405
Windows represent a fixed-size sliding window over a stream of data.
Definition Types.h:334
ir.Attribute var_to_attribute(obj, bool none_on_fail=False)
Definition support.py:57
get_self_or_inner(mlir_type)
Definition support.py:217
attribute_to_var(attr)
Definition support.py:163
walk_with_filter(Operation operation, List[ir.OpView] op_views, callback, walk_order)
Definition support.py:429
ir.Type type_to_pytype(t)
Definition support.py:81
get_canonical_type(mlir_type)
Definition support.py:209
ir.Value get_value(obj)
Definition support.py:27
connect(destination, source)
Definition support.py:39