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 base class for module-like operations."""
115 
116  def __init__(
117  self,
118  name,
119  input_ports=[],
120  output_ports=[],
121  *,
122  parameters=[],
123  attributes={},
124  body_builder=None,
125  loc=None,
126  ip=None,
127  ):
128  """
129  Create a module-like with the provided `name`, `input_ports`, and
130  `output_ports`.
131  - `name` is a string representing the module name.
132  - `input_ports` is a list of pairs of string names and mlir.ir types.
133  - `output_ports` is a list of pairs of string names and mlir.ir types.
134  - `body_builder` is an optional callback, when provided a new entry block
135  is created and the callback is invoked with the new op as argument within
136  an InsertionPoint context already set for the block. The callback is
137  expected to insert a terminator in the block.
138  """
139  # Copy the mutable default arguments. 'Cause python.
140  input_ports = list(input_ports)
141  output_ports = list(output_ports)
142  parameters = list(parameters)
143  attributes = dict(attributes)
144 
145  operands = []
146  results = []
147  attributes["sym_name"] = StringAttr.get(str(name))
148 
149  module_ports = []
150  input_names = []
151  unknownLoc = Location.unknown().attr
152  for (i, (port_name, port_type)) in enumerate(input_ports):
153  input_name = StringAttr.get(str(port_name))
154  input_dir = hw.ModulePortDirection.INPUT
155  input_port = hw.ModulePort(input_name, port_type, input_dir)
156  module_ports.append(input_port)
157  input_names.append(input_name)
158 
159  output_types = []
160  output_names = []
161  for (i, (port_name, port_type)) in enumerate(output_ports):
162  output_name = StringAttr.get(str(port_name))
163  output_dir = hw.ModulePortDirection.OUTPUT
164  output_port = hw.ModulePort(output_name, port_type, output_dir)
165  module_ports.append(output_port)
166  output_names.append(output_name)
167  attributes["per_port_attrs"] = ArrayAttr.get([])
168 
169  if len(parameters) > 0 or "parameters" not in attributes:
170  attributes["parameters"] = ArrayAttr.get(parameters)
171 
172  attributes["module_type"] = TypeAttr.get(hw.ModuleType.get(module_ports))
173 
174  _ods_cext.ir.OpView.__init__(
175  self,
176  self.build_generic(attributes=attributes,
177  results=results,
178  operands=operands,
179  loc=loc,
180  ip=ip))
181 
182  if body_builder:
183  entry_block = self.add_entry_block()
184 
185  with InsertionPoint(entry_block):
186  with support.BackedgeBuilder(str(name)):
187  outputs = body_builder(self)
188  _create_output_op(name, output_ports, entry_block, outputs)
189 
190  @property
191  def type(self):
192  return hw.ModuleType(TypeAttr(self.attributes["module_type"]).value)
193 
194  @property
195  def name(self):
196  return self.attributes["sym_name"]
197 
198  @property
199  def is_external(self):
200  return len(self.regions[0].blocks) == 0
201 
202  @property
203  def parameters(self) -> list[ParamDeclAttr]:
204  return [
205  hw.ParamDeclAttr(a) for a in ArrayAttr(self.attributes["parameters"])
206  ]
207 
208  def instantiate(self,
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(self,
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  super().__init__(name,
312  input_ports,
313  output_ports,
314  parameters=parameters,
315  attributes=attributes,
316  body_builder=body_builder,
317  loc=loc,
318  ip=ip)
319 
320  @property
321  def body(self):
322  return self.regions[0]
323 
324  @property
325  def entry_block(self):
326  return self.regions[0].blocks[0]
327 
328  @property
329  def input_indices(self):
330  indices: dict[int, str] = {}
331  op_names = self.typetype.input_names
332  for idx, name in enumerate(op_names):
333  indices[name] = idx
334  return indices
335 
336  # Support attribute access to block arguments by name
337  def __getattr__(self, name):
338  if name in self.input_indicesinput_indices:
339  index = self.input_indicesinput_indices[name]
340  return self.entry_blockentry_block.arguments[index]
341  raise AttributeError(f"unknown input port name {name}")
342 
343  def inputs(self) -> dict[str:Value]:
344  ret = {}
345  for (name, idx) in self.input_indicesinput_indices.items():
346  ret[name] = self.entry_blockentry_block.arguments[idx]
347  return ret
348 
349  def outputs(self) -> dict[str:Type]:
350  result_names = self.typetype.output_names
351  result_types = self.typetype.output_types
352  return dict(zip(result_names, result_types))
353 
354  def add_entry_block(self):
355  if not self.is_externalis_external:
356  raise IndexError('The module already has an entry block')
357  self.bodybody.blocks.append(*self.typetype.input_types)
358  return self.bodybody.blocks[0]
359 
360 
361 @_ods_cext.register_operation(_Dialect, replace=True)
363  """Specialization for the HW module op class."""
364 
365  def __init__(
366  self,
367  name,
368  input_ports=[],
369  output_ports=[],
370  *,
371  parameters=[],
372  attributes={},
373  body_builder=None,
374  loc=None,
375  ip=None,
376  ):
377  if "comment" not in attributes:
378  attributes["comment"] = StringAttr.get("")
379  super().__init__(name,
380  input_ports,
381  output_ports,
382  parameters=parameters,
383  attributes=attributes,
384  body_builder=body_builder,
385  loc=loc,
386  ip=ip)
387 
388 
389 @_ods_cext.register_operation(_Dialect, replace=True)
391 
392  @staticmethod
393  def create(data_type, value):
394  return hw.ConstantOp(IntegerAttr.get(data_type, value))
395 
396 
397 @_ods_cext.register_operation(_Dialect, replace=True)
399 
400  @staticmethod
401  def create(data_type, value):
402  value = support.get_value(value)
403  return hw.BitcastOp(data_type, value)
404 
405 
406 @_ods_cext.register_operation(_Dialect, replace=True)
408 
409  @staticmethod
410  def create(array_value, idx):
411  array_value = support.get_value(array_value)
412  array_type = support.get_self_or_inner(array_value.type)
413  if isinstance(idx, int):
414  idx_width = (array_type.size - 1).bit_length()
415  idx_val = ConstantOp.create(IntegerType.get_signless(idx_width),
416  idx).result
417  else:
418  idx_val = support.get_value(idx)
419  return hw.ArrayGetOp(array_value, idx_val)
420 
421 
422 @_ods_cext.register_operation(_Dialect, replace=True)
424 
425  @staticmethod
426  def create(array_value, low_index, ret_type):
427  array_value = support.get_value(array_value)
428  array_type = support.get_self_or_inner(array_value.type)
429  if isinstance(low_index, int):
430  idx_width = (array_type.size - 1).bit_length()
431  idx_width = max(1, idx_width) # hw.constant cannot produce i0.
432  idx_val = ConstantOp.create(IntegerType.get_signless(idx_width),
433  low_index).result
434  else:
435  idx_val = support.get_value(low_index)
436  return hw.ArraySliceOp(ret_type, array_value, idx_val)
437 
438 
439 @_ods_cext.register_operation(_Dialect, replace=True)
441 
442  @staticmethod
443  def create(elements):
444  if not elements:
445  raise ValueError("Cannot 'create' an array of length zero")
446  vals = []
447  type = None
448  for i, arg in enumerate(elements):
449  arg_val = support.get_value(arg)
450  vals.append(arg_val)
451  if type is None:
452  type = arg_val.type
453  elif type != arg_val.type:
454  raise TypeError(
455  f"Argument {i} has a different element type ({arg_val.type}) than the element type of the array ({type})"
456  )
457  return hw.ArrayCreateOp(hw.ArrayType.get(type, len(vals)), vals)
458 
459 
460 @_ods_cext.register_operation(_Dialect, replace=True)
462 
463  @staticmethod
464  def create(*sub_arrays):
465  vals = []
466  types = []
467  element_type = None
468  for i, array in enumerate(sub_arrays):
469  array_value = support.get_value(array)
470  array_type = support.type_to_pytype(array_value.type)
471  if array_value is None or not isinstance(array_type, hw.ArrayType):
472  raise TypeError(f"Cannot concatenate {array_value}")
473  if element_type is None:
474  element_type = array_type.element_type
475  elif element_type != array_type.element_type:
476  raise TypeError(
477  f"Argument {i} has a different element type ({element_type}) than the element type of the array ({array_type.element_type})"
478  )
479 
480  vals.append(array_value)
481  types.append(array_type)
482 
483  size = sum(t.size for t in types)
484  combined_type = hw.ArrayType.get(element_type, size)
485  return hw.ArrayConcatOp(combined_type, vals)
486 
487 
488 @_ods_cext.register_operation(_Dialect, replace=True)
490 
491  @staticmethod
492  def create(elements, result_type: Type = None):
493  elem_name_values = [
494  (name, support.get_value(value)) for (name, value) in elements
495  ]
496  struct_fields = [(name, value.type) for (name, value) in elem_name_values]
497  struct_type = hw.StructType.get(struct_fields)
498 
499  if result_type is None:
500  result_type = struct_type
501  else:
502  result_type_inner = support.get_self_or_inner(result_type)
503  if result_type_inner != struct_type:
504  raise TypeError(
505  f"result type:\n\t{result_type_inner}\nmust match generated struct type:\n\t{struct_type}"
506  )
507 
508  return hw.StructCreateOp(result_type,
509  [value for (_, value) in elem_name_values])
510 
511 
512 @_ods_cext.register_operation(_Dialect, replace=True)
514 
515  @staticmethod
516  def create(struct_value, field_name: str):
517  struct_value = support.get_value(struct_value)
518  struct_type = support.get_self_or_inner(struct_value.type)
519  field_type = struct_type.get_field(field_name)
520  field_index = struct_type.get_field_index(field_name)
521  if field_index == UnitAttr.get():
522  raise TypeError(
523  f"field '{field_name}' not element of struct type {struct_type}")
524  return hw.StructExtractOp(field_type, struct_value, field_index)
525 
526 
527 @_ods_cext.register_operation(_Dialect, replace=True)
529 
530  @staticmethod
531  def create(sym_name: str, type: Type, verilog_name: str = None):
532  return hw.TypedeclOp(StringAttr.get(sym_name),
533  TypeAttr.get(type),
534  verilogName=verilog_name)
535 
536 
537 @_ods_cext.register_operation(_Dialect, replace=True)
539 
540  @staticmethod
541  def create(sym_name: str):
542  op = hw.TypeScopeOp(StringAttr.get(sym_name))
543  op.regions[0].blocks.append()
544  return op
545 
546  @property
547  def body(self):
548  return self.regions[0].blocks[0]
def create(*sub_arrays)
Definition: hw.py:464
def create(elements)
Definition: hw.py:443
def create(array_value, idx)
Definition: hw.py:410
def create(array_value, low_index, ret_type)
Definition: hw.py:426
def create(data_type, value)
Definition: hw.py:401
def create(data_type, value)
Definition: hw.py:393
def __init__(self, name, input_ports=[], output_ports=[], *parameters=[], attributes={}, body_builder=None, loc=None, ip=None)
Definition: hw.py:376
def body(self)
Definition: hw.py:321
def add_entry_block(self)
Definition: hw.py:354
dict[str:Value] inputs(self)
Definition: hw.py:343
def entry_block(self)
Definition: hw.py:325
dict[str:Type] outputs(self)
Definition: hw.py:349
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:329
def __getattr__(self, name)
Definition: hw.py:337
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 is_external(self)
Definition: hw.py:199
list[ParamDeclAttr] parameters(self)
Definition: hw.py:203
def name(self)
Definition: hw.py:195
def type(self)
Definition: hw.py:191
def instantiate(self, str name, Dict[str, object] parameters={}, results=None, sym_name=None, loc=None, ip=None, **kwargs)
Definition: hw.py:215
def __init__(self, name, input_ports=[], output_ports=[], *parameters=[], attributes={}, body_builder=None, loc=None, ip=None)
Definition: hw.py:127
def create(elements, Type result_type=None)
Definition: hw.py:492
def create(struct_value, str field_name)
Definition: hw.py:516
def create(str sym_name)
Definition: hw.py:541
def body(self)
Definition: hw.py:547
def create(str sym_name, Type type, str verilog_name=None)
Definition: hw.py:531
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