CIRCT  20.0.0git
hw.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 
5 from __future__ import annotations
6 
7 from . import hw
8 from .. import support
9 from .._mlir_libs._circt._hw import *
10 from ..dialects._ods_common import _cext as _ods_cext
11 from ..ir import *
12 from ._hw_ops_gen import *
13 from ._hw_ops_gen import _Dialect
14 from typing import Dict, Type
15 
16 
17 def create_parameters(parameters: dict[str, Attribute], module: ModuleLike):
18  # Compute mapping from parameter name to index, and initialize array.
19  mod_param_decls = module.parameters
20  mod_param_decls_idxs = {
21  decl.name: idx for (idx, decl) in enumerate(mod_param_decls)
22  }
23  inst_param_array = [None] * len(module.parameters)
24 
25  # Fill in all the parameters specified.
26  if isinstance(parameters, DictAttr):
27  parameters = {i.name: i.attr for i in parameters}
28  for (pname, pval) in parameters.items():
29  if pname not in mod_param_decls_idxs:
30  raise ValueError(
31  f"Could not find parameter '{pname}' in module parameter decls")
32  idx = mod_param_decls_idxs[pname]
33  param_decl = mod_param_decls[idx]
34  inst_param_array[idx] = hw.ParamDeclAttr.get(pname, param_decl.param_type,
35  pval)
36 
37  # Fill in the defaults from the module param decl.
38  for (idx, pval) in enumerate(inst_param_array):
39  if pval is not None:
40  continue
41  inst_param_array[idx] = mod_param_decls[idx]
42 
43  return inst_param_array
44 
45 
46 class InstanceBuilder(support.NamedValueOpView):
47  """Helper class to incrementally construct an instance of a module."""
48 
49  def __init__(self,
50  module,
51  name,
52  input_port_mapping,
53  *,
54  results=None,
55  parameters={},
56  sym_name=None,
57  loc=None,
58  ip=None):
59  self.modulemodule = module
60  instance_name = StringAttr.get(name)
61  module_name = FlatSymbolRefAttr.get(StringAttr(module.name).value)
62  inst_param_array = create_parameters(parameters, module)
63  if sym_name:
64  inner_sym = hw.InnerSymAttr.get(StringAttr.get(sym_name))
65  else:
66  inner_sym = None
67  pre_args = [instance_name, module_name]
68  post_args = [
69  ArrayAttr.get([StringAttr.get(x) for x in self.operand_namesoperand_names()]),
70  ArrayAttr.get([StringAttr.get(x) for x in self.result_namesresult_names()]),
71  ArrayAttr.get(inst_param_array)
72  ]
73  if results is None:
74  results = module.type.output_types
75 
76  if not isinstance(module, hw.HWModuleExternOp):
77  input_name_type_lookup = {
78  name: support.type_to_pytype(ty)
79  for name, ty in zip(self.operand_namesoperand_names(), module.type.input_types)
80  }
81  for input_name, input_value in input_port_mapping.items():
82  if input_name not in input_name_type_lookup:
83  continue # This error gets caught and raised later.
84  mod_input_type = input_name_type_lookup[input_name]
85  if support.type_to_pytype(input_value.type) != mod_input_type:
86  raise TypeError(f"Input '{input_name}' has type '{input_value.type}' "
87  f"but expected '{mod_input_type}'")
88 
89  super().__init__(hw.InstanceOp,
90  results,
91  input_port_mapping,
92  pre_args,
93  post_args,
94  needs_result_type=True,
95  inner_sym=inner_sym,
96  loc=loc,
97  ip=ip)
98 
99  def create_default_value(self, index, data_type, arg_name):
100  type = self.modulemodule.type.input_types[index]
101  return support.BackedgeBuilder.create(type,
102  arg_name,
103  self,
104  instance_of=self.modulemodule)
105 
106  def operand_names(self):
107  return self.modulemodule.type.input_names
108 
109  def result_names(self):
110  return self.modulemodule.type.output_names
111 
112 
114  """Custom Python helper class for module-like operations."""
115 
116  @staticmethod
117  def init(
118  op,
119  name,
120  input_ports=[],
121  output_ports=[],
122  *,
123  parameters=[],
124  attributes={},
125  body_builder=None,
126  loc=None,
127  ip=None,
128  ):
129  """
130  Create a module-like with the provided `name`, `input_ports`, and
131  `output_ports`.
132  - `name` is a string representing the module name.
133  - `input_ports` is a list of pairs of string names and mlir.ir types.
134  - `output_ports` is a list of pairs of string names and mlir.ir types.
135  - `body_builder` is an optional callback, when provided a new entry block
136  is created and the callback is invoked with the new op as argument within
137  an InsertionPoint context already set for the block. The callback is
138  expected to insert a terminator in the block.
139  """
140  # Copy the mutable default arguments. 'Cause python.
141  input_ports = list(input_ports)
142  output_ports = list(output_ports)
143  parameters = list(parameters)
144  attributes = dict(attributes)
145 
146  operands = []
147  results = []
148  attributes["sym_name"] = StringAttr.get(str(name))
149 
150  module_ports = []
151  input_names = []
152  unknownLoc = Location.unknown().attr
153  for (i, (port_name, port_type)) in enumerate(input_ports):
154  input_name = StringAttr.get(str(port_name))
155  input_dir = hw.ModulePortDirection.INPUT
156  input_port = hw.ModulePort(input_name, port_type, input_dir)
157  module_ports.append(input_port)
158  input_names.append(input_name)
159 
160  output_types = []
161  output_names = []
162  for (i, (port_name, port_type)) in enumerate(output_ports):
163  output_name = StringAttr.get(str(port_name))
164  output_dir = hw.ModulePortDirection.OUTPUT
165  output_port = hw.ModulePort(output_name, port_type, output_dir)
166  module_ports.append(output_port)
167  output_names.append(output_name)
168  attributes["per_port_attrs"] = ArrayAttr.get([])
169 
170  if len(parameters) > 0 or "parameters" not in attributes:
171  attributes["parameters"] = ArrayAttr.get(parameters)
172 
173  attributes["module_type"] = TypeAttr.get(hw.ModuleType.get(module_ports))
174 
175  _ods_cext.ir.OpView.__init__(
176  op,
177  op.build_generic(attributes=attributes,
178  results=results,
179  operands=operands,
180  loc=loc,
181  ip=ip))
182 
183  if body_builder:
184  entry_block = op.add_entry_block()
185 
186  with InsertionPoint(entry_block):
187  with support.BackedgeBuilder(str(name)):
188  outputs = body_builder(op)
189  _create_output_op(name, output_ports, entry_block, outputs)
190 
191  @staticmethod
192  def type(op):
193  return hw.ModuleType(TypeAttr(op.attributes["module_type"]).value)
194 
195  @staticmethod
196  def name(op):
197  return op.attributes["sym_name"]
198 
199  @staticmethod
200  def is_external(op):
201  return len(op.regions[0].blocks) == 0
202 
203  @staticmethod
204  def parameters(op) -> list[ParamDeclAttr]:
205  return [hw.ParamDeclAttr(a) for a in ArrayAttr(op.attributes["parameters"])]
206 
207  @staticmethod
208  def instantiate(op,
209  name: str,
210  parameters: Dict[str, object] = {},
211  results=None,
212  sym_name=None,
213  loc=None,
214  ip=None,
215  **kwargs):
216  return InstanceBuilder(op,
217  name,
218  kwargs,
219  parameters=parameters,
220  results=results,
221  sym_name=sym_name,
222  loc=loc,
223  ip=ip)
224 
225 
226 def _create_output_op(cls_name, output_ports, entry_block, bb_ret):
227  """Create the hw.OutputOp from the body_builder return."""
228 
229  # Determine if the body already has an output op.
230  block_len = len(entry_block.operations)
231  if block_len > 0:
232  last_op = entry_block.operations[block_len - 1]
233  if isinstance(last_op, hw.OutputOp):
234  # If it does, the return from body_builder must be None.
235  if bb_ret is not None and bb_ret != last_op:
236  raise support.ConnectionError(
237  f"In {cls_name}, cannot return value from body_builder and "
238  "create hw.OutputOp")
239  return
240 
241  # If builder didn't create an output op and didn't return anything, this op
242  # mustn't have any outputs.
243  if bb_ret is None:
244  if len(output_ports) == 0:
245  hw.OutputOp([])
246  return
247  raise support.ConnectionError(
248  f"In {cls_name}, must return module output values")
249 
250  # Now create the output op depending on the object type returned
251  outputs: list[Value] = list()
252 
253  # Only acceptable return is a dict of port, value mappings.
254  if not isinstance(bb_ret, dict):
255  raise support.ConnectionError(
256  f"In {cls_name}, can only return a dict of port, value mappings "
257  "from body_builder.")
258 
259  # A dict of `OutputPortName` -> ValueLike must be converted to a list in port
260  # order.
261  unconnected_ports = []
262  for (name, port_type) in output_ports:
263  if name not in bb_ret:
264  unconnected_ports.append(name)
265  outputs.append(None)
266  else:
267  val = support.get_value(bb_ret[name])
268  if val is None:
269  field_type = type(bb_ret[name])
270  raise TypeError(
271  f"In {cls_name}, body_builder return doesn't support type "
272  f"'{field_type}'")
273  if val.type != port_type:
274  if isinstance(port_type, hw.TypeAliasType) and \
275  port_type.inner_type == val.type:
276  val = hw.BitcastOp.create(port_type, val).result
277  else:
278  raise TypeError(
279  f"In {cls_name}, output port '{name}' type ({val.type}) doesn't "
280  f"match declared type ({port_type})")
281  outputs.append(val)
282  bb_ret.pop(name)
283  if len(unconnected_ports) > 0:
284  raise support.UnconnectedSignalError(cls_name, unconnected_ports)
285  if len(bb_ret) > 0:
286  raise support.ConnectionError(
287  f"Could not map the following to output ports in {cls_name}: " +
288  ",".join(bb_ret.keys()))
289 
290  hw.OutputOp(outputs)
291 
292 
293 @_ods_cext.register_operation(_Dialect, replace=True)
295  """Specialization for the HW module op class."""
296 
297  def __init__(
298  self,
299  name,
300  input_ports=[],
301  output_ports=[],
302  *,
303  parameters=[],
304  attributes={},
305  body_builder=None,
306  loc=None,
307  ip=None,
308  ):
309  if "comment" not in attributes:
310  attributes["comment"] = StringAttr.get("")
311  ModuleLike.init(self,
312  name,
313  input_ports,
314  output_ports,
315  parameters=parameters,
316  attributes=attributes,
317  body_builder=body_builder,
318  loc=loc,
319  ip=ip)
320 
321  def instantiate(self, *args, **kwargs):
322  return ModuleLike.instantiate(self, *args, **kwargs)
323 
324  @property
325  def type(self):
326  return ModuleLike.type(self)
327 
328  @property
329  def name(self):
330  return ModuleLike.name(self)
331 
332  @property
333  def is_external(self):
334  return ModuleLike.is_external(self)
335 
336  @property
337  def parameters(self) -> list[ParamDeclAttr]:
338  return ModuleLike.parameters(self)
339 
340  @property
341  def body(self):
342  return self.regions[0]
343 
344  @property
345  def entry_block(self):
346  return self.regions[0].blocks[0]
347 
348  @property
349  def input_indices(self):
350  indices: dict[int, str] = {}
351  op_names = self.typetype.input_names
352  for idx, name in enumerate(op_names):
353  indices[name] = idx
354  return indices
355 
356  # Support attribute access to block arguments by name
357  def __getattr__(self, name):
358  if name in self.input_indicesinput_indices:
359  index = self.input_indicesinput_indices[name]
360  return self.entry_blockentry_block.arguments[index]
361  raise AttributeError(f"unknown input port name {name}")
362 
363  def inputs(self) -> dict[str:Value]:
364  ret = {}
365  for (name, idx) in self.input_indicesinput_indices.items():
366  ret[name] = self.entry_blockentry_block.arguments[idx]
367  return ret
368 
369  def outputs(self) -> dict[str:Type]:
370  result_names = self.typetype.output_names
371  result_types = self.typetype.output_types
372  return dict(zip(result_names, result_types))
373 
374  def add_entry_block(self):
375  if not self.is_externalis_external:
376  raise IndexError('The module already has an entry block')
377  self.bodybody.blocks.append(*self.typetype.input_types)
378  return self.bodybody.blocks[0]
379 
380 
381 @_ods_cext.register_operation(_Dialect, replace=True)
383  """Specialization for the HW module op class."""
384 
385  def __init__(
386  self,
387  name,
388  input_ports=[],
389  output_ports=[],
390  *,
391  parameters=[],
392  attributes={},
393  body_builder=None,
394  loc=None,
395  ip=None,
396  ):
397  if "comment" not in attributes:
398  attributes["comment"] = StringAttr.get("")
399  ModuleLike.init(self,
400  name,
401  input_ports,
402  output_ports,
403  parameters=parameters,
404  attributes=attributes,
405  body_builder=body_builder,
406  loc=loc,
407  ip=ip)
408 
409  def instantiate(self, *args, **kwargs):
410  return ModuleLike.instantiate(self, *args, **kwargs)
411 
412  @property
413  def type(self):
414  return ModuleLike.type(self)
415 
416  @property
417  def name(self):
418  return ModuleLike.name(self)
419 
420  @property
421  def is_external(self):
422  return ModuleLike.is_external(self)
423 
424  @property
425  def parameters(self) -> list[ParamDeclAttr]:
426  return ModuleLike.parameters(self)
427 
428 
429 @_ods_cext.register_operation(_Dialect, replace=True)
431 
432  @staticmethod
433  def create(data_type, value):
434  return hw.ConstantOp(IntegerAttr.get(data_type, value))
435 
436 
437 @_ods_cext.register_operation(_Dialect, replace=True)
439 
440  @staticmethod
441  def create(data_type, value):
442  value = support.get_value(value)
443  return hw.BitcastOp(data_type, value)
444 
445 
446 @_ods_cext.register_operation(_Dialect, replace=True)
448 
449  @staticmethod
450  def create(array_value, idx):
451  array_value = support.get_value(array_value)
452  array_type = support.get_self_or_inner(array_value.type)
453  if isinstance(idx, int):
454  idx_width = (array_type.size - 1).bit_length()
455  idx_val = ConstantOp.create(IntegerType.get_signless(idx_width),
456  idx).result
457  else:
458  idx_val = support.get_value(idx)
459  return hw.ArrayGetOp(array_value, idx_val)
460 
461 
462 @_ods_cext.register_operation(_Dialect, replace=True)
464 
465  @staticmethod
466  def create(array_value, low_index, ret_type):
467  array_value = support.get_value(array_value)
468  array_type = support.get_self_or_inner(array_value.type)
469  if isinstance(low_index, int):
470  idx_width = (array_type.size - 1).bit_length()
471  idx_width = max(1, idx_width) # hw.constant cannot produce i0.
472  idx_val = ConstantOp.create(IntegerType.get_signless(idx_width),
473  low_index).result
474  else:
475  idx_val = support.get_value(low_index)
476  return hw.ArraySliceOp(ret_type, array_value, idx_val)
477 
478 
479 @_ods_cext.register_operation(_Dialect, replace=True)
481 
482  @staticmethod
483  def create(elements):
484  if not elements:
485  raise ValueError("Cannot 'create' an array of length zero")
486  vals = []
487  type = None
488  for i, arg in enumerate(elements):
489  arg_val = support.get_value(arg)
490  vals.append(arg_val)
491  if type is None:
492  type = arg_val.type
493  elif type != arg_val.type:
494  raise TypeError(
495  f"Argument {i} has a different element type ({arg_val.type}) than the element type of the array ({type})"
496  )
497  return hw.ArrayCreateOp(hw.ArrayType.get(type, len(vals)), vals)
498 
499 
500 @_ods_cext.register_operation(_Dialect, replace=True)
502 
503  @staticmethod
504  def create(*sub_arrays):
505  vals = []
506  types = []
507  element_type = None
508  for i, array in enumerate(sub_arrays):
509  array_value = support.get_value(array)
510  array_type = support.type_to_pytype(array_value.type)
511  if array_value is None or not isinstance(array_type, hw.ArrayType):
512  raise TypeError(f"Cannot concatenate {array_value}")
513  if element_type is None:
514  element_type = array_type.element_type
515  elif element_type != array_type.element_type:
516  raise TypeError(
517  f"Argument {i} has a different element type ({element_type}) than the element type of the array ({array_type.element_type})"
518  )
519 
520  vals.append(array_value)
521  types.append(array_type)
522 
523  size = sum(t.size for t in types)
524  combined_type = hw.ArrayType.get(element_type, size)
525  return hw.ArrayConcatOp(combined_type, vals)
526 
527 
528 @_ods_cext.register_operation(_Dialect, replace=True)
530 
531  @staticmethod
532  def create(elements, result_type: Type = None):
533  elem_name_values = [
534  (name, support.get_value(value)) for (name, value) in elements
535  ]
536  struct_fields = [(name, value.type) for (name, value) in elem_name_values]
537  struct_type = hw.StructType.get(struct_fields)
538 
539  if result_type is None:
540  result_type = struct_type
541  else:
542  result_type_inner = support.get_self_or_inner(result_type)
543  if result_type_inner != struct_type:
544  raise TypeError(
545  f"result type:\n\t{result_type_inner}\nmust match generated struct type:\n\t{struct_type}"
546  )
547 
548  return hw.StructCreateOp(result_type,
549  [value for (_, value) in elem_name_values])
550 
551 
552 @_ods_cext.register_operation(_Dialect, replace=True)
554 
555  @staticmethod
556  def create(struct_value, field_name: str):
557  struct_value = support.get_value(struct_value)
558  struct_type = support.get_self_or_inner(struct_value.type)
559  field_type = struct_type.get_field(field_name)
560  field_index = struct_type.get_field_index(field_name)
561  if field_index == UnitAttr.get():
562  raise TypeError(
563  f"field '{field_name}' not element of struct type {struct_type}")
564  return hw.StructExtractOp(field_type, struct_value, field_index)
565 
566 
567 @_ods_cext.register_operation(_Dialect, replace=True)
569 
570  @staticmethod
571  def create(sym_name: str, type: Type, verilog_name: str = None):
572  return hw.TypedeclOp(StringAttr.get(sym_name),
573  TypeAttr.get(type),
574  verilogName=verilog_name)
575 
576 
577 @_ods_cext.register_operation(_Dialect, replace=True)
579 
580  @staticmethod
581  def create(sym_name: str):
582  op = hw.TypeScopeOp(StringAttr.get(sym_name))
583  op.regions[0].blocks.append()
584  return op
585 
586  @property
587  def body(self):
588  return self.regions[0].blocks[0]
def create(*sub_arrays)
Definition: hw.py:504
def create(elements)
Definition: hw.py:483
def create(array_value, idx)
Definition: hw.py:450
def create(array_value, low_index, ret_type)
Definition: hw.py:466
def create(data_type, value)
Definition: hw.py:441
def create(data_type, value)
Definition: hw.py:433
def is_external(self)
Definition: hw.py:421
def type(self)
Definition: hw.py:413
def name(self)
Definition: hw.py:417
def __init__(self, name, input_ports=[], output_ports=[], *parameters=[], attributes={}, body_builder=None, loc=None, ip=None)
Definition: hw.py:396
list[ParamDeclAttr] parameters(self)
Definition: hw.py:425
def instantiate(self, *args, **kwargs)
Definition: hw.py:409
def body(self)
Definition: hw.py:341
def is_external(self)
Definition: hw.py:333
def add_entry_block(self)
Definition: hw.py:374
def type(self)
Definition: hw.py:325
dict[str:Value] inputs(self)
Definition: hw.py:363
def entry_block(self)
Definition: hw.py:345
list[ParamDeclAttr] parameters(self)
Definition: hw.py:337
dict[str:Type] outputs(self)
Definition: hw.py:369
def __init__(self, name, input_ports=[], output_ports=[], *parameters=[], attributes={}, body_builder=None, loc=None, ip=None)
Definition: hw.py:308
def input_indices(self)
Definition: hw.py:349
def name(self)
Definition: hw.py:329
def __getattr__(self, name)
Definition: hw.py:357
def instantiate(self, *args, **kwargs)
Definition: hw.py:321
def create_default_value(self, index, data_type, arg_name)
Definition: hw.py:99
def __init__(self, module, name, input_port_mapping, *results=None, parameters={}, sym_name=None, loc=None, ip=None)
Definition: hw.py:58
def operand_names(self)
Definition: hw.py:106
def result_names(self)
Definition: hw.py:109
def instantiate(op, str name, Dict[str, object] parameters={}, results=None, sym_name=None, loc=None, ip=None, **kwargs)
Definition: hw.py:215
def is_external(op)
Definition: hw.py:200
def name(op)
Definition: hw.py:196
def type(op)
Definition: hw.py:192
list[ParamDeclAttr] parameters(op)
Definition: hw.py:204
def init(op, name, input_ports=[], output_ports=[], *parameters=[], attributes={}, body_builder=None, loc=None, ip=None)
Definition: hw.py:128
def create(elements, Type result_type=None)
Definition: hw.py:532
def create(struct_value, str field_name)
Definition: hw.py:556
def create(str sym_name)
Definition: hw.py:581
def body(self)
Definition: hw.py:587
def create(str sym_name, Type type, str verilog_name=None)
Definition: hw.py:571
def _create_output_op(cls_name, output_ports, entry_block, bb_ret)
Definition: hw.py:226
def create_parameters(dict[str, Attribute] parameters, ModuleLike module)
Definition: hw.py:17