CIRCT  20.0.0git
CalyxOps.cpp
Go to the documentation of this file.
1 //===- CalyxOps.cpp - Calyx op code defs ------------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This is where op definitions live.
10 //
11 //===----------------------------------------------------------------------===//
12 
17 #include "circt/Dialect/HW/HWOps.h"
19 #include "mlir/IR/AsmState.h"
20 #include "mlir/IR/Builders.h"
21 #include "mlir/IR/BuiltinAttributes.h"
22 #include "mlir/IR/BuiltinTypes.h"
23 #include "mlir/IR/Diagnostics.h"
24 #include "mlir/IR/DialectImplementation.h"
25 #include "mlir/IR/PatternMatch.h"
26 #include "mlir/IR/SymbolTable.h"
27 #include "mlir/Interfaces/FunctionImplementation.h"
28 #include "mlir/Support/LLVM.h"
29 #include "llvm/ADT/DenseMap.h"
30 #include "llvm/ADT/PriorityQueue.h"
31 #include "llvm/ADT/STLExtras.h"
32 #include "llvm/ADT/SmallSet.h"
33 #include "llvm/ADT/StringExtras.h"
34 #include "llvm/ADT/TypeSwitch.h"
35 #include "llvm/Support/Casting.h"
36 
37 using namespace circt;
38 using namespace circt::calyx;
39 using namespace mlir;
40 
41 namespace {
42 
43 // A struct to enforce that the LHS template is one of the RHS templates.
44 // For example:
45 // std::is_any<uint32_t, uint16_t, float, int32_t>::value is false.
46 template <class T, class... Ts>
47 struct IsAny : std::disjunction<std::is_same<T, Ts>...> {};
48 
49 } // namespace
50 
51 //===----------------------------------------------------------------------===//
52 // Utilities related to Direction
53 //===----------------------------------------------------------------------===//
54 
55 Direction direction::get(bool isOutput) {
56  return static_cast<Direction>(isOutput);
57 }
58 
59 IntegerAttr direction::packAttribute(MLIRContext *ctx, size_t nIns,
60  size_t nOuts) {
61  // Pack the array of directions into an APInt. Input direction is zero,
62  // output direction is one.
63  size_t numDirections = nIns + nOuts;
64  APInt portDirections(/*width=*/numDirections, /*value=*/0);
65  for (size_t i = nIns, e = numDirections; i != e; ++i)
66  portDirections.setBit(i);
67 
68  return IntegerAttr::get(IntegerType::get(ctx, numDirections), portDirections);
69 }
70 
71 //===----------------------------------------------------------------------===//
72 // Utilities
73 //===----------------------------------------------------------------------===//
74 
75 /// This pattern collapses a calyx.seq or calyx.par operation when it
76 /// contains exactly one calyx.enable operation.
77 template <typename CtrlOp>
80  LogicalResult matchAndRewrite(CtrlOp ctrlOp,
81  PatternRewriter &rewriter) const override {
82  auto &ops = ctrlOp.getBodyBlock()->getOperations();
83  bool isUnaryControl =
84  (ops.size() == 1) && isa<EnableOp>(ops.front()) &&
85  isa<SeqOp, ParOp, StaticSeqOp, StaticParOp>(ctrlOp->getParentOp());
86  if (!isUnaryControl)
87  return failure();
88 
89  ops.front().moveBefore(ctrlOp);
90  rewriter.eraseOp(ctrlOp);
91  return success();
92  }
93 };
94 
95 /// Verify that the value is not a "complex" value. For example, the source
96 /// of an AssignOp should be a constant or port, e.g.
97 /// %and = comb.and %a, %b : i1
98 /// calyx.assign %port = %c1_i1 ? %and : i1 // Incorrect
99 /// calyx.assign %port = %and ? %c1_i1 : i1 // Correct
100 /// TODO(Calyx): This is useful to verify current MLIR can be lowered to the
101 /// native compiler. Remove this when Calyx supports wire declarations.
102 /// See: https://github.com/llvm/circt/pull/1774 for context.
103 template <typename Op>
104 static LogicalResult verifyNotComplexSource(Op op) {
105  Operation *definingOp = op.getSrc().getDefiningOp();
106  if (definingOp == nullptr)
107  // This is a port of the parent component.
108  return success();
109 
110  // Currently, we use the Combinational dialect to perform logical operations
111  // on wires, i.e. comb::AndOp, comb::OrOp, comb::XorOp.
112  if (auto dialect = definingOp->getDialect(); isa<comb::CombDialect>(dialect))
113  return op->emitOpError("has source that is not a port or constant. "
114  "Complex logic should be conducted in the guard.");
115 
116  return success();
117 }
118 
119 /// Convenience function for getting the SSA name of `v` under the scope of
120 /// operation `scopeOp`.
121 static std::string valueName(Operation *scopeOp, Value v) {
122  std::string s;
123  llvm::raw_string_ostream os(s);
124  // CAVEAT: Since commit 27df7158fe MLIR prefers verifiers to print errors for
125  // operations in generic form, and the printer by default runs a verification.
126  // `valueName` is used in some of these verifiers where preferably the generic
127  // operand form should be used instead.
128  AsmState asmState(scopeOp, OpPrintingFlags().assumeVerified());
129  v.printAsOperand(os, asmState);
130  return s;
131 }
132 
133 /// Returns whether this value is either (1) a port on a ComponentOp or (2) a
134 /// port on a cell interface.
135 static bool isPort(Value value) {
136  Operation *definingOp = value.getDefiningOp();
137  return isa<BlockArgument>(value) ||
138  isa_and_nonnull<CellInterface>(definingOp);
139 }
140 
141 /// Gets the port for a given BlockArgument.
142 PortInfo calyx::getPortInfo(BlockArgument arg) {
143  Operation *op = arg.getOwner()->getParentOp();
144  assert(isa<ComponentInterface>(op) &&
145  "Only ComponentInterface should support lookup by BlockArgument.");
146  return cast<ComponentInterface>(op).getPortInfo()[arg.getArgNumber()];
147 }
148 
149 /// Returns whether the given operation has a control region.
150 static bool hasControlRegion(Operation *op) {
151  return isa<ControlOp, SeqOp, IfOp, RepeatOp, WhileOp, ParOp, StaticRepeatOp,
152  StaticParOp, StaticSeqOp, StaticIfOp>(op);
153 }
154 
155 /// Returns whether the given operation is a static control operator
156 static bool isStaticControl(Operation *op) {
157  if (isa<EnableOp>(op)) {
158  // for enables, we need to check whether its corresponding group is static
159  auto component = op->getParentOfType<ComponentOp>();
160  auto enableOp = llvm::cast<EnableOp>(op);
161  StringRef groupName = enableOp.getGroupName();
162  auto group = component.getWiresOp().lookupSymbol<GroupInterface>(groupName);
163  return isa<StaticGroupOp>(group);
164  }
165  return isa<StaticIfOp, StaticSeqOp, StaticRepeatOp, StaticParOp>(op);
166 }
167 
168 /// Verifies the body of a ControlLikeOp.
169 static LogicalResult verifyControlBody(Operation *op) {
170  if (isa<SeqOp, ParOp, StaticSeqOp, StaticParOp>(op))
171  // This does not apply to sequential and parallel regions.
172  return success();
173 
174  // Some ControlLike operations have (possibly) multiple regions, e.g. IfOp.
175  for (auto &region : op->getRegions()) {
176  auto opsIt = region.getOps();
177  size_t numOperations = std::distance(opsIt.begin(), opsIt.end());
178  // A body of a ControlLike operation may have a single EnableOp within it.
179  // However, that must be the only operation.
180  // E.g. Allowed: calyx.control { calyx.enable @A }
181  // Not Allowed: calyx.control { calyx.enable @A calyx.seq { ... } }
182  bool usesEnableAsCompositionOperator =
183  numOperations > 1 && llvm::any_of(region.front(), [](auto &&bodyOp) {
184  return isa<EnableOp>(bodyOp);
185  });
186  if (usesEnableAsCompositionOperator)
187  return op->emitOpError(
188  "EnableOp is not a composition operator. It should be nested "
189  "in a control flow operation, such as \"calyx.seq\"");
190 
191  // Verify that multiple control flow operations are nested inside a single
192  // control operator. See: https://github.com/llvm/circt/issues/1723
193  size_t numControlFlowRegions =
194  llvm::count_if(opsIt, [](auto &&op) { return hasControlRegion(&op); });
195  if (numControlFlowRegions > 1)
196  return op->emitOpError(
197  "has an invalid control sequence. Multiple control flow operations "
198  "must all be nested in a single calyx.seq or calyx.par");
199  }
200  return success();
201 }
202 
203 LogicalResult calyx::verifyComponent(Operation *op) {
204  auto *opParent = op->getParentOp();
205  if (!isa<ModuleOp>(opParent))
206  return op->emitOpError()
207  << "has parent: " << opParent << ", expected ModuleOp.";
208  return success();
209 }
210 
211 LogicalResult calyx::verifyCell(Operation *op) {
212  auto opParent = op->getParentOp();
213  if (!isa<ComponentInterface>(opParent))
214  return op->emitOpError()
215  << "has parent: " << opParent << ", expected ComponentInterface.";
216  return success();
217 }
218 
219 LogicalResult calyx::verifyControlLikeOp(Operation *op) {
220  auto parent = op->getParentOp();
221 
222  if (isa<calyx::EnableOp>(op) &&
223  !isa<calyx::CalyxDialect>(parent->getDialect())) {
224  // Allow embedding calyx.enable ops within other dialects. This is motivated
225  // by allowing experimentation with new styles of Calyx lowering. For more
226  // info and the historical discussion, see:
227  // https://github.com/llvm/circt/pull/3211
228  return success();
229  }
230 
231  if (!hasControlRegion(parent))
232  return op->emitOpError()
233  << "has parent: " << parent
234  << ", which is not allowed for a control-like operation.";
235 
236  if (op->getNumRegions() == 0)
237  return success();
238 
239  auto &region = op->getRegion(0);
240  // Operations that are allowed in the body of a ControlLike op.
241  auto isValidBodyOp = [](Operation *operation) {
242  return isa<EnableOp, InvokeOp, SeqOp, IfOp, RepeatOp, WhileOp, ParOp,
243  StaticParOp, StaticRepeatOp, StaticSeqOp, StaticIfOp>(operation);
244  };
245  for (auto &&bodyOp : region.front()) {
246  if (isValidBodyOp(&bodyOp))
247  continue;
248 
249  return op->emitOpError()
250  << "has operation: " << bodyOp.getName()
251  << ", which is not allowed in this control-like operation";
252  }
253  return verifyControlBody(op);
254 }
255 
256 LogicalResult calyx::verifyIf(Operation *op) {
257  auto ifOp = dyn_cast<IfInterface>(op);
258 
259  if (ifOp.elseBodyExists() && ifOp.getElseBody()->empty())
260  return ifOp->emitOpError() << "empty 'else' region.";
261 
262  return success();
263 }
264 
265 // Helper function for parsing a group port operation, i.e. GroupDoneOp and
266 // GroupPortOp. These may take one of two different forms:
267 // (1) %<guard> ? %<src> : i1
268 // (2) %<src> : i1
269 static ParseResult parseGroupPort(OpAsmParser &parser, OperationState &result) {
270  SmallVector<OpAsmParser::UnresolvedOperand, 2> operandInfos;
271  OpAsmParser::UnresolvedOperand guardOrSource;
272  if (parser.parseOperand(guardOrSource))
273  return failure();
274 
275  if (succeeded(parser.parseOptionalQuestion())) {
276  OpAsmParser::UnresolvedOperand source;
277  // The guard exists.
278  if (parser.parseOperand(source))
279  return failure();
280  operandInfos.push_back(source);
281  }
282  // No matter if this is the source or guard, it should be last.
283  operandInfos.push_back(guardOrSource);
284 
285  Type type;
286  // Resolving the operands with the same type works here since the source and
287  // guard of a group port is always i1.
288  if (parser.parseColonType(type) ||
289  parser.resolveOperands(operandInfos, type, result.operands))
290  return failure();
291 
292  return success();
293 }
294 
295 // A helper function for printing group ports, i.e. GroupGoOp and GroupDoneOp.
296 template <typename GroupPortType>
297 static void printGroupPort(OpAsmPrinter &p, GroupPortType op) {
298  static_assert(IsAny<GroupPortType, GroupGoOp, GroupDoneOp>(),
299  "Should be a Calyx Group port.");
300 
301  p << " ";
302  // The guard is optional.
303  Value guard = op.getGuard(), source = op.getSrc();
304  if (guard)
305  p << guard << " ? ";
306  p << source << " : " << source.getType();
307 }
308 
309 // Collapse nested control of the same type for SeqOp and ParOp, e.g.
310 // calyx.seq { calyx.seq { ... } } -> calyx.seq { ... }
311 template <typename OpTy>
312 static LogicalResult collapseControl(OpTy controlOp,
313  PatternRewriter &rewriter) {
314  static_assert(IsAny<OpTy, SeqOp, ParOp, StaticSeqOp, StaticParOp>(),
315  "Should be a SeqOp, ParOp, StaticSeqOp, or StaticParOp");
316 
317  if (isa<OpTy>(controlOp->getParentOp())) {
318  Block *controlBody = controlOp.getBodyBlock();
319  for (auto &op : make_early_inc_range(*controlBody))
320  op.moveBefore(controlOp);
321 
322  rewriter.eraseOp(controlOp);
323  return success();
324  }
325 
326  return failure();
327 }
328 
329 template <typename OpTy>
330 static LogicalResult emptyControl(OpTy controlOp, PatternRewriter &rewriter) {
331  if (controlOp.getBodyBlock()->empty()) {
332  rewriter.eraseOp(controlOp);
333  return success();
334  }
335  return failure();
336 }
337 
338 /// A helper function to check whether the conditional and group (if it exists)
339 /// needs to be erased to maintain a valid state of a Calyx program. If these
340 /// have no more uses, they will be erased.
341 template <typename OpTy>
343  PatternRewriter &rewriter) {
344  static_assert(IsAny<OpTy, IfOp, WhileOp>(),
345  "This is only applicable to WhileOp and IfOp.");
346 
347  // Save information about the operation, and erase it.
348  Value cond = op.getCond();
349  std::optional<StringRef> groupName = op.getGroupName();
350  auto component = op->template getParentOfType<ComponentOp>();
351  rewriter.eraseOp(op);
352 
353  // Clean up the attached conditional and combinational group (if it exists).
354  if (groupName) {
355  auto group = component.getWiresOp().template lookupSymbol<GroupInterface>(
356  *groupName);
357  if (SymbolTable::symbolKnownUseEmpty(group, component.getRegion()))
358  rewriter.eraseOp(group);
359  }
360  // Check the conditional after the Group, since it will be driven within.
361  if (!isa<BlockArgument>(cond) && cond.getDefiningOp()->use_empty())
362  rewriter.eraseOp(cond.getDefiningOp());
363 }
364 
365 /// A helper function to check whether the conditional needs to be erased
366 /// to maintain a valid state of a Calyx program. If these
367 /// have no more uses, they will be erased.
368 template <typename OpTy>
369 static void eraseControlWithConditional(OpTy op, PatternRewriter &rewriter) {
370  static_assert(std::is_same<OpTy, StaticIfOp>(),
371  "This is only applicable to StatifIfOp.");
372 
373  // Save information about the operation, and erase it.
374  Value cond = op.getCond();
375  rewriter.eraseOp(op);
376 
377  // Check if conditional is still needed, and remove if it isn't
378  if (!isa<BlockArgument>(cond) && cond.getDefiningOp()->use_empty())
379  rewriter.eraseOp(cond.getDefiningOp());
380 }
381 
382 //===----------------------------------------------------------------------===//
383 // ComponentInterface
384 //===----------------------------------------------------------------------===//
385 
386 template <typename ComponentTy>
387 static void printComponentInterface(OpAsmPrinter &p, ComponentInterface comp) {
388  auto componentName = comp->template getAttrOfType<StringAttr>(
389  ::mlir::SymbolTable::getSymbolAttrName())
390  .getValue();
391  p << " ";
392  p.printSymbolName(componentName);
393 
394  // Print the port definition list for input and output ports.
395  auto printPortDefList = [&](auto ports) {
396  p << "(";
397  llvm::interleaveComma(ports, p, [&](const PortInfo &port) {
398  p << "%" << port.name.getValue() << ": " << port.type;
399  if (!port.attributes.empty()) {
400  p << " ";
401  p.printAttributeWithoutType(port.attributes);
402  }
403  });
404  p << ")";
405  };
406  printPortDefList(comp.getInputPortInfo());
407  p << " -> ";
408  printPortDefList(comp.getOutputPortInfo());
409 
410  p << " ";
411  p.printRegion(*comp.getRegion(), /*printEntryBlockArgs=*/false,
412  /*printBlockTerminators=*/false,
413  /*printEmptyBlock=*/false);
414 
415  SmallVector<StringRef> elidedAttrs = {
416  "portAttributes",
417  "portNames",
418  "portDirections",
419  "sym_name",
420  ComponentTy::getFunctionTypeAttrName(comp->getName()),
421  ComponentTy::getArgAttrsAttrName(comp->getName()),
422  ComponentTy::getResAttrsAttrName(comp->getName())};
423  p.printOptionalAttrDict(comp->getAttrs(), elidedAttrs);
424 }
425 
426 /// Parses the ports of a Calyx component signature, and adds the corresponding
427 /// port names to `attrName`.
428 static ParseResult
429 parsePortDefList(OpAsmParser &parser, OperationState &result,
430  SmallVectorImpl<OpAsmParser::Argument> &ports,
431  SmallVectorImpl<Type> &portTypes,
432  SmallVectorImpl<NamedAttrList> &portAttrs) {
433  auto parsePort = [&]() -> ParseResult {
434  OpAsmParser::Argument port;
435  Type portType;
436  // Expect each port to have the form `%<ssa-name> : <type>`.
437  if (parser.parseArgument(port) || parser.parseColon() ||
438  parser.parseType(portType))
439  return failure();
440  port.type = portType;
441  ports.push_back(port);
442  portTypes.push_back(portType);
443 
444  NamedAttrList portAttr;
445  portAttrs.push_back(succeeded(parser.parseOptionalAttrDict(portAttr))
446  ? portAttr
447  : NamedAttrList());
448  return success();
449  };
450 
451  return parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
452  parsePort);
453 }
454 
455 /// Parses the signature of a Calyx component.
456 static ParseResult
457 parseComponentSignature(OpAsmParser &parser, OperationState &result,
458  SmallVectorImpl<OpAsmParser::Argument> &ports,
459  SmallVectorImpl<Type> &portTypes) {
460  SmallVector<OpAsmParser::Argument> inPorts, outPorts;
461  SmallVector<Type> inPortTypes, outPortTypes;
462  SmallVector<NamedAttrList> portAttributes;
463 
464  if (parsePortDefList(parser, result, inPorts, inPortTypes, portAttributes))
465  return failure();
466 
467  if (parser.parseArrow() ||
468  parsePortDefList(parser, result, outPorts, outPortTypes, portAttributes))
469  return failure();
470 
471  auto *context = parser.getBuilder().getContext();
472  // Add attribute for port names; these are currently
473  // just inferred from the SSA names of the component.
474  SmallVector<Attribute> portNames;
475  auto getPortName = [context](const auto &port) -> StringAttr {
476  StringRef name = port.ssaName.name;
477  if (name.starts_with("%"))
478  name = name.drop_front();
479  return StringAttr::get(context, name);
480  };
481  llvm::transform(inPorts, std::back_inserter(portNames), getPortName);
482  llvm::transform(outPorts, std::back_inserter(portNames), getPortName);
483 
484  result.addAttribute("portNames", ArrayAttr::get(context, portNames));
485  result.addAttribute(
486  "portDirections",
487  direction::packAttribute(context, inPorts.size(), outPorts.size()));
488 
489  ports.append(inPorts);
490  ports.append(outPorts);
491  portTypes.append(inPortTypes);
492  portTypes.append(outPortTypes);
493 
494  SmallVector<Attribute> portAttrs;
495  llvm::transform(portAttributes, std::back_inserter(portAttrs),
496  [&](auto attr) { return attr.getDictionary(context); });
497  result.addAttribute("portAttributes", ArrayAttr::get(context, portAttrs));
498 
499  return success();
500 }
501 
502 template <typename ComponentTy>
503 static ParseResult parseComponentInterface(OpAsmParser &parser,
504  OperationState &result) {
505  using namespace mlir::function_interface_impl;
506 
507  StringAttr componentName;
508  if (parser.parseSymbolName(componentName,
509  ::mlir::SymbolTable::getSymbolAttrName(),
510  result.attributes))
511  return failure();
512 
513  SmallVector<mlir::OpAsmParser::Argument> ports;
514 
515  SmallVector<Type> portTypes;
516  if (parseComponentSignature(parser, result, ports, portTypes))
517  return failure();
518 
519  // Build the component's type for FunctionLike trait. All ports are listed
520  // as arguments so they may be accessed within the component.
521  auto type = parser.getBuilder().getFunctionType(portTypes, /*results=*/{});
522  result.addAttribute(ComponentTy::getFunctionTypeAttrName(result.name),
523  TypeAttr::get(type));
524 
525  auto *body = result.addRegion();
526  if (parser.parseRegion(*body, ports))
527  return failure();
528 
529  if (body->empty())
530  body->push_back(new Block());
531 
532  if (parser.parseOptionalAttrDict(result.attributes))
533  return failure();
534 
535  return success();
536 }
537 
538 /// Returns a new vector containing the concatenation of vectors `a` and `b`.
539 template <typename T>
540 static SmallVector<T> concat(const SmallVectorImpl<T> &a,
541  const SmallVectorImpl<T> &b) {
542  SmallVector<T> out;
543  out.append(a);
544  out.append(b);
545  return out;
546 }
547 
548 static void buildComponentLike(OpBuilder &builder, OperationState &result,
549  StringAttr name, ArrayRef<PortInfo> ports,
550  bool combinational) {
551  using namespace mlir::function_interface_impl;
552 
553  result.addAttribute(::mlir::SymbolTable::getSymbolAttrName(), name);
554 
555  std::pair<SmallVector<Type, 8>, SmallVector<Type, 8>> portIOTypes;
556  std::pair<SmallVector<Attribute, 8>, SmallVector<Attribute, 8>> portIONames;
557  std::pair<SmallVector<Attribute, 8>, SmallVector<Attribute, 8>>
558  portIOAttributes;
559  SmallVector<Direction, 8> portDirections;
560  // Avoid using llvm::partition or llvm::sort to preserve relative ordering
561  // between individual inputs and outputs.
562  for (auto &&port : ports) {
563  bool isInput = port.direction == Direction::Input;
564  (isInput ? portIOTypes.first : portIOTypes.second).push_back(port.type);
565  (isInput ? portIONames.first : portIONames.second).push_back(port.name);
566  (isInput ? portIOAttributes.first : portIOAttributes.second)
567  .push_back(port.attributes);
568  }
569  auto portTypes = concat(portIOTypes.first, portIOTypes.second);
570  auto portNames = concat(portIONames.first, portIONames.second);
571  auto portAttributes = concat(portIOAttributes.first, portIOAttributes.second);
572 
573  // Build the function type of the component.
574  auto functionType = builder.getFunctionType(portTypes, {});
575  if (combinational) {
576  result.addAttribute(CombComponentOp::getFunctionTypeAttrName(result.name),
577  TypeAttr::get(functionType));
578  } else {
579  result.addAttribute(ComponentOp::getFunctionTypeAttrName(result.name),
580  TypeAttr::get(functionType));
581  }
582 
583  // Record the port names and number of input ports of the component.
584  result.addAttribute("portNames", builder.getArrayAttr(portNames));
585  result.addAttribute("portDirections",
586  direction::packAttribute(builder.getContext(),
587  portIOTypes.first.size(),
588  portIOTypes.second.size()));
589  // Record the attributes of the ports.
590  result.addAttribute("portAttributes", builder.getArrayAttr(portAttributes));
591 
592  // Create a single-blocked region.
593  Region *region = result.addRegion();
594  Block *body = new Block();
595  region->push_back(body);
596 
597  // Add all ports to the body.
598  body->addArguments(portTypes, SmallVector<Location, 4>(
599  portTypes.size(), builder.getUnknownLoc()));
600 
601  // Insert the WiresOp and ControlOp.
602  IRRewriter::InsertionGuard guard(builder);
603  builder.setInsertionPointToStart(body);
604  builder.create<WiresOp>(result.location);
605  if (!combinational)
606  builder.create<ControlOp>(result.location);
607 }
608 
609 //===----------------------------------------------------------------------===//
610 // ComponentOp
611 //===----------------------------------------------------------------------===//
612 
613 /// This is a helper function that should only be used to get the WiresOp or
614 /// ControlOp of a ComponentOp, which are guaranteed to exist and generally at
615 /// the end of a component's body. In the worst case, this will run in linear
616 /// time with respect to the number of instances within the component.
617 template <typename Op>
618 static Op getControlOrWiresFrom(ComponentOp op) {
619  auto *body = op.getBodyBlock();
620  // We verify there is a single WiresOp and ControlOp,
621  // so this is safe.
622  auto opIt = body->getOps<Op>().begin();
623  return *opIt;
624 }
625 
626 /// Returns the Block argument with the given name from a ComponentOp.
627 /// If the name doesn't exist, returns an empty Value.
628 static Value getBlockArgumentWithName(StringRef name, ComponentOp op) {
629  ArrayAttr portNames = op.getPortNames();
630 
631  for (size_t i = 0, e = portNames.size(); i != e; ++i) {
632  auto portName = cast<StringAttr>(portNames[i]);
633  if (portName.getValue() == name)
634  return op.getBodyBlock()->getArgument(i);
635  }
636  return Value{};
637 }
638 
639 WiresOp calyx::ComponentOp::getWiresOp() {
640  return getControlOrWiresFrom<WiresOp>(*this);
641 }
642 
643 ControlOp calyx::ComponentOp::getControlOp() {
644  return getControlOrWiresFrom<ControlOp>(*this);
645 }
646 
647 Value calyx::ComponentOp::getGoPort() {
648  return getBlockArgumentWithName(goPort, *this);
649 }
650 
651 Value calyx::ComponentOp::getDonePort() {
652  return getBlockArgumentWithName(donePort, *this);
653 }
654 
655 Value calyx::ComponentOp::getClkPort() {
656  return getBlockArgumentWithName(clkPort, *this);
657 }
658 
659 Value calyx::ComponentOp::getResetPort() {
660  return getBlockArgumentWithName(resetPort, *this);
661 }
662 
663 SmallVector<PortInfo> ComponentOp::getPortInfo() {
664  auto portTypes = getArgumentTypes();
665  ArrayAttr portNamesAttr = getPortNames(), portAttrs = getPortAttributes();
666  APInt portDirectionsAttr = getPortDirections();
667 
668  SmallVector<PortInfo> results;
669  for (size_t i = 0, e = portNamesAttr.size(); i != e; ++i) {
670  results.push_back(PortInfo{cast<StringAttr>(portNamesAttr[i]), portTypes[i],
671  direction::get(portDirectionsAttr[i]),
672  cast<DictionaryAttr>(portAttrs[i])});
673  }
674  return results;
675 }
676 
677 /// A helper function to return a filtered subset of a component's ports.
678 template <typename Pred>
679 static SmallVector<PortInfo> getFilteredPorts(ComponentOp op, Pred p) {
680  SmallVector<PortInfo> ports = op.getPortInfo();
681  llvm::erase_if(ports, p);
682  return ports;
683 }
684 
685 SmallVector<PortInfo> ComponentOp::getInputPortInfo() {
686  return getFilteredPorts(
687  *this, [](const PortInfo &port) { return port.direction == Output; });
688 }
689 
690 SmallVector<PortInfo> ComponentOp::getOutputPortInfo() {
691  return getFilteredPorts(
692  *this, [](const PortInfo &port) { return port.direction == Input; });
693 }
694 
695 void ComponentOp::print(OpAsmPrinter &p) {
696  printComponentInterface<ComponentOp>(p, *this);
697 }
698 
699 ParseResult ComponentOp::parse(OpAsmParser &parser, OperationState &result) {
700  return parseComponentInterface<ComponentOp>(parser, result);
701 }
702 
703 /// Determines whether the given ComponentOp has all the required ports.
704 static LogicalResult hasRequiredPorts(ComponentOp op) {
705  // Get all identifiers from the component ports.
706  llvm::SmallVector<StringRef, 4> identifiers;
707  for (PortInfo &port : op.getPortInfo()) {
708  auto portIds = port.getAllIdentifiers();
709  identifiers.append(portIds.begin(), portIds.end());
710  }
711  // Sort the identifiers: a pre-condition for std::set_intersection.
712  std::sort(identifiers.begin(), identifiers.end());
713 
714  llvm::SmallVector<StringRef, 4> intersection,
715  interfacePorts{clkPort, donePort, goPort, resetPort};
716  // Find the intersection between all identifiers and required ports.
717  std::set_intersection(interfacePorts.begin(), interfacePorts.end(),
718  identifiers.begin(), identifiers.end(),
719  std::back_inserter(intersection));
720 
721  if (intersection.size() == interfacePorts.size())
722  return success();
723 
724  SmallVector<StringRef, 4> difference;
725  std::set_difference(interfacePorts.begin(), interfacePorts.end(),
726  intersection.begin(), intersection.end(),
727  std::back_inserter(difference));
728  return op->emitOpError()
729  << "is missing the following required port attribute identifiers: "
730  << difference;
731 }
732 
733 LogicalResult ComponentOp::verify() {
734  // Verify there is exactly one of each the wires and control operations.
735  auto wIt = getBodyBlock()->getOps<WiresOp>();
736  auto cIt = getBodyBlock()->getOps<ControlOp>();
737  if (std::distance(wIt.begin(), wIt.end()) +
738  std::distance(cIt.begin(), cIt.end()) !=
739  2)
740  return emitOpError() << "requires exactly one of each: '"
741  << WiresOp::getOperationName() << "', '"
742  << ControlOp::getOperationName() << "'.";
743 
744  if (failed(hasRequiredPorts(*this)))
745  return failure();
746 
747  // Verify the component actually does something: has a non-empty Control
748  // region, or continuous assignments.
749  bool hasNoControlConstructs = true;
750  getControlOp().walk<WalkOrder::PreOrder>([&](Operation *op) {
751  if (isa<EnableOp, InvokeOp, fsm::MachineOp>(op)) {
752  hasNoControlConstructs = false;
753  return WalkResult::interrupt();
754  }
755  return WalkResult::advance();
756  });
757  bool hasNoAssignments =
758  getWiresOp().getBodyBlock()->getOps<AssignOp>().empty();
759  if (hasNoControlConstructs && hasNoAssignments)
760  return emitOpError(
761  "The component currently does nothing. It needs to either have "
762  "continuous assignments in the Wires region or control "
763  "constructs in the Control region. The Control region "
764  "should contain at least one of ")
765  << "'" << EnableOp::getOperationName() << "' , "
766  << "'" << InvokeOp::getOperationName() << "' or "
767  << "'" << fsm::MachineOp::getOperationName() << "'.";
768  return success();
769 }
770 
771 void ComponentOp::build(OpBuilder &builder, OperationState &result,
772  StringAttr name, ArrayRef<PortInfo> ports) {
773  buildComponentLike(builder, result, name, ports, /*combinational=*/false);
774 }
775 
776 void ComponentOp::getAsmBlockArgumentNames(
777  mlir::Region &region, mlir::OpAsmSetValueNameFn setNameFn) {
778  if (region.empty())
779  return;
780  auto ports = getPortNames();
781  auto *block = &getRegion()->front();
782  for (size_t i = 0, e = block->getNumArguments(); i != e; ++i)
783  setNameFn(block->getArgument(i), cast<StringAttr>(ports[i]).getValue());
784 }
785 
786 //===----------------------------------------------------------------------===//
787 // CombComponentOp
788 //===----------------------------------------------------------------------===//
789 
790 SmallVector<PortInfo> CombComponentOp::getPortInfo() {
791  auto portTypes = getArgumentTypes();
792  ArrayAttr portNamesAttr = getPortNames(), portAttrs = getPortAttributes();
793  APInt portDirectionsAttr = getPortDirections();
794 
795  SmallVector<PortInfo> results;
796  for (size_t i = 0, e = portNamesAttr.size(); i != e; ++i) {
797  results.push_back(PortInfo{cast<StringAttr>(portNamesAttr[i]), portTypes[i],
798  direction::get(portDirectionsAttr[i]),
799  cast<DictionaryAttr>(portAttrs[i])});
800  }
801  return results;
802 }
803 
804 WiresOp calyx::CombComponentOp::getWiresOp() {
805  auto *body = getBodyBlock();
806  auto opIt = body->getOps<WiresOp>().begin();
807  return *opIt;
808 }
809 
810 /// A helper function to return a filtered subset of a comb component's ports.
811 template <typename Pred>
812 static SmallVector<PortInfo> getFilteredPorts(CombComponentOp op, Pred p) {
813  SmallVector<PortInfo> ports = op.getPortInfo();
814  llvm::erase_if(ports, p);
815  return ports;
816 }
817 
818 SmallVector<PortInfo> CombComponentOp::getInputPortInfo() {
819  return getFilteredPorts(
820  *this, [](const PortInfo &port) { return port.direction == Output; });
821 }
822 
823 SmallVector<PortInfo> CombComponentOp::getOutputPortInfo() {
824  return getFilteredPorts(
825  *this, [](const PortInfo &port) { return port.direction == Input; });
826 }
827 
828 void CombComponentOp::print(OpAsmPrinter &p) {
829  printComponentInterface<CombComponentOp>(p, *this);
830 }
831 
832 ParseResult CombComponentOp::parse(OpAsmParser &parser,
833  OperationState &result) {
834  return parseComponentInterface<CombComponentOp>(parser, result);
835 }
836 
837 LogicalResult CombComponentOp::verify() {
838  // Verify there is exactly one wires operation.
839  auto wIt = getBodyBlock()->getOps<WiresOp>();
840  if (std::distance(wIt.begin(), wIt.end()) != 1)
841  return emitOpError() << "requires exactly one "
842  << WiresOp::getOperationName() << " op.";
843 
844  // Verify there is not a control operation.
845  auto cIt = getBodyBlock()->getOps<ControlOp>();
846  if (std::distance(cIt.begin(), cIt.end()) != 0)
847  return emitOpError() << "must not have a `" << ControlOp::getOperationName()
848  << "` op.";
849 
850  // Verify the component actually does something: has continuous assignments.
851  bool hasNoAssignments =
852  getWiresOp().getBodyBlock()->getOps<AssignOp>().empty();
853  if (hasNoAssignments)
854  return emitOpError(
855  "The component currently does nothing. It needs to either have "
856  "continuous assignments in the Wires region.");
857 
858  // Check that all cells are combinational
859  auto cells = getOps<CellInterface>();
860  for (auto cell : cells) {
861  if (!cell.isCombinational())
862  return emitOpError() << "contains non-combinational cell "
863  << cell.instanceName();
864  }
865 
866  // Check that the component has no groups
867  auto groups = getWiresOp().getOps<GroupOp>();
868  if (!groups.empty())
869  return emitOpError() << "contains group " << (*groups.begin()).getSymName();
870 
871  // Combinational groups aren't allowed in combinational components either.
872  // For more information see here:
873  // https://docs.calyxir.org/lang/ref.html#comb-group-definitions
874  auto combGroups = getWiresOp().getOps<CombGroupOp>();
875  if (!combGroups.empty())
876  return emitOpError() << "contains comb group "
877  << (*combGroups.begin()).getSymName();
878 
879  return success();
880 }
881 
882 void CombComponentOp::build(OpBuilder &builder, OperationState &result,
883  StringAttr name, ArrayRef<PortInfo> ports) {
884  buildComponentLike(builder, result, name, ports, /*combinational=*/true);
885 }
886 
887 void CombComponentOp::getAsmBlockArgumentNames(
888  mlir::Region &region, mlir::OpAsmSetValueNameFn setNameFn) {
889  if (region.empty())
890  return;
891  auto ports = getPortNames();
892  auto *block = &getRegion()->front();
893  for (size_t i = 0, e = block->getNumArguments(); i != e; ++i)
894  setNameFn(block->getArgument(i), cast<StringAttr>(ports[i]).getValue());
895 }
896 
897 //===----------------------------------------------------------------------===//
898 // ControlOp
899 //===----------------------------------------------------------------------===//
900 LogicalResult ControlOp::verify() { return verifyControlBody(*this); }
901 
902 // Get the InvokeOps of this ControlOp.
903 SmallVector<InvokeOp, 4> ControlOp::getInvokeOps() {
904  SmallVector<InvokeOp, 4> ret;
905  this->walk([&](InvokeOp invokeOp) { ret.push_back(invokeOp); });
906  return ret;
907 }
908 
909 //===----------------------------------------------------------------------===//
910 // SeqOp
911 //===----------------------------------------------------------------------===//
912 
913 void SeqOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
914  MLIRContext *context) {
915  patterns.add(collapseControl<SeqOp>);
916  patterns.add(emptyControl<SeqOp>);
917  patterns.insert<CollapseUnaryControl<SeqOp>>(context);
918 }
919 
920 //===----------------------------------------------------------------------===//
921 // StaticSeqOp
922 //===----------------------------------------------------------------------===//
923 
924 LogicalResult StaticSeqOp::verify() {
925  // StaticSeqOp should only have static control in it
926  auto &ops = (*this).getBodyBlock()->getOperations();
927  if (!llvm::all_of(ops, [&](Operation &op) { return isStaticControl(&op); })) {
928  return emitOpError("StaticSeqOp has non static control within it");
929  }
930 
931  return success();
932 }
933 
934 void StaticSeqOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
935  MLIRContext *context) {
936  patterns.add(collapseControl<StaticSeqOp>);
937  patterns.add(emptyControl<StaticSeqOp>);
939 }
940 
941 //===----------------------------------------------------------------------===//
942 // ParOp
943 //===----------------------------------------------------------------------===//
944 
945 LogicalResult ParOp::verify() {
946  llvm::SmallSet<StringRef, 8> groupNames;
947 
948  // Add loose requirement that the body of a ParOp may not enable the same
949  // Group more than once, e.g. calyx.par { calyx.enable @G calyx.enable @G }
950  for (EnableOp op : getBodyBlock()->getOps<EnableOp>()) {
951  StringRef groupName = op.getGroupName();
952  if (groupNames.count(groupName))
953  return emitOpError() << "cannot enable the same group: \"" << groupName
954  << "\" more than once.";
955  groupNames.insert(groupName);
956  }
957 
958  return success();
959 }
960 
961 void ParOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
962  MLIRContext *context) {
963  patterns.add(collapseControl<ParOp>);
964  patterns.add(emptyControl<ParOp>);
965  patterns.insert<CollapseUnaryControl<ParOp>>(context);
966 }
967 
968 //===----------------------------------------------------------------------===//
969 // StaticParOp
970 //===----------------------------------------------------------------------===//
971 
972 LogicalResult StaticParOp::verify() {
973  llvm::SmallSet<StringRef, 8> groupNames;
974 
975  // Add loose requirement that the body of a ParOp may not enable the same
976  // Group more than once, e.g. calyx.par { calyx.enable @G calyx.enable @G }
977  for (EnableOp op : getBodyBlock()->getOps<EnableOp>()) {
978  StringRef groupName = op.getGroupName();
979  if (groupNames.count(groupName))
980  return emitOpError() << "cannot enable the same group: \"" << groupName
981  << "\" more than once.";
982  groupNames.insert(groupName);
983  }
984 
985  // static par must only have static control in it
986  auto &ops = (*this).getBodyBlock()->getOperations();
987  for (Operation &op : ops) {
988  if (!isStaticControl(&op)) {
989  return op.emitOpError("StaticParOp has non static control within it");
990  }
991  }
992 
993  return success();
994 }
995 
996 void StaticParOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
997  MLIRContext *context) {
998  patterns.add(collapseControl<StaticParOp>);
999  patterns.add(emptyControl<StaticParOp>);
1001 }
1002 
1003 //===----------------------------------------------------------------------===//
1004 // WiresOp
1005 //===----------------------------------------------------------------------===//
1006 LogicalResult WiresOp::verify() {
1007  auto componentInterface = (*this)->getParentOfType<ComponentInterface>();
1008  if (llvm::isa<ComponentOp>(componentInterface)) {
1009  auto component = llvm::cast<ComponentOp>(componentInterface);
1010  auto control = component.getControlOp();
1011 
1012  // Verify each group is referenced in the control section.
1013  for (auto &&op : *getBodyBlock()) {
1014  if (!isa<GroupInterface>(op))
1015  continue;
1016  auto group = cast<GroupInterface>(op);
1017  auto groupName = group.symName();
1018  if (mlir::SymbolTable::symbolKnownUseEmpty(groupName, control))
1019  return op.emitOpError()
1020  << "with name: " << groupName
1021  << " is unused in the control execution schedule";
1022  }
1023  }
1024 
1025  // Verify that:
1026  // - At most one continuous assignment exists for any given value
1027  // - A continuously assigned wire has no assignments inside groups.
1028  for (auto thisAssignment : getBodyBlock()->getOps<AssignOp>()) {
1029  // Always assume guarded assignments will not be driven simultaneously. We
1030  // liberally assume that guards are mutually exclusive (more elaborate
1031  // static and dynamic checking can be performed to validate such cases).
1032  if (thisAssignment.getGuard())
1033  continue;
1034 
1035  Value dest = thisAssignment.getDest();
1036  for (Operation *user : dest.getUsers()) {
1037  auto assignUser = dyn_cast<AssignOp>(user);
1038  if (!assignUser || assignUser.getDest() != dest ||
1039  assignUser == thisAssignment)
1040  continue;
1041 
1042  return user->emitOpError() << "destination is already continuously "
1043  "driven. Other assignment is "
1044  << thisAssignment;
1045  }
1046  }
1047 
1048  return success();
1049 }
1050 
1051 //===----------------------------------------------------------------------===//
1052 // CombGroupOp
1053 //===----------------------------------------------------------------------===//
1054 
1055 /// Verifies the defining operation of a value is combinational.
1056 static LogicalResult isCombinational(Value value, GroupInterface group) {
1057  Operation *definingOp = value.getDefiningOp();
1058  if (definingOp == nullptr || definingOp->hasTrait<Combinational>())
1059  // This is a port of the parent component or combinational.
1060  return success();
1061 
1062  // For now, assumes all component instances are combinational. Once
1063  // combinational components are supported, this can be strictly enforced.
1064  if (isa<InstanceOp>(definingOp))
1065  return success();
1066 
1067  // Constants and logical operations are OK.
1068  if (isa<comb::CombDialect, hw::HWDialect>(definingOp->getDialect()))
1069  return success();
1070 
1071  // Reads to MemoryOp and RegisterOp are combinational. Writes are not.
1072  if (auto r = dyn_cast<RegisterOp>(definingOp)) {
1073  return value == r.getOut()
1074  ? success()
1075  : group->emitOpError()
1076  << "with register: \"" << r.instanceName()
1077  << "\" is conducting a memory store. This is not "
1078  "combinational.";
1079  } else if (auto m = dyn_cast<MemoryOp>(definingOp)) {
1080  auto writePorts = {m.writeData(), m.writeEn()};
1081  return (llvm::none_of(writePorts, [&](Value p) { return p == value; }))
1082  ? success()
1083  : group->emitOpError()
1084  << "with memory: \"" << m.instanceName()
1085  << "\" is conducting a memory store. This "
1086  "is not combinational.";
1087  }
1088 
1089  std::string portName =
1090  valueName(group->getParentOfType<ComponentOp>(), value);
1091  return group->emitOpError() << "with port: " << portName
1092  << ". This operation is not combinational.";
1093 }
1094 
1095 /// Verifies a combinational group may contain only combinational primitives or
1096 /// perform combinational logic.
1097 LogicalResult CombGroupOp::verify() {
1098  for (auto &&op : *getBodyBlock()) {
1099  auto assign = dyn_cast<AssignOp>(op);
1100  if (assign == nullptr)
1101  continue;
1102  Value dst = assign.getDest(), src = assign.getSrc();
1103  if (failed(isCombinational(dst, *this)) ||
1104  failed(isCombinational(src, *this)))
1105  return failure();
1106  }
1107  return success();
1108 }
1109 
1110 //===----------------------------------------------------------------------===//
1111 // GroupGoOp
1112 //===----------------------------------------------------------------------===//
1113 GroupGoOp GroupOp::getGoOp() {
1114  auto goOps = getBodyBlock()->getOps<GroupGoOp>();
1115  size_t nOps = std::distance(goOps.begin(), goOps.end());
1116  return nOps ? *goOps.begin() : GroupGoOp();
1117 }
1118 
1119 GroupDoneOp GroupOp::getDoneOp() {
1120  auto body = this->getBodyBlock();
1121  return cast<GroupDoneOp>(body->getTerminator());
1122 }
1123 
1124 //===----------------------------------------------------------------------===//
1125 // CycleOp
1126 //===----------------------------------------------------------------------===//
1127 void CycleOp::print(OpAsmPrinter &p) {
1128  p << " ";
1129  // The guard is optional.
1130  auto start = this->getStart();
1131  auto end = this->getEnd();
1132  if (end.has_value()) {
1133  p << "[" << start << ":" << end.value() << "]";
1134  } else {
1135  p << start;
1136  }
1137 }
1138 
1139 ParseResult CycleOp::parse(OpAsmParser &parser, OperationState &result) {
1140  SmallVector<OpAsmParser::UnresolvedOperand, 2> operandInfos;
1141 
1142  uint32_t startLiteral;
1143  uint32_t endLiteral;
1144 
1145  auto hasEnd = succeeded(parser.parseOptionalLSquare());
1146 
1147  if (parser.parseInteger(startLiteral)) {
1148  parser.emitError(parser.getNameLoc(), "Could not parse start cycle");
1149  return failure();
1150  }
1151 
1152  auto start = parser.getBuilder().getI32IntegerAttr(startLiteral);
1153  result.addAttribute(getStartAttrName(result.name), start);
1154 
1155  if (hasEnd) {
1156  if (parser.parseColon())
1157  return failure();
1158 
1159  if (auto res = parser.parseOptionalInteger(endLiteral); res.has_value()) {
1160  auto end = parser.getBuilder().getI32IntegerAttr(endLiteral);
1161  result.addAttribute(getEndAttrName(result.name), end);
1162  }
1163 
1164  if (parser.parseRSquare())
1165  return failure();
1166  }
1167 
1168  result.addTypes(parser.getBuilder().getI1Type());
1169 
1170  return success();
1171 }
1172 
1173 LogicalResult CycleOp::verify() {
1174  uint32_t latency = this->getGroupLatency();
1175 
1176  if (this->getStart() >= latency) {
1177  emitOpError("start cycle must be less than the group latency");
1178  return failure();
1179  }
1180 
1181  if (this->getEnd().has_value()) {
1182  if (this->getStart() >= this->getEnd().value()) {
1183  emitOpError("start cycle must be less than end cycle");
1184  return failure();
1185  }
1186 
1187  if (this->getEnd() >= latency) {
1188  emitOpError("end cycle must be less than the group latency");
1189  return failure();
1190  }
1191  }
1192 
1193  return success();
1194 }
1195 
1196 uint32_t CycleOp::getGroupLatency() {
1197  auto group = (*this)->getParentOfType<StaticGroupOp>();
1198  return group.getLatency();
1199 }
1200 
1201 //===----------------------------------------------------------------------===//
1202 // Floating Point Op
1203 //===----------------------------------------------------------------------===//
1204 FloatingPointStandard AddFOpIEEE754::getFloatingPointStandard() {
1206 }
1207 
1208 FloatingPointStandard MulFOpIEEE754::getFloatingPointStandard() {
1210 }
1211 
1212 std::string AddFOpIEEE754::getCalyxLibraryName() { return "std_addFN"; }
1213 
1214 std::string MulFOpIEEE754::getCalyxLibraryName() { return "std_mulFN"; }
1215 
1216 //===----------------------------------------------------------------------===//
1217 // GroupInterface
1218 //===----------------------------------------------------------------------===//
1219 
1220 /// Determines whether the given port is used in the group. Its use depends on
1221 /// the `isDriven` value; if true, then the port should be a destination in an
1222 /// AssignOp. Otherwise, it should be the source, i.e. a read.
1223 static bool portIsUsedInGroup(GroupInterface group, Value port, bool isDriven) {
1224  return llvm::any_of(port.getUses(), [&](auto &&use) {
1225  auto assignOp = dyn_cast<AssignOp>(use.getOwner());
1226  if (assignOp == nullptr)
1227  return false;
1228 
1229  Operation *parent = assignOp->getParentOp();
1230  if (isa<WiresOp>(parent))
1231  // This is a continuous assignment.
1232  return false;
1233 
1234  // A port is used if it meet the criteria:
1235  // (1) it is a {source, destination} of an assignment.
1236  // (2) that assignment is found in the provided group.
1237 
1238  // If not driven, then read.
1239  Value expected = isDriven ? assignOp.getDest() : assignOp.getSrc();
1240  return expected == port && group == parent;
1241  });
1242 }
1243 
1244 /// Checks whether `port` is driven from within `groupOp`.
1245 static LogicalResult portDrivenByGroup(GroupInterface groupOp, Value port) {
1246  // Check if the port is driven by an assignOp from within `groupOp`.
1247  if (portIsUsedInGroup(groupOp, port, /*isDriven=*/true))
1248  return success();
1249 
1250  // If `port` is an output of a cell then we conservatively enforce that at
1251  // least one input port of the cell must be driven by the group.
1252  if (auto cell = dyn_cast<CellInterface>(port.getDefiningOp());
1253  cell && cell.direction(port) == calyx::Direction::Output)
1254  return groupOp.drivesAnyPort(cell.getInputPorts());
1255 
1256  return failure();
1257 }
1258 
1259 LogicalResult GroupOp::drivesPort(Value port) {
1260  return portDrivenByGroup(*this, port);
1261 }
1262 
1263 LogicalResult CombGroupOp::drivesPort(Value port) {
1264  return portDrivenByGroup(*this, port);
1265 }
1266 
1267 LogicalResult StaticGroupOp::drivesPort(Value port) {
1268  return portDrivenByGroup(*this, port);
1269 }
1270 
1271 /// Checks whether all ports are driven within the group.
1272 static LogicalResult allPortsDrivenByGroup(GroupInterface group,
1273  ValueRange ports) {
1274  return success(llvm::all_of(ports, [&](Value port) {
1275  return portIsUsedInGroup(group, port, /*isDriven=*/true);
1276  }));
1277 }
1278 
1279 LogicalResult GroupOp::drivesAllPorts(ValueRange ports) {
1280  return allPortsDrivenByGroup(*this, ports);
1281 }
1282 
1283 LogicalResult CombGroupOp::drivesAllPorts(ValueRange ports) {
1284  return allPortsDrivenByGroup(*this, ports);
1285 }
1286 
1287 LogicalResult StaticGroupOp::drivesAllPorts(ValueRange ports) {
1288  return allPortsDrivenByGroup(*this, ports);
1289 }
1290 
1291 /// Checks whether any ports are driven within the group.
1292 static LogicalResult anyPortsDrivenByGroup(GroupInterface group,
1293  ValueRange ports) {
1294  return success(llvm::any_of(ports, [&](Value port) {
1295  return portIsUsedInGroup(group, port, /*isDriven=*/true);
1296  }));
1297 }
1298 
1299 LogicalResult GroupOp::drivesAnyPort(ValueRange ports) {
1300  return anyPortsDrivenByGroup(*this, ports);
1301 }
1302 
1303 LogicalResult CombGroupOp::drivesAnyPort(ValueRange ports) {
1304  return anyPortsDrivenByGroup(*this, ports);
1305 }
1306 
1307 LogicalResult StaticGroupOp::drivesAnyPort(ValueRange ports) {
1308  return anyPortsDrivenByGroup(*this, ports);
1309 }
1310 
1311 /// Checks whether any ports are read within the group.
1312 static LogicalResult anyPortsReadByGroup(GroupInterface group,
1313  ValueRange ports) {
1314  return success(llvm::any_of(ports, [&](Value port) {
1315  return portIsUsedInGroup(group, port, /*isDriven=*/false);
1316  }));
1317 }
1318 
1319 LogicalResult GroupOp::readsAnyPort(ValueRange ports) {
1320  return anyPortsReadByGroup(*this, ports);
1321 }
1322 
1323 LogicalResult CombGroupOp::readsAnyPort(ValueRange ports) {
1324  return anyPortsReadByGroup(*this, ports);
1325 }
1326 
1327 LogicalResult StaticGroupOp::readsAnyPort(ValueRange ports) {
1328  return anyPortsReadByGroup(*this, ports);
1329 }
1330 
1331 /// Verifies that certain ports of primitives are either driven or read
1332 /// together.
1333 static LogicalResult verifyPrimitivePortDriving(AssignOp assign,
1334  GroupInterface group) {
1335  Operation *destDefiningOp = assign.getDest().getDefiningOp();
1336  if (destDefiningOp == nullptr)
1337  return success();
1338  auto destCell = dyn_cast<CellInterface>(destDefiningOp);
1339  if (destCell == nullptr)
1340  return success();
1341 
1342  LogicalResult verifyWrites =
1343  TypeSwitch<Operation *, LogicalResult>(destCell)
1344  .Case<RegisterOp>([&](auto op) {
1345  // We only want to verify this is written to if the {write enable,
1346  // in} port is driven.
1347  return succeeded(group.drivesAnyPort({op.getWriteEn(), op.getIn()}))
1348  ? group.drivesAllPorts({op.getWriteEn(), op.getIn()})
1349  : success();
1350  })
1351  .Case<MemoryOp>([&](auto op) {
1352  SmallVector<Value> requiredWritePorts;
1353  // If writing to memory, write_en, write_data, and all address ports
1354  // should be driven.
1355  requiredWritePorts.push_back(op.writeEn());
1356  requiredWritePorts.push_back(op.writeData());
1357  for (Value address : op.addrPorts())
1358  requiredWritePorts.push_back(address);
1359 
1360  // We only want to verify the write ports if either write_data or
1361  // write_en is driven.
1362  return succeeded(
1363  group.drivesAnyPort({op.writeData(), op.writeEn()}))
1364  ? group.drivesAllPorts(requiredWritePorts)
1365  : success();
1366  })
1367  .Case<AndLibOp, OrLibOp, XorLibOp, AddLibOp, SubLibOp, GtLibOp,
1368  LtLibOp, EqLibOp, NeqLibOp, GeLibOp, LeLibOp, LshLibOp,
1369  RshLibOp, SgtLibOp, SltLibOp, SeqLibOp, SneqLibOp, SgeLibOp,
1370  SleLibOp, SrshLibOp>([&](auto op) {
1371  Value lhs = op.getLeft(), rhs = op.getRight();
1372  return succeeded(group.drivesAnyPort({lhs, rhs}))
1373  ? group.drivesAllPorts({lhs, rhs})
1374  : success();
1375  })
1376  .Default([&](auto op) { return success(); });
1377 
1378  if (failed(verifyWrites))
1379  return group->emitOpError()
1380  << "with cell: " << destCell->getName() << " \""
1381  << destCell.instanceName()
1382  << "\" is performing a write and failed to drive all necessary "
1383  "ports.";
1384 
1385  Operation *srcDefiningOp = assign.getSrc().getDefiningOp();
1386  if (srcDefiningOp == nullptr)
1387  return success();
1388  auto srcCell = dyn_cast<CellInterface>(srcDefiningOp);
1389  if (srcCell == nullptr)
1390  return success();
1391 
1392  LogicalResult verifyReads =
1393  TypeSwitch<Operation *, LogicalResult>(srcCell)
1394  .Case<MemoryOp>([&](auto op) {
1395  // If reading memory, all address ports should be driven. Note that
1396  // we only want to verify the read ports if read_data is used in the
1397  // group.
1398  return succeeded(group.readsAnyPort({op.readData()}))
1399  ? group.drivesAllPorts(op.addrPorts())
1400  : success();
1401  })
1402  .Default([&](auto op) { return success(); });
1403 
1404  if (failed(verifyReads))
1405  return group->emitOpError() << "with cell: " << srcCell->getName() << " \""
1406  << srcCell.instanceName()
1407  << "\" is having a read performed upon it, and "
1408  "failed to drive all necessary ports.";
1409 
1410  return success();
1411 }
1412 
1413 LogicalResult calyx::verifyGroupInterface(Operation *op) {
1414  auto group = dyn_cast<GroupInterface>(op);
1415  if (group == nullptr)
1416  return success();
1417 
1418  for (auto &&groupOp : *group.getBody()) {
1419  auto assign = dyn_cast<AssignOp>(groupOp);
1420  if (assign == nullptr)
1421  continue;
1422  if (failed(verifyPrimitivePortDriving(assign, group)))
1423  return failure();
1424  }
1425 
1426  return success();
1427 }
1428 
1429 //===----------------------------------------------------------------------===//
1430 // Utilities for operations with the Cell trait.
1431 //===----------------------------------------------------------------------===//
1432 
1433 /// Gives each result of the cell a meaningful name in the form:
1434 /// <instance-name>.<port-name>
1435 static void getCellAsmResultNames(OpAsmSetValueNameFn setNameFn, Operation *op,
1436  ArrayRef<StringRef> portNames) {
1437  auto cellInterface = dyn_cast<CellInterface>(op);
1438  assert(cellInterface && "must implement the Cell interface");
1439 
1440  std::string prefix = cellInterface.instanceName().str() + ".";
1441  for (size_t i = 0, e = portNames.size(); i != e; ++i)
1442  setNameFn(op->getResult(i), prefix + portNames[i].str());
1443 }
1444 
1445 //===----------------------------------------------------------------------===//
1446 // AssignOp
1447 //===----------------------------------------------------------------------===//
1448 
1449 /// Determines whether the given direction is valid with the given inputs. The
1450 /// `isDestination` boolean is used to distinguish whether the value is a source
1451 /// or a destination.
1452 static LogicalResult verifyPortDirection(Operation *op, Value value,
1453  bool isDestination) {
1454  Operation *definingOp = value.getDefiningOp();
1455  bool isComponentPort = isa<BlockArgument>(value),
1456  isCellInterfacePort = isa_and_nonnull<CellInterface>(definingOp);
1457  assert((isComponentPort || isCellInterfacePort) && "Not a port.");
1458 
1459  PortInfo port = isComponentPort
1460  ? getPortInfo(cast<BlockArgument>(value))
1461  : cast<CellInterface>(definingOp).portInfo(value);
1462 
1463  bool isSource = !isDestination;
1464  // Component output ports and cell interface input ports should be driven.
1465  Direction validDirection =
1466  (isDestination && isComponentPort) || (isSource && isCellInterfacePort)
1468  : Direction::Input;
1469 
1470  return port.direction == validDirection
1471  ? success()
1472  : op->emitOpError()
1473  << "has a " << (isComponentPort ? "component" : "cell")
1474  << " port as the "
1475  << (isDestination ? "destination" : "source")
1476  << " with the incorrect direction.";
1477 }
1478 
1479 /// Verifies the value of a given assignment operation. The boolean
1480 /// `isDestination` is used to distinguish whether the destination
1481 /// or source of the AssignOp is to be verified.
1482 static LogicalResult verifyAssignOpValue(AssignOp op, bool isDestination) {
1483  bool isSource = !isDestination;
1484  Value value = isDestination ? op.getDest() : op.getSrc();
1485  if (isPort(value))
1486  return verifyPortDirection(op, value, isDestination);
1487 
1488  // A destination may also be the Go or Done hole of a GroupOp.
1489  if (isDestination && !isa<GroupGoOp, GroupDoneOp>(value.getDefiningOp()))
1490  return op->emitOpError(
1491  "has an invalid destination port. It must be drive-able.");
1492  else if (isSource)
1493  return verifyNotComplexSource(op);
1494 
1495  return success();
1496 }
1497 
1498 LogicalResult AssignOp::verify() {
1499  bool isDestination = true, isSource = false;
1500  if (failed(verifyAssignOpValue(*this, isDestination)))
1501  return failure();
1502  if (failed(verifyAssignOpValue(*this, isSource)))
1503  return failure();
1504 
1505  return success();
1506 }
1507 
1508 ParseResult AssignOp::parse(OpAsmParser &parser, OperationState &result) {
1509  OpAsmParser::UnresolvedOperand destination;
1510  if (parser.parseOperand(destination) || parser.parseEqual())
1511  return failure();
1512 
1513  // An AssignOp takes one of the two following forms:
1514  // (1) %<dest> = %<src> : <type>
1515  // (2) %<dest> = %<guard> ? %<src> : <type>
1516  OpAsmParser::UnresolvedOperand guardOrSource;
1517  if (parser.parseOperand(guardOrSource))
1518  return failure();
1519 
1520  // Since the guard is optional, we need to check if there is an accompanying
1521  // `?` symbol.
1522  OpAsmParser::UnresolvedOperand source;
1523  bool hasGuard = succeeded(parser.parseOptionalQuestion());
1524  if (hasGuard) {
1525  // The guard exists. Parse the source.
1526  if (parser.parseOperand(source))
1527  return failure();
1528  }
1529 
1530  Type type;
1531  if (parser.parseColonType(type) ||
1532  parser.resolveOperand(destination, type, result.operands))
1533  return failure();
1534 
1535  if (hasGuard) {
1536  Type i1Type = parser.getBuilder().getI1Type();
1537  // Since the guard is optional, it is listed last in the arguments of the
1538  // AssignOp. Therefore, we must parse the source first.
1539  if (parser.resolveOperand(source, type, result.operands) ||
1540  parser.resolveOperand(guardOrSource, i1Type, result.operands))
1541  return failure();
1542  } else {
1543  // This is actually a source.
1544  if (parser.resolveOperand(guardOrSource, type, result.operands))
1545  return failure();
1546  }
1547 
1548  return success();
1549 }
1550 
1551 void AssignOp::print(OpAsmPrinter &p) {
1552  p << " " << getDest() << " = ";
1553 
1554  Value bguard = getGuard(), source = getSrc();
1555  // The guard is optional.
1556  if (bguard)
1557  p << bguard << " ? ";
1558 
1559  // We only need to print a single type; the destination and source are
1560  // guaranteed to be the same type.
1561  p << source << " : " << source.getType();
1562 }
1563 
1564 //===----------------------------------------------------------------------===//
1565 // InstanceOp
1566 //===----------------------------------------------------------------------===//
1567 
1568 /// Lookup the component for the symbol. This returns null on
1569 /// invalid IR.
1570 ComponentInterface InstanceOp::getReferencedComponent() {
1571  auto module = (*this)->getParentOfType<ModuleOp>();
1572  if (!module)
1573  return nullptr;
1574 
1575  return module.lookupSymbol<ComponentInterface>(getComponentName());
1576 }
1577 
1578 /// Verifies the port information in comparison with the referenced component
1579 /// of an instance. This helper function avoids conducting a lookup for the
1580 /// referenced component twice.
1581 static LogicalResult
1582 verifyInstanceOpType(InstanceOp instance,
1583  ComponentInterface referencedComponent) {
1584  auto module = instance->getParentOfType<ModuleOp>();
1585  StringRef entryPointName =
1586  module->getAttrOfType<StringAttr>("calyx.entrypoint");
1587  if (instance.getComponentName() == entryPointName)
1588  return instance.emitOpError()
1589  << "cannot reference the entry-point component: '" << entryPointName
1590  << "'.";
1591 
1592  // Verify the instance result ports with those of its referenced component.
1593  SmallVector<PortInfo> componentPorts = referencedComponent.getPortInfo();
1594  size_t numPorts = componentPorts.size();
1595 
1596  size_t numResults = instance.getNumResults();
1597  if (numResults != numPorts)
1598  return instance.emitOpError()
1599  << "has a wrong number of results; expected: " << numPorts
1600  << " but got " << numResults;
1601 
1602  for (size_t i = 0; i != numResults; ++i) {
1603  auto resultType = instance.getResult(i).getType();
1604  auto expectedType = componentPorts[i].type;
1605  if (resultType == expectedType)
1606  continue;
1607  return instance.emitOpError()
1608  << "result type for " << componentPorts[i].name << " must be "
1609  << expectedType << ", but got " << resultType;
1610  }
1611  return success();
1612 }
1613 
1614 LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1615  Operation *op = *this;
1616  auto module = op->getParentOfType<ModuleOp>();
1617  Operation *referencedComponent =
1618  symbolTable.lookupNearestSymbolFrom(module, getComponentNameAttr());
1619  if (referencedComponent == nullptr)
1620  return emitError() << "referencing component: '" << getComponentName()
1621  << "', which does not exist.";
1622 
1623  Operation *shadowedComponentName =
1624  symbolTable.lookupNearestSymbolFrom(module, getSymNameAttr());
1625  if (shadowedComponentName != nullptr)
1626  return emitError() << "instance symbol: '" << instanceName()
1627  << "' is already a symbol for another component.";
1628 
1629  // Verify the referenced component is not instantiating itself.
1630  auto parentComponent = op->getParentOfType<ComponentOp>();
1631  if (parentComponent == referencedComponent)
1632  return emitError() << "recursive instantiation of its parent component: '"
1633  << getComponentName() << "'";
1634 
1635  assert(isa<ComponentInterface>(referencedComponent) &&
1636  "Should be a ComponentInterface.");
1637  return verifyInstanceOpType(*this,
1638  cast<ComponentInterface>(referencedComponent));
1639 }
1640 
1641 /// Provide meaningful names to the result values of an InstanceOp.
1643  getCellAsmResultNames(setNameFn, *this, this->portNames());
1644 }
1645 
1646 SmallVector<StringRef> InstanceOp::portNames() {
1647  SmallVector<StringRef> portNames;
1648  for (Attribute name : getReferencedComponent().getPortNames())
1649  portNames.push_back(cast<StringAttr>(name).getValue());
1650  return portNames;
1651 }
1652 
1653 SmallVector<Direction> InstanceOp::portDirections() {
1654  SmallVector<Direction> portDirections;
1655  for (const PortInfo &port : getReferencedComponent().getPortInfo())
1656  portDirections.push_back(port.direction);
1657  return portDirections;
1658 }
1659 
1660 SmallVector<DictionaryAttr> InstanceOp::portAttributes() {
1661  SmallVector<DictionaryAttr> portAttributes;
1662  for (const PortInfo &port : getReferencedComponent().getPortInfo())
1663  portAttributes.push_back(port.attributes);
1664  return portAttributes;
1665 }
1666 
1668  return isa<CombComponentOp>(getReferencedComponent());
1669 }
1670 
1671 //===----------------------------------------------------------------------===//
1672 // PrimitiveOp
1673 //===----------------------------------------------------------------------===//
1674 
1675 /// Lookup the component for the symbol. This returns null on
1676 /// invalid IR.
1677 hw::HWModuleExternOp PrimitiveOp::getReferencedPrimitive() {
1678  auto module = (*this)->getParentOfType<ModuleOp>();
1679  if (!module)
1680  return nullptr;
1681 
1682  return module.lookupSymbol<hw::HWModuleExternOp>(getPrimitiveName());
1683 }
1684 
1685 /// Verifies the port information in comparison with the referenced component
1686 /// of an instance. This helper function avoids conducting a lookup for the
1687 /// referenced component twice.
1688 static LogicalResult
1689 verifyPrimitiveOpType(PrimitiveOp instance,
1690  hw::HWModuleExternOp referencedPrimitive) {
1691  auto module = instance->getParentOfType<ModuleOp>();
1692  StringRef entryPointName =
1693  module->getAttrOfType<StringAttr>("calyx.entrypoint");
1694  if (instance.getPrimitiveName() == entryPointName)
1695  return instance.emitOpError()
1696  << "cannot reference the entry-point component: '" << entryPointName
1697  << "'.";
1698 
1699  // Verify the instance result ports with those of its referenced component.
1700  auto primitivePorts = referencedPrimitive.getPortList();
1701  size_t numPorts = primitivePorts.size();
1702 
1703  size_t numResults = instance.getNumResults();
1704  if (numResults != numPorts)
1705  return instance.emitOpError()
1706  << "has a wrong number of results; expected: " << numPorts
1707  << " but got " << numResults;
1708 
1709  // Verify parameters match up
1710  ArrayAttr modParameters = referencedPrimitive.getParameters();
1711  ArrayAttr parameters = instance.getParameters().value_or(ArrayAttr());
1712  size_t numExpected = modParameters.size();
1713  size_t numParams = parameters.size();
1714  if (numParams != numExpected)
1715  return instance.emitOpError()
1716  << "has the wrong number of parameters; expected: " << numExpected
1717  << " but got " << numParams;
1718 
1719  for (size_t i = 0; i != numExpected; ++i) {
1720  auto param = cast<circt::hw::ParamDeclAttr>(parameters[i]);
1721  auto modParam = cast<circt::hw::ParamDeclAttr>(modParameters[i]);
1722 
1723  auto paramName = param.getName();
1724  if (paramName != modParam.getName())
1725  return instance.emitOpError()
1726  << "parameter #" << i << " should have name " << modParam.getName()
1727  << " but has name " << paramName;
1728 
1729  if (param.getType() != modParam.getType())
1730  return instance.emitOpError()
1731  << "parameter " << paramName << " should have type "
1732  << modParam.getType() << " but has type " << param.getType();
1733 
1734  // All instance parameters must have a value. Specify the same value as
1735  // a module's default value if you want the default.
1736  if (!param.getValue())
1737  return instance.emitOpError("parameter ")
1738  << paramName << " must have a value";
1739  }
1740 
1741  for (size_t i = 0; i != numResults; ++i) {
1742  auto resultType = instance.getResult(i).getType();
1743  auto expectedType = primitivePorts[i].type;
1744  auto replacedType = hw::evaluateParametricType(
1745  instance.getLoc(), instance.getParametersAttr(), expectedType);
1746  if (failed(replacedType))
1747  return failure();
1748  if (resultType == replacedType)
1749  continue;
1750  return instance.emitOpError()
1751  << "result type for " << primitivePorts[i].name << " must be "
1752  << expectedType << ", but got " << resultType;
1753  }
1754  return success();
1755 }
1756 
1757 LogicalResult
1758 PrimitiveOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1759  Operation *op = *this;
1760  auto module = op->getParentOfType<ModuleOp>();
1761  Operation *referencedPrimitive =
1762  symbolTable.lookupNearestSymbolFrom(module, getPrimitiveNameAttr());
1763  if (referencedPrimitive == nullptr)
1764  return emitError() << "referencing primitive: '" << getPrimitiveName()
1765  << "', which does not exist.";
1766 
1767  Operation *shadowedPrimitiveName =
1768  symbolTable.lookupNearestSymbolFrom(module, getSymNameAttr());
1769  if (shadowedPrimitiveName != nullptr)
1770  return emitError() << "instance symbol: '" << instanceName()
1771  << "' is already a symbol for another primitive.";
1772 
1773  // Verify the referenced primitive is not instantiating itself.
1774  auto parentPrimitive = op->getParentOfType<hw::HWModuleExternOp>();
1775  if (parentPrimitive == referencedPrimitive)
1776  return emitError() << "recursive instantiation of its parent primitive: '"
1777  << getPrimitiveName() << "'";
1778 
1779  assert(isa<hw::HWModuleExternOp>(referencedPrimitive) &&
1780  "Should be a HardwareModuleExternOp.");
1781 
1782  return verifyPrimitiveOpType(*this,
1783  cast<hw::HWModuleExternOp>(referencedPrimitive));
1784 }
1785 
1786 /// Provide meaningful names to the result values of an PrimitiveOp.
1788  getCellAsmResultNames(setNameFn, *this, this->portNames());
1789 }
1790 
1791 SmallVector<StringRef> PrimitiveOp::portNames() {
1792  SmallVector<StringRef> portNames;
1793  auto ports = getReferencedPrimitive().getPortList();
1794  for (auto port : ports)
1795  portNames.push_back(port.name.getValue());
1796 
1797  return portNames;
1798 }
1799 
1801  switch (direction) {
1803  return Direction::Input;
1805  return Direction::Output;
1807  llvm_unreachable("InOut ports not supported by Calyx");
1808  }
1809  llvm_unreachable("Impossible port type");
1810 }
1811 
1812 SmallVector<Direction> PrimitiveOp::portDirections() {
1813  SmallVector<Direction> portDirections;
1814  auto ports = getReferencedPrimitive().getPortList();
1815  for (hw::PortInfo port : ports)
1816  portDirections.push_back(convertHWDirectionToCalyx(port.dir));
1817  return portDirections;
1818 }
1819 
1820 bool PrimitiveOp::isCombinational() { return false; }
1821 
1822 /// Returns a new DictionaryAttr containing only the calyx dialect attrs
1823 /// in the input DictionaryAttr. Also strips the 'calyx.' prefix from these
1824 /// attrs.
1825 static DictionaryAttr cleanCalyxPortAttrs(OpBuilder builder,
1826  DictionaryAttr dict) {
1827  if (!dict) {
1828  return dict;
1829  }
1830  llvm::SmallVector<NamedAttribute> attrs;
1831  for (NamedAttribute attr : dict) {
1832  Dialect *dialect = attr.getNameDialect();
1833  if (dialect == nullptr || !isa<CalyxDialect>(*dialect))
1834  continue;
1835  StringRef name = attr.getName().strref();
1836  StringAttr newName = builder.getStringAttr(std::get<1>(name.split(".")));
1837  attr.setName(newName);
1838  attrs.push_back(attr);
1839  }
1840  return builder.getDictionaryAttr(attrs);
1841 }
1842 
1843 // Grabs calyx port attributes from the HWModuleExternOp arg/result attributes.
1844 SmallVector<DictionaryAttr> PrimitiveOp::portAttributes() {
1845  SmallVector<DictionaryAttr> portAttributes;
1846  OpBuilder builder(getContext());
1847  hw::HWModuleExternOp prim = getReferencedPrimitive();
1848  auto argAttrs = prim.getAllInputAttrs();
1849  auto resAttrs = prim.getAllOutputAttrs();
1850  for (auto a : argAttrs)
1851  portAttributes.push_back(
1852  cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1853  for (auto a : resAttrs)
1854  portAttributes.push_back(
1855  cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1856  return portAttributes;
1857 }
1858 
1859 /// Parse an parameter list if present. Same format as HW dialect.
1860 /// module-parameter-list ::= `<` parameter-decl (`,` parameter-decl)* `>`
1861 /// parameter-decl ::= identifier `:` type
1862 /// parameter-decl ::= identifier `:` type `=` attribute
1863 ///
1864 static ParseResult parseParameterList(OpAsmParser &parser,
1865  SmallVector<Attribute> &parameters) {
1866 
1867  return parser.parseCommaSeparatedList(
1868  OpAsmParser::Delimiter::OptionalLessGreater, [&]() {
1869  std::string name;
1870  Type type;
1871  Attribute value;
1872 
1873  if (parser.parseKeywordOrString(&name) || parser.parseColonType(type))
1874  return failure();
1875 
1876  // Parse the default value if present.
1877  if (succeeded(parser.parseOptionalEqual())) {
1878  if (parser.parseAttribute(value, type))
1879  return failure();
1880  }
1881 
1882  auto &builder = parser.getBuilder();
1883  parameters.push_back(hw::ParamDeclAttr::get(
1884  builder.getContext(), builder.getStringAttr(name), type, value));
1885  return success();
1886  });
1887 }
1888 
1889 /// Shim to also use this for the InstanceOp custom parser.
1890 static ParseResult parseParameterList(OpAsmParser &parser,
1891  ArrayAttr &parameters) {
1892  SmallVector<Attribute> parseParameters;
1893  if (failed(parseParameterList(parser, parseParameters)))
1894  return failure();
1895 
1896  parameters = ArrayAttr::get(parser.getContext(), parseParameters);
1897 
1898  return success();
1899 }
1900 
1901 /// Print a parameter list for a module or instance. Same format as HW dialect.
1902 static void printParameterList(OpAsmPrinter &p, Operation *op,
1903  ArrayAttr parameters) {
1904  if (parameters.empty())
1905  return;
1906 
1907  p << '<';
1908  llvm::interleaveComma(parameters, p, [&](Attribute param) {
1909  auto paramAttr = cast<hw::ParamDeclAttr>(param);
1910  p << paramAttr.getName().getValue() << ": " << paramAttr.getType();
1911  if (auto value = paramAttr.getValue()) {
1912  p << " = ";
1913  p.printAttributeWithoutType(value);
1914  }
1915  });
1916  p << '>';
1917 }
1918 
1919 //===----------------------------------------------------------------------===//
1920 // GroupGoOp
1921 //===----------------------------------------------------------------------===//
1922 
1923 LogicalResult GroupGoOp::verify() { return verifyNotComplexSource(*this); }
1924 
1925 /// Provide meaningful names to the result value of a GroupGoOp.
1926 void GroupGoOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
1927  auto parent = (*this)->getParentOfType<GroupOp>();
1928  StringRef name = parent.getSymName();
1929  std::string resultName = name.str() + ".go";
1930  setNameFn(getResult(), resultName);
1931 }
1932 
1933 void GroupGoOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
1934 
1935 ParseResult GroupGoOp::parse(OpAsmParser &parser, OperationState &result) {
1936  if (parseGroupPort(parser, result))
1937  return failure();
1938 
1939  result.addTypes(parser.getBuilder().getI1Type());
1940  return success();
1941 }
1942 
1943 //===----------------------------------------------------------------------===//
1944 // GroupDoneOp
1945 //===----------------------------------------------------------------------===//
1946 
1947 LogicalResult GroupDoneOp::verify() {
1948  Operation *srcOp = getSrc().getDefiningOp();
1949  Value optionalGuard = getGuard();
1950  Operation *guardOp = optionalGuard ? optionalGuard.getDefiningOp() : nullptr;
1951  bool noGuard = (guardOp == nullptr);
1952 
1953  if (srcOp == nullptr)
1954  // This is a port of the parent component.
1955  return success();
1956 
1957  if (isa<hw::ConstantOp>(srcOp) && (noGuard || isa<hw::ConstantOp>(guardOp)))
1958  return emitOpError() << "with constant source"
1959  << (noGuard ? "" : " and constant guard")
1960  << ". This should be a combinational group.";
1961 
1962  return verifyNotComplexSource(*this);
1963 }
1964 
1965 void GroupDoneOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
1966 
1967 ParseResult GroupDoneOp::parse(OpAsmParser &parser, OperationState &result) {
1968  return parseGroupPort(parser, result);
1969 }
1970 
1971 //===----------------------------------------------------------------------===//
1972 // ConstantOp
1973 //===----------------------------------------------------------------------===//
1974 void ConstantOp::getAsmResultNames(
1975  function_ref<void(Value, StringRef)> setNameFn) {
1976  if (isa<FloatAttr>(getValue())) {
1977  setNameFn(getResult(), "cst");
1978  return;
1979  }
1980  auto intCst = llvm::dyn_cast<IntegerAttr>(getValue());
1981  auto intType = llvm::dyn_cast<IntegerType>(getType());
1982 
1983  // Sugar i1 constants with 'true' and 'false'.
1984  if (intType && intType.getWidth() == 1)
1985  return setNameFn(getResult(), intCst.getInt() > 0 ? "true" : "false");
1986 
1987  // Otherwise, build a complex name with the value and type.
1988  SmallString<32> specialNameBuffer;
1989  llvm::raw_svector_ostream specialName(specialNameBuffer);
1990  specialName << 'c' << intCst.getValue();
1991  if (intType)
1992  specialName << '_' << getType();
1993  setNameFn(getResult(), specialName.str());
1994 }
1995 
1996 LogicalResult ConstantOp::verify() {
1997  auto type = getType();
1998  assert(isa<IntegerType>(type) && "must be an IntegerType");
1999  // The value's bit width must match the return type bitwidth.
2000  if (auto valTyBitWidth = getValue().getType().getIntOrFloatBitWidth();
2001  valTyBitWidth != type.getIntOrFloatBitWidth()) {
2002  return emitOpError() << "value type bit width" << valTyBitWidth
2003  << " must match return type: "
2004  << type.getIntOrFloatBitWidth();
2005  }
2006  // Integer values must be signless.
2007  if (llvm::isa<IntegerType>(type) &&
2008  !llvm::cast<IntegerType>(type).isSignless())
2009  return emitOpError("integer return type must be signless");
2010  // Any float or integers attribute are acceptable.
2011  if (!llvm::isa<IntegerAttr, FloatAttr>(getValue())) {
2012  return emitOpError("value must be an integer or float attribute");
2013  }
2014 
2015  return success();
2016 }
2017 
2018 OpFoldResult calyx::ConstantOp::fold(FoldAdaptor adaptor) {
2019  return getValueAttr();
2020 }
2021 
2022 void calyx::ConstantOp::build(OpBuilder &builder, OperationState &state,
2023  StringRef symName, Attribute attr, Type type) {
2024  state.addAttribute(SymbolTable::getSymbolAttrName(),
2025  builder.getStringAttr(symName));
2026  state.addAttribute("value", attr);
2027  SmallVector<Type> types;
2028  types.push_back(type); // Out
2029  state.addTypes(types);
2030 }
2031 
2032 SmallVector<StringRef> ConstantOp::portNames() { return {"out"}; }
2033 
2034 SmallVector<Direction> ConstantOp::portDirections() { return {Output}; }
2035 
2036 SmallVector<DictionaryAttr> ConstantOp::portAttributes() {
2037  return {DictionaryAttr::get(getContext())};
2038 }
2039 
2040 bool ConstantOp::isCombinational() { return true; }
2041 
2042 //===----------------------------------------------------------------------===//
2043 // RegisterOp
2044 //===----------------------------------------------------------------------===//
2045 
2046 /// Provide meaningful names to the result values of a RegisterOp.
2048  getCellAsmResultNames(setNameFn, *this, this->portNames());
2049 }
2050 
2051 SmallVector<StringRef> RegisterOp::portNames() {
2052  return {"in", "write_en", clkPort, resetPort, "out", donePort};
2053 }
2054 
2055 SmallVector<Direction> RegisterOp::portDirections() {
2056  return {Input, Input, Input, Input, Output, Output};
2057 }
2058 
2059 SmallVector<DictionaryAttr> RegisterOp::portAttributes() {
2060  MLIRContext *context = getContext();
2061  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2062  NamedAttrList writeEn, clk, reset, done;
2063  writeEn.append(goPort, isSet);
2064  clk.append(clkPort, isSet);
2065  reset.append(resetPort, isSet);
2066  done.append(donePort, isSet);
2067  return {
2068  DictionaryAttr::get(context), // In
2069  writeEn.getDictionary(context), // Write enable
2070  clk.getDictionary(context), // Clk
2071  reset.getDictionary(context), // Reset
2072  DictionaryAttr::get(context), // Out
2073  done.getDictionary(context) // Done
2074  };
2075 }
2076 
2077 bool RegisterOp::isCombinational() { return false; }
2078 
2079 //===----------------------------------------------------------------------===//
2080 // MemoryOp
2081 //===----------------------------------------------------------------------===//
2082 
2083 /// Provide meaningful names to the result values of a MemoryOp.
2085  getCellAsmResultNames(setNameFn, *this, this->portNames());
2086 }
2087 
2088 SmallVector<StringRef> MemoryOp::portNames() {
2089  SmallVector<StringRef> portNames;
2090  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2091  auto nameAttr =
2092  StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2093  portNames.push_back(nameAttr.getValue());
2094  }
2095  portNames.append({"write_data", "write_en", clkPort, "read_data", donePort});
2096  return portNames;
2097 }
2098 
2099 SmallVector<Direction> MemoryOp::portDirections() {
2100  SmallVector<Direction> portDirections;
2101  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2102  portDirections.push_back(Input);
2103  portDirections.append({Input, Input, Input, Output, Output});
2104  return portDirections;
2105 }
2106 
2107 SmallVector<DictionaryAttr> MemoryOp::portAttributes() {
2108  SmallVector<DictionaryAttr> portAttributes;
2109  MLIRContext *context = getContext();
2110  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2111  portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2112 
2113  // Use a boolean to indicate this attribute is used.
2114  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2115  NamedAttrList writeEn, clk, reset, done;
2116  writeEn.append(goPort, isSet);
2117  clk.append(clkPort, isSet);
2118  done.append(donePort, isSet);
2119  portAttributes.append({DictionaryAttr::get(context), // In
2120  writeEn.getDictionary(context), // Write enable
2121  clk.getDictionary(context), // Clk
2122  DictionaryAttr::get(context), // Out
2123  done.getDictionary(context)} // Done
2124  );
2125  return portAttributes;
2126 }
2127 
2128 void MemoryOp::build(OpBuilder &builder, OperationState &state,
2129  StringRef instanceName, int64_t width,
2130  ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2131  state.addAttribute(SymbolTable::getSymbolAttrName(),
2132  builder.getStringAttr(instanceName));
2133  state.addAttribute("width", builder.getI64IntegerAttr(width));
2134  state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2135  state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2136  SmallVector<Type> types;
2137  for (int64_t size : addrSizes)
2138  types.push_back(builder.getIntegerType(size)); // Addresses
2139  types.push_back(builder.getIntegerType(width)); // Write data
2140  types.push_back(builder.getI1Type()); // Write enable
2141  types.push_back(builder.getI1Type()); // Clk
2142  types.push_back(builder.getIntegerType(width)); // Read data
2143  types.push_back(builder.getI1Type()); // Done
2144  state.addTypes(types);
2145 }
2146 
2147 LogicalResult MemoryOp::verify() {
2148  ArrayRef<Attribute> opSizes = getSizes().getValue();
2149  ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2150  size_t numDims = getSizes().size();
2151  size_t numAddrs = getAddrSizes().size();
2152  if (numDims != numAddrs)
2153  return emitOpError("mismatched number of dimensions (")
2154  << numDims << ") and address sizes (" << numAddrs << ")";
2155 
2156  size_t numExtraPorts = 5; // write data/enable, clk, and read data/done.
2157  if (getNumResults() != numAddrs + numExtraPorts)
2158  return emitOpError("incorrect number of address ports, expected ")
2159  << numAddrs;
2160 
2161  for (size_t i = 0; i < numDims; ++i) {
2162  int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2163  int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2164  if (llvm::Log2_64_Ceil(size) > addrSize)
2165  return emitOpError("address size (")
2166  << addrSize << ") for dimension " << i
2167  << " can't address the entire range (" << size << ")";
2168  }
2169 
2170  return success();
2171 }
2172 
2173 //===----------------------------------------------------------------------===//
2174 // SeqMemoryOp
2175 //===----------------------------------------------------------------------===//
2176 
2177 /// Provide meaningful names to the result values of a SeqMemoryOp.
2179  getCellAsmResultNames(setNameFn, *this, this->portNames());
2180 }
2181 
2182 SmallVector<StringRef> SeqMemoryOp::portNames() {
2183  SmallVector<StringRef> portNames;
2184  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2185  auto nameAttr =
2186  StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2187  portNames.push_back(nameAttr.getValue());
2188  }
2189  portNames.append({clkPort, "reset", "content_en", "write_en", "write_data",
2190  "read_data", "done"});
2191  return portNames;
2192 }
2193 
2194 SmallVector<Direction> SeqMemoryOp::portDirections() {
2195  SmallVector<Direction> portDirections;
2196  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2197  portDirections.push_back(Input);
2198  portDirections.append({Input, Input, Input, Input, Input, Output, Output});
2199  return portDirections;
2200 }
2201 
2202 SmallVector<DictionaryAttr> SeqMemoryOp::portAttributes() {
2203  SmallVector<DictionaryAttr> portAttributes;
2204  MLIRContext *context = getContext();
2205  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2206  portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2207 
2208  OpBuilder builder(context);
2209  // Use a boolean to indicate this attribute is used.
2210  IntegerAttr isSet = IntegerAttr::get(builder.getIndexType(), 1);
2211  IntegerAttr isTwo = IntegerAttr::get(builder.getIndexType(), 2);
2212  NamedAttrList done, clk, reset, contentEn;
2213  done.append(donePort, isSet);
2214  clk.append(clkPort, isSet);
2215  clk.append(resetPort, isSet);
2216  contentEn.append(goPort, isTwo);
2217  portAttributes.append({clk.getDictionary(context), // Clk
2218  reset.getDictionary(context), // Reset
2219  contentEn.getDictionary(context), // Content enable
2220  DictionaryAttr::get(context), // Write enable
2221  DictionaryAttr::get(context), // Write data
2222  DictionaryAttr::get(context), // Read data
2223  done.getDictionary(context)} // Done
2224  );
2225  return portAttributes;
2226 }
2227 
2228 void SeqMemoryOp::build(OpBuilder &builder, OperationState &state,
2229  StringRef instanceName, int64_t width,
2230  ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2231  state.addAttribute(SymbolTable::getSymbolAttrName(),
2232  builder.getStringAttr(instanceName));
2233  state.addAttribute("width", builder.getI64IntegerAttr(width));
2234  state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2235  state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2236  SmallVector<Type> types;
2237  for (int64_t size : addrSizes)
2238  types.push_back(builder.getIntegerType(size)); // Addresses
2239  types.push_back(builder.getI1Type()); // Clk
2240  types.push_back(builder.getI1Type()); // Reset
2241  types.push_back(builder.getI1Type()); // Content enable
2242  types.push_back(builder.getI1Type()); // Write enable
2243  types.push_back(builder.getIntegerType(width)); // Write data
2244  types.push_back(builder.getIntegerType(width)); // Read data
2245  types.push_back(builder.getI1Type()); // Done
2246  state.addTypes(types);
2247 }
2248 
2249 LogicalResult SeqMemoryOp::verify() {
2250  ArrayRef<Attribute> opSizes = getSizes().getValue();
2251  ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2252  size_t numDims = getSizes().size();
2253  size_t numAddrs = getAddrSizes().size();
2254  if (numDims != numAddrs)
2255  return emitOpError("mismatched number of dimensions (")
2256  << numDims << ") and address sizes (" << numAddrs << ")";
2257 
2258  size_t numExtraPorts =
2259  7; // write data/enable, clk, reset, read data, content enable, and done.
2260  if (getNumResults() != numAddrs + numExtraPorts)
2261  return emitOpError("incorrect number of address ports, expected ")
2262  << numAddrs;
2263 
2264  for (size_t i = 0; i < numDims; ++i) {
2265  int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2266  int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2267  if (llvm::Log2_64_Ceil(size) > addrSize)
2268  return emitOpError("address size (")
2269  << addrSize << ") for dimension " << i
2270  << " can't address the entire range (" << size << ")";
2271  }
2272 
2273  return success();
2274 }
2275 
2276 //===----------------------------------------------------------------------===//
2277 // EnableOp
2278 //===----------------------------------------------------------------------===//
2279 LogicalResult EnableOp::verify() {
2280  auto component = (*this)->getParentOfType<ComponentOp>();
2281  auto wiresOp = component.getWiresOp();
2282  StringRef name = getGroupName();
2283 
2284  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(name);
2285  if (!groupOp)
2286  return emitOpError() << "with group '" << name
2287  << "', which does not exist.";
2288 
2289  if (isa<CombGroupOp>(groupOp))
2290  return emitOpError() << "with group '" << name
2291  << "', which is a combinational group.";
2292 
2293  return success();
2294 }
2295 
2296 //===----------------------------------------------------------------------===//
2297 // IfOp
2298 //===----------------------------------------------------------------------===//
2299 
2300 LogicalResult IfOp::verify() {
2301  std::optional<StringRef> optGroupName = getGroupName();
2302  if (!optGroupName) {
2303  // No combinational group was provided.
2304  return success();
2305  }
2306  auto component = (*this)->getParentOfType<ComponentOp>();
2307  WiresOp wiresOp = component.getWiresOp();
2308  StringRef groupName = *optGroupName;
2309  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2310  if (!groupOp)
2311  return emitOpError() << "with group '" << groupName
2312  << "', which does not exist.";
2313 
2314  if (isa<GroupOp>(groupOp))
2315  return emitOpError() << "with group '" << groupName
2316  << "', which is not a combinational group.";
2317 
2318  if (failed(groupOp.drivesPort(getCond())))
2319  return emitError() << "with conditional op: '"
2320  << valueName(component, getCond())
2321  << "' expected to be driven from group: '" << groupName
2322  << "' but no driver was found.";
2323 
2324  return success();
2325 }
2326 
2327 /// Returns the last EnableOp within the child tree of 'parentSeqOp' or
2328 /// `parentStaticSeqOp.` If no EnableOp was found (e.g. a "calyx.par" operation
2329 /// is present), returns None.
2330 template <typename OpTy>
2331 static std::optional<EnableOp> getLastEnableOp(OpTy parent) {
2332  static_assert(IsAny<OpTy, SeqOp, StaticSeqOp>(),
2333  "Should be a StaticSeqOp or SeqOp.");
2334  if (parent.getBodyBlock()->empty())
2335  return std::nullopt;
2336  auto &lastOp = parent.getBodyBlock()->back();
2337  if (auto enableOp = dyn_cast<EnableOp>(lastOp))
2338  return enableOp;
2339  if (auto seqOp = dyn_cast<SeqOp>(lastOp))
2340  return getLastEnableOp(seqOp);
2341  if (auto staticSeqOp = dyn_cast<StaticSeqOp>(lastOp))
2342  return getLastEnableOp(staticSeqOp);
2343 
2344  return std::nullopt;
2345 }
2346 
2347 /// Returns a mapping of {enabled Group name, EnableOp} for all EnableOps within
2348 /// the immediate ParOp's body.
2349 template <typename OpTy>
2350 static llvm::StringMap<EnableOp> getAllEnableOpsInImmediateBody(OpTy parent) {
2351  static_assert(IsAny<OpTy, ParOp, StaticParOp>(),
2352  "Should be a StaticParOp or ParOp.");
2353 
2354  llvm::StringMap<EnableOp> enables;
2355  Block *body = parent.getBodyBlock();
2356  for (EnableOp op : body->getOps<EnableOp>())
2357  enables.insert(std::pair(op.getGroupName(), op));
2358 
2359  return enables;
2360 }
2361 
2362 /// Checks preconditions for the common tail pattern. This canonicalization is
2363 /// stringent about not entering nested control operations, as this may cause
2364 /// unintentional changes in behavior.
2365 /// We only look for two cases: (1) both regions are ParOps, and
2366 /// (2) both regions are SeqOps. The case when these are different, e.g. ParOp
2367 /// and SeqOp, will only produce less optimal code, or even worse, change the
2368 /// behavior.
2369 template <typename IfOpTy, typename TailOpTy>
2370 static bool hasCommonTailPatternPreConditions(IfOpTy op) {
2371  static_assert(IsAny<TailOpTy, SeqOp, ParOp, StaticSeqOp, StaticParOp>(),
2372  "Should be a SeqOp, ParOp, StaticSeqOp, or StaticParOp.");
2373  static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2374  "Should be a IfOp or StaticIfOp.");
2375 
2376  if (!op.thenBodyExists() || !op.elseBodyExists())
2377  return false;
2378  if (op.getThenBody()->empty() || op.getElseBody()->empty())
2379  return false;
2380 
2381  Block *thenBody = op.getThenBody(), *elseBody = op.getElseBody();
2382  return isa<TailOpTy>(thenBody->front()) && isa<TailOpTy>(elseBody->front());
2383 }
2384 
2385 /// seq {
2386 /// if %a with @G { if %a with @G {
2387 /// seq { ... calyx.enable @A } seq { ... }
2388 /// else { -> } else {
2389 /// seq { ... calyx.enable @A } seq { ... }
2390 /// } }
2391 /// calyx.enable @A
2392 /// }
2393 template <typename IfOpTy, typename SeqOpTy>
2394 static LogicalResult commonTailPatternWithSeq(IfOpTy ifOp,
2395  PatternRewriter &rewriter) {
2396  static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2397  "Should be an IfOp or StaticIfOp.");
2398  static_assert(IsAny<SeqOpTy, SeqOp, StaticSeqOp>(),
2399  "Branches should be checking for an SeqOp or StaticSeqOp");
2400  if (!hasCommonTailPatternPreConditions<IfOpTy, SeqOpTy>(ifOp))
2401  return failure();
2402  auto thenControl = cast<SeqOpTy>(ifOp.getThenBody()->front()),
2403  elseControl = cast<SeqOpTy>(ifOp.getElseBody()->front());
2404 
2405  std::optional<EnableOp> lastThenEnableOp = getLastEnableOp(thenControl),
2406  lastElseEnableOp = getLastEnableOp(elseControl);
2407 
2408  if (!lastThenEnableOp || !lastElseEnableOp)
2409  return failure();
2410  if (lastThenEnableOp->getGroupName() != lastElseEnableOp->getGroupName())
2411  return failure();
2412 
2413  // Place the IfOp and pulled EnableOp inside a sequential region, in case
2414  // this IfOp is nested in a ParOp. This avoids unintentionally
2415  // parallelizing the pulled out EnableOps.
2416  rewriter.setInsertionPointAfter(ifOp);
2417  SeqOpTy seqOp = rewriter.create<SeqOpTy>(ifOp.getLoc());
2418  Block *body = seqOp.getBodyBlock();
2419  ifOp->remove();
2420  body->push_back(ifOp);
2421  rewriter.setInsertionPointToEnd(body);
2422  rewriter.create<EnableOp>(seqOp.getLoc(), lastThenEnableOp->getGroupName());
2423 
2424  // Erase the common EnableOp from the Then and Else regions.
2425  rewriter.eraseOp(*lastThenEnableOp);
2426  rewriter.eraseOp(*lastElseEnableOp);
2427  return success();
2428 }
2429 
2430 /// if %a with @G { par {
2431 /// par { if %a with @G {
2432 /// ... par { ... }
2433 /// calyx.enable @A } else {
2434 /// calyx.enable @B -> par { ... }
2435 /// } }
2436 /// } else { calyx.enable @A
2437 /// par { calyx.enable @B
2438 /// ... }
2439 /// calyx.enable @A
2440 /// calyx.enable @B
2441 /// }
2442 /// }
2443 template <typename OpTy, typename ParOpTy>
2444 static LogicalResult commonTailPatternWithPar(OpTy controlOp,
2445  PatternRewriter &rewriter) {
2446  static_assert(IsAny<OpTy, IfOp, StaticIfOp>(),
2447  "Should be an IfOp or StaticIfOp.");
2448  static_assert(IsAny<ParOpTy, ParOp, StaticParOp>(),
2449  "Branches should be checking for an ParOp or StaticParOp");
2450  if (!hasCommonTailPatternPreConditions<OpTy, ParOpTy>(controlOp))
2451  return failure();
2452  auto thenControl = cast<ParOpTy>(controlOp.getThenBody()->front()),
2453  elseControl = cast<ParOpTy>(controlOp.getElseBody()->front());
2454 
2455  llvm::StringMap<EnableOp> a = getAllEnableOpsInImmediateBody(thenControl),
2456  b = getAllEnableOpsInImmediateBody(elseControl);
2457  // Compute the intersection between `A` and `B`.
2458  SmallVector<StringRef> groupNames;
2459  for (auto aIndex = a.begin(); aIndex != a.end(); ++aIndex) {
2460  StringRef groupName = aIndex->getKey();
2461  auto bIndex = b.find(groupName);
2462  if (bIndex == b.end())
2463  continue;
2464  // This is also an element in B.
2465  groupNames.push_back(groupName);
2466  // Since these are being pulled out, erase them.
2467  rewriter.eraseOp(aIndex->getValue());
2468  rewriter.eraseOp(bIndex->getValue());
2469  }
2470 
2471  // Place the IfOp and EnableOp(s) inside a parallel region, in case this
2472  // IfOp is nested in a SeqOp. This avoids unintentionally sequentializing
2473  // the pulled out EnableOps.
2474  rewriter.setInsertionPointAfter(controlOp);
2475 
2476  ParOpTy parOp = rewriter.create<ParOpTy>(controlOp.getLoc());
2477  Block *body = parOp.getBodyBlock();
2478  controlOp->remove();
2479  body->push_back(controlOp);
2480  // Pull out the intersection between these two sets, and erase their
2481  // counterparts in the Then and Else regions.
2482  rewriter.setInsertionPointToEnd(body);
2483  for (StringRef groupName : groupNames)
2484  rewriter.create<EnableOp>(parOp.getLoc(), groupName);
2485 
2486  return success();
2487 }
2488 
2489 /// This pattern checks for one of two cases that will lead to IfOp deletion:
2490 /// (1) Then and Else bodies are both empty.
2491 /// (2) Then body is empty and Else body does not exist.
2494  LogicalResult matchAndRewrite(IfOp ifOp,
2495  PatternRewriter &rewriter) const override {
2496  if (!ifOp.getThenBody()->empty())
2497  return failure();
2498  if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2499  return failure();
2500 
2501  eraseControlWithGroupAndConditional(ifOp, rewriter);
2502 
2503  return success();
2504  }
2505 };
2506 
2507 void IfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2508  MLIRContext *context) {
2509  patterns.add<EmptyIfBody>(context);
2510  patterns.add(commonTailPatternWithPar<IfOp, ParOp>);
2511  patterns.add(commonTailPatternWithSeq<IfOp, SeqOp>);
2512 }
2513 
2514 //===----------------------------------------------------------------------===//
2515 // StaticIfOp
2516 //===----------------------------------------------------------------------===//
2517 LogicalResult StaticIfOp::verify() {
2518  if (elseBodyExists()) {
2519  auto *elseBod = getElseBody();
2520  auto &elseOps = elseBod->getOperations();
2521  // should only have one Operation, static, in the else branch
2522  for (Operation &op : elseOps) {
2523  if (!isStaticControl(&op)) {
2524  return op.emitOpError(
2525  "static if's else branch has non static control within it");
2526  }
2527  }
2528  }
2529 
2530  auto *thenBod = getThenBody();
2531  auto &thenOps = thenBod->getOperations();
2532  for (Operation &op : thenOps) {
2533  // should only have one, static, Operation in the then branch
2534  if (!isStaticControl(&op)) {
2535  return op.emitOpError(
2536  "static if's then branch has non static control within it");
2537  }
2538  }
2539 
2540  return success();
2541 }
2542 
2543 /// This pattern checks for one of two cases that will lead to StaticIfOp
2544 /// deletion: (1) Then and Else bodies are both empty. (2) Then body is empty
2545 /// and Else body does not exist.
2548  LogicalResult matchAndRewrite(StaticIfOp ifOp,
2549  PatternRewriter &rewriter) const override {
2550  if (!ifOp.getThenBody()->empty())
2551  return failure();
2552  if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2553  return failure();
2554 
2555  eraseControlWithConditional(ifOp, rewriter);
2556 
2557  return success();
2558  }
2559 };
2560 
2561 void StaticIfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2562  MLIRContext *context) {
2563  patterns.add<EmptyStaticIfBody>(context);
2564  patterns.add(commonTailPatternWithPar<StaticIfOp, StaticParOp>);
2565  patterns.add(commonTailPatternWithSeq<StaticIfOp, StaticSeqOp>);
2566 }
2567 
2568 //===----------------------------------------------------------------------===//
2569 // WhileOp
2570 //===----------------------------------------------------------------------===//
2571 LogicalResult WhileOp::verify() {
2572  auto component = (*this)->getParentOfType<ComponentOp>();
2573  auto wiresOp = component.getWiresOp();
2574 
2575  std::optional<StringRef> optGroupName = getGroupName();
2576  if (!optGroupName) {
2577  /// No combinational group was provided
2578  return success();
2579  }
2580  StringRef groupName = *optGroupName;
2581  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2582  if (!groupOp)
2583  return emitOpError() << "with group '" << groupName
2584  << "', which does not exist.";
2585 
2586  if (isa<GroupOp>(groupOp))
2587  return emitOpError() << "with group '" << groupName
2588  << "', which is not a combinational group.";
2589 
2590  if (failed(groupOp.drivesPort(getCond())))
2591  return emitError() << "conditional op: '" << valueName(component, getCond())
2592  << "' expected to be driven from group: '" << groupName
2593  << "' but no driver was found.";
2594 
2595  return success();
2596 }
2597 
2598 LogicalResult WhileOp::canonicalize(WhileOp whileOp,
2599  PatternRewriter &rewriter) {
2600  if (whileOp.getBodyBlock()->empty()) {
2601  eraseControlWithGroupAndConditional(whileOp, rewriter);
2602  return success();
2603  }
2604 
2605  return failure();
2606 }
2607 
2608 //===----------------------------------------------------------------------===//
2609 // StaticRepeatOp
2610 //===----------------------------------------------------------------------===//
2611 LogicalResult StaticRepeatOp::verify() {
2612  for (auto &&bodyOp : (*this).getRegion().front()) {
2613  // there should only be one bodyOp for each StaticRepeatOp
2614  if (!isStaticControl(&bodyOp)) {
2615  return bodyOp.emitOpError(
2616  "static repeat has non static control within it");
2617  }
2618  }
2619 
2620  return success();
2621 }
2622 
2623 template <typename OpTy>
2624 static LogicalResult zeroRepeat(OpTy op, PatternRewriter &rewriter) {
2625  static_assert(IsAny<OpTy, RepeatOp, StaticRepeatOp>(),
2626  "Should be a RepeatOp or StaticPRepeatOp");
2627  if (op.getCount() == 0) {
2628  Block *controlBody = op.getBodyBlock();
2629  for (auto &op : make_early_inc_range(*controlBody))
2630  op.erase();
2631 
2632  rewriter.eraseOp(op);
2633  return success();
2634  }
2635 
2636  return failure();
2637 }
2638 
2639 void StaticRepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2640  MLIRContext *context) {
2641  patterns.add(emptyControl<StaticRepeatOp>);
2642  patterns.add(zeroRepeat<StaticRepeatOp>);
2643 }
2644 
2645 //===----------------------------------------------------------------------===//
2646 // RepeatOp
2647 //===----------------------------------------------------------------------===//
2648 void RepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2649  MLIRContext *context) {
2650  patterns.add(emptyControl<RepeatOp>);
2651  patterns.add(zeroRepeat<RepeatOp>);
2652 }
2653 
2654 //===----------------------------------------------------------------------===//
2655 // InvokeOp
2656 //===----------------------------------------------------------------------===//
2657 
2658 // Parse the parameter list of invoke.
2659 static ParseResult
2660 parseParameterList(OpAsmParser &parser, OperationState &result,
2661  SmallVectorImpl<OpAsmParser::UnresolvedOperand> &ports,
2662  SmallVectorImpl<OpAsmParser::UnresolvedOperand> &inputs,
2663  SmallVectorImpl<Attribute> &portNames,
2664  SmallVectorImpl<Attribute> &inputNames,
2665  SmallVectorImpl<Type> &types) {
2666  OpAsmParser::UnresolvedOperand port;
2667  OpAsmParser::UnresolvedOperand input;
2668  Type type;
2669  auto parseParameter = [&]() -> ParseResult {
2670  if (parser.parseOperand(port) || parser.parseEqual() ||
2671  parser.parseOperand(input))
2672  return failure();
2673  ports.push_back(port);
2674  portNames.push_back(StringAttr::get(parser.getContext(), port.name));
2675  inputs.push_back(input);
2676  inputNames.push_back(StringAttr::get(parser.getContext(), input.name));
2677  return success();
2678  };
2679  if (parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2680  parseParameter))
2681  return failure();
2682  if (parser.parseArrow())
2683  return failure();
2684  auto parseType = [&]() -> ParseResult {
2685  if (parser.parseType(type))
2686  return failure();
2687  types.push_back(type);
2688  return success();
2689  };
2690  return parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2691  parseType);
2692 }
2693 
2694 ParseResult InvokeOp::parse(OpAsmParser &parser, OperationState &result) {
2695  StringAttr componentName;
2696  SmallVector<OpAsmParser::UnresolvedOperand, 4> ports;
2697  SmallVector<OpAsmParser::UnresolvedOperand, 4> inputs;
2698  SmallVector<Attribute> portNames;
2699  SmallVector<Attribute> inputNames;
2700  SmallVector<Type, 4> types;
2701  if (parser.parseSymbolName(componentName))
2702  return failure();
2703  FlatSymbolRefAttr callee = FlatSymbolRefAttr::get(componentName);
2704  SMLoc loc = parser.getCurrentLocation();
2705 
2706  SmallVector<Attribute, 4> refCells;
2707  if (succeeded(parser.parseOptionalLSquare())) {
2708  if (parser.parseCommaSeparatedList([&]() -> ParseResult {
2709  std::string refCellName;
2710  std::string externalMem;
2711  NamedAttrList refCellAttr;
2712  if (parser.parseKeywordOrString(&refCellName) ||
2713  parser.parseEqual() || parser.parseKeywordOrString(&externalMem))
2714  return failure();
2715  auto externalMemAttr =
2716  SymbolRefAttr::get(parser.getContext(), externalMem);
2717  refCellAttr.append(StringAttr::get(parser.getContext(), refCellName),
2718  externalMemAttr);
2719  refCells.push_back(
2720  DictionaryAttr::get(parser.getContext(), refCellAttr));
2721  return success();
2722  }) ||
2723  parser.parseRSquare())
2724  return failure();
2725  }
2726  result.addAttribute("refCellsMap",
2727  ArrayAttr::get(parser.getContext(), refCells));
2728 
2729  result.addAttribute("callee", callee);
2730  if (parseParameterList(parser, result, ports, inputs, portNames, inputNames,
2731  types))
2732  return failure();
2733  if (parser.resolveOperands(ports, types, loc, result.operands))
2734  return failure();
2735  if (parser.resolveOperands(inputs, types, loc, result.operands))
2736  return failure();
2737  result.addAttribute("portNames",
2738  ArrayAttr::get(parser.getContext(), portNames));
2739  result.addAttribute("inputNames",
2740  ArrayAttr::get(parser.getContext(), inputNames));
2741  return success();
2742 }
2743 
2744 void InvokeOp::print(OpAsmPrinter &p) {
2745  p << " @" << getCallee() << "[";
2746  auto refCellNamesMap = getRefCellsMap();
2747  llvm::interleaveComma(refCellNamesMap, p, [&](Attribute attr) {
2748  auto dictAttr = cast<DictionaryAttr>(attr);
2749  llvm::interleaveComma(dictAttr, p, [&](NamedAttribute namedAttr) {
2750  auto refCellName = namedAttr.getName().str();
2751  auto externalMem =
2752  cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
2753  p << refCellName << " = " << externalMem;
2754  });
2755  });
2756  p << "](";
2757 
2758  auto ports = getPorts();
2759  auto inputs = getInputs();
2760  llvm::interleaveComma(llvm::zip(ports, inputs), p, [&](auto arg) {
2761  p << std::get<0>(arg) << " = " << std::get<1>(arg);
2762  });
2763  p << ") -> (";
2764  llvm::interleaveComma(ports, p, [&](auto port) { p << port.getType(); });
2765  p << ")";
2766 }
2767 
2768 // Check the direction of one of the ports in one of the connections of an
2769 // InvokeOp.
2770 static LogicalResult verifyInvokeOpValue(InvokeOp &op, Value &value,
2771  bool isDestination) {
2772  if (isPort(value))
2773  return verifyPortDirection(op, value, isDestination);
2774  return success();
2775 }
2776 
2777 // Checks if the value comes from complex logic.
2778 static LogicalResult verifyComplexLogic(InvokeOp &op, Value &value) {
2779  // Refer to the above function verifyNotComplexSource for its role.
2780  Operation *operation = value.getDefiningOp();
2781  if (operation == nullptr)
2782  return success();
2783  if (auto *dialect = operation->getDialect(); isa<comb::CombDialect>(dialect))
2784  return failure();
2785  return success();
2786 }
2787 
2788 // Get the go port of the invoked component.
2789 Value InvokeOp::getInstGoValue() {
2790  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2791  Operation *operation = componentOp.lookupSymbol(getCallee());
2792  Value ret = nullptr;
2793  llvm::TypeSwitch<Operation *>(operation)
2794  .Case<RegisterOp>([&](auto op) { ret = operation->getResult(1); })
2795  .Case<MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2796  RemSPipeLibOp, RemUPipeLibOp>(
2797  [&](auto op) { ret = operation->getResult(2); })
2798  .Case<InstanceOp>([&](auto op) {
2799  auto portInfo = op.getReferencedComponent().getPortInfo();
2800  for (auto [portInfo, res] :
2801  llvm::zip(portInfo, operation->getResults())) {
2802  if (portInfo.hasAttribute(goPort))
2803  ret = res;
2804  }
2805  })
2806  .Case<PrimitiveOp>([&](auto op) {
2807  auto moduleExternOp = op.getReferencedPrimitive();
2808  auto argAttrs = moduleExternOp.getAllInputAttrs();
2809  for (auto [attr, res] : llvm::zip(argAttrs, op.getResults())) {
2810  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2811  if (!dictAttr.empty()) {
2812  if (dictAttr.begin()->getName().getValue() == "calyx.go")
2813  ret = res;
2814  }
2815  }
2816  }
2817  });
2818  return ret;
2819 }
2820 
2821 // Get the done port of the invoked component.
2822 Value InvokeOp::getInstDoneValue() {
2823  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2824  Operation *operation = componentOp.lookupSymbol(getCallee());
2825  Value ret = nullptr;
2826  llvm::TypeSwitch<Operation *>(operation)
2827  .Case<RegisterOp, MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2828  RemSPipeLibOp, RemUPipeLibOp>([&](auto op) {
2829  size_t doneIdx = operation->getResults().size() - 1;
2830  ret = operation->getResult(doneIdx);
2831  })
2832  .Case<InstanceOp>([&](auto op) {
2833  InstanceOp instanceOp = cast<InstanceOp>(operation);
2834  auto portInfo = instanceOp.getReferencedComponent().getPortInfo();
2835  for (auto [portInfo, res] :
2836  llvm::zip(portInfo, operation->getResults())) {
2837  if (portInfo.hasAttribute(donePort))
2838  ret = res;
2839  }
2840  })
2841  .Case<PrimitiveOp>([&](auto op) {
2842  PrimitiveOp primOp = cast<PrimitiveOp>(operation);
2843  auto moduleExternOp = primOp.getReferencedPrimitive();
2844  auto resAttrs = moduleExternOp.getAllOutputAttrs();
2845  for (auto [attr, res] : llvm::zip(resAttrs, primOp.getResults())) {
2846  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2847  if (!dictAttr.empty()) {
2848  if (dictAttr.begin()->getName().getValue() == "calyx.done")
2849  ret = res;
2850  }
2851  }
2852  }
2853  });
2854  return ret;
2855 }
2856 
2857 // A helper function that gets the number of go or done ports in
2858 // hw.module.extern.
2859 static size_t
2861  bool isGo) {
2862  size_t ret = 0;
2863  std::string str = isGo ? "calyx.go" : "calyx.done";
2864  for (Attribute attr : moduleExternOp.getAllInputAttrs()) {
2865  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2866  ret = llvm::count_if(dictAttr, [&](NamedAttribute iter) {
2867  return iter.getName().getValue() == str;
2868  });
2869  }
2870  }
2871  return ret;
2872 }
2873 
2874 LogicalResult InvokeOp::verify() {
2875  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2876  StringRef callee = getCallee();
2877  Operation *operation = componentOp.lookupSymbol(callee);
2878  // The referenced symbol does not exist.
2879  if (!operation)
2880  return emitOpError() << "with instance '@" << callee
2881  << "', which does not exist.";
2882  // The argument list of invoke is empty.
2883  if (getInputs().empty() && getRefCellsMap().empty()) {
2884  return emitOpError() << "'@" << callee
2885  << "' has zero input and output port connections and "
2886  "has no passing-by-reference cells; "
2887  "expected at least one.";
2888  }
2889  size_t goPortNum = 0, donePortNum = 0;
2890  // They both have a go port and a done port, but the "go" port for
2891  // registers and memrey should be "write_en" port.
2892  llvm::TypeSwitch<Operation *>(operation)
2893  .Case<RegisterOp, DivSPipeLibOp, DivUPipeLibOp, MemoryOp, MultPipeLibOp,
2894  RemSPipeLibOp, RemUPipeLibOp>(
2895  [&](auto op) { goPortNum = 1, donePortNum = 1; })
2896  .Case<InstanceOp>([&](auto op) {
2897  auto portInfo = op.getReferencedComponent().getPortInfo();
2898  for (PortInfo info : portInfo) {
2899  if (info.hasAttribute(goPort))
2900  ++goPortNum;
2901  if (info.hasAttribute(donePort))
2902  ++donePortNum;
2903  }
2904  })
2905  .Case<PrimitiveOp>([&](auto op) {
2906  auto moduleExternOp = op.getReferencedPrimitive();
2907  // Get the number of go ports and done ports by their attrubutes.
2908  goPortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, true);
2909  donePortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, false);
2910  });
2911  // If the number of go ports and done ports is wrong.
2912  if (goPortNum != 1 && donePortNum != 1)
2913  return emitOpError()
2914  << "'@" << callee << "'"
2915  << " is a combinational component and cannot be invoked, which must "
2916  "have single go port and single done port.";
2917 
2918  auto ports = getPorts();
2919  auto inputs = getInputs();
2920  // We have verified earlier that the instance has a go and a done port.
2921  Value goValue = getInstGoValue();
2922  Value doneValue = getInstDoneValue();
2923  for (auto [port, input, portName, inputName] :
2924  llvm::zip(ports, inputs, getPortNames(), getInputNames())) {
2925  // Check the direction of these destination ports.
2926  // 'calyx.invoke' op '@r0' has input '%r.out', which is a source port. The
2927  // inputs are required to be destination ports.
2928  if (failed(verifyInvokeOpValue(*this, port, true)))
2929  return emitOpError() << "'@" << callee << "' has input '"
2930  << cast<StringAttr>(portName).getValue()
2931  << "', which is a source port. The inputs are "
2932  "required to be destination ports.";
2933  // The go port should not appear in the parameter list.
2934  if (port == goValue)
2935  return emitOpError() << "the go or write_en port of '@" << callee
2936  << "' cannot appear here.";
2937  // Check the direction of these source ports.
2938  if (failed(verifyInvokeOpValue(*this, input, false)))
2939  return emitOpError() << "'@" << callee << "' has output '"
2940  << cast<StringAttr>(inputName).getValue()
2941  << "', which is a destination port. The inputs are "
2942  "required to be source ports.";
2943  if (failed(verifyComplexLogic(*this, input)))
2944  return emitOpError() << "'@" << callee << "' has '"
2945  << cast<StringAttr>(inputName).getValue()
2946  << "', which is not a port or constant. Complex "
2947  "logic should be conducted in the guard.";
2948  if (input == doneValue)
2949  return emitOpError() << "the done port of '@" << callee
2950  << "' cannot appear here.";
2951  // Check if the connection uses the callee's port.
2952  if (port.getDefiningOp() != operation && input.getDefiningOp() != operation)
2953  return emitOpError() << "the connection "
2954  << cast<StringAttr>(portName).getValue() << " = "
2955  << cast<StringAttr>(inputName).getValue()
2956  << " is not defined as an input port of '@" << callee
2957  << "'.";
2958  }
2959  return success();
2960 }
2961 
2962 //===----------------------------------------------------------------------===//
2963 // Calyx library ops
2964 //===----------------------------------------------------------------------===//
2965 
2966 LogicalResult PadLibOp::verify() {
2967  unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
2968  unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
2969  if (inBits >= outBits)
2970  return emitOpError("expected input bits (")
2971  << inBits << ')' << " to be less than output bits (" << outBits
2972  << ')';
2973  return success();
2974 }
2975 
2976 LogicalResult SliceLibOp::verify() {
2977  unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
2978  unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
2979  if (inBits <= outBits)
2980  return emitOpError("expected input bits (")
2981  << inBits << ')' << " to be greater than output bits (" << outBits
2982  << ')';
2983  return success();
2984 }
2985 
2986 #define ImplBinPipeOpCellInterface(OpType, outName) \
2987  SmallVector<StringRef> OpType::portNames() { \
2988  return {clkPort, resetPort, goPort, "left", "right", outName, donePort}; \
2989  } \
2990  \
2991  SmallVector<Direction> OpType::portDirections() { \
2992  return {Input, Input, Input, Input, Input, Output, Output}; \
2993  } \
2994  \
2995  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
2996  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
2997  } \
2998  \
2999  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3000  MLIRContext *context = getContext(); \
3001  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); \
3002  NamedAttrList go, clk, reset, done; \
3003  go.append(goPort, isSet); \
3004  clk.append(clkPort, isSet); \
3005  reset.append(resetPort, isSet); \
3006  done.append(donePort, isSet); \
3007  return { \
3008  clk.getDictionary(context), /* Clk */ \
3009  reset.getDictionary(context), /* Reset */ \
3010  go.getDictionary(context), /* Go */ \
3011  DictionaryAttr::get(context), /* Lhs */ \
3012  DictionaryAttr::get(context), /* Rhs */ \
3013  DictionaryAttr::get(context), /* Out */ \
3014  done.getDictionary(context) /* Done */ \
3015  }; \
3016  } \
3017  \
3018  bool OpType::isCombinational() { return false; }
3019 
3020 #define ImplUnaryOpCellInterface(OpType) \
3021  SmallVector<StringRef> OpType::portNames() { return {"in", "out"}; } \
3022  SmallVector<Direction> OpType::portDirections() { return {Input, Output}; } \
3023  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3024  return {DictionaryAttr::get(getContext()), \
3025  DictionaryAttr::get(getContext())}; \
3026  } \
3027  bool OpType::isCombinational() { return true; } \
3028  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3029  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3030  }
3031 
3032 #define ImplBinOpCellInterface(OpType) \
3033  SmallVector<StringRef> OpType::portNames() { \
3034  return {"left", "right", "out"}; \
3035  } \
3036  SmallVector<Direction> OpType::portDirections() { \
3037  return {Input, Input, Output}; \
3038  } \
3039  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3040  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3041  } \
3042  bool OpType::isCombinational() { return true; } \
3043  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3044  return {DictionaryAttr::get(getContext()), \
3045  DictionaryAttr::get(getContext()), \
3046  DictionaryAttr::get(getContext())}; \
3047  }
3048 
3049 // clang-format off
3050 ImplBinPipeOpCellInterface(MultPipeLibOp, "out")
3051 ImplBinPipeOpCellInterface(DivUPipeLibOp, "out_quotient")
3052 ImplBinPipeOpCellInterface(DivSPipeLibOp, "out_quotient")
3053 ImplBinPipeOpCellInterface(RemUPipeLibOp, "out_remainder")
3054 ImplBinPipeOpCellInterface(RemSPipeLibOp, "out_remainder")
3055 
3056 ImplUnaryOpCellInterface(PadLibOp)
3057 ImplUnaryOpCellInterface(SliceLibOp)
3058 ImplUnaryOpCellInterface(NotLibOp)
3059 ImplUnaryOpCellInterface(WireLibOp)
3060 ImplUnaryOpCellInterface(ExtSILibOp)
3061 
3062 ImplBinOpCellInterface(LtLibOp)
3063 ImplBinOpCellInterface(GtLibOp)
3064 ImplBinOpCellInterface(EqLibOp)
3065 ImplBinOpCellInterface(NeqLibOp)
3066 ImplBinOpCellInterface(GeLibOp)
3067 ImplBinOpCellInterface(LeLibOp)
3068 ImplBinOpCellInterface(SltLibOp)
3069 ImplBinOpCellInterface(SgtLibOp)
3070 ImplBinOpCellInterface(SeqLibOp)
3071 ImplBinOpCellInterface(SneqLibOp)
3072 ImplBinOpCellInterface(SgeLibOp)
3073 ImplBinOpCellInterface(SleLibOp)
3074 
3075 ImplBinOpCellInterface(AddLibOp)
3076 ImplBinOpCellInterface(SubLibOp)
3077 ImplBinOpCellInterface(ShruLibOp)
3078 ImplBinOpCellInterface(RshLibOp)
3079 ImplBinOpCellInterface(SrshLibOp)
3080 ImplBinOpCellInterface(LshLibOp)
3081 ImplBinOpCellInterface(AndLibOp)
3082 ImplBinOpCellInterface(OrLibOp)
3083 ImplBinOpCellInterface(XorLibOp)
3084 // clang-format on
3085 
3086 //===----------------------------------------------------------------------===//
3087 // TableGen generated logic.
3088 //===----------------------------------------------------------------------===//
3089 
3090 #include "circt/Dialect/Calyx/CalyxInterfaces.cpp.inc"
3091 
3092 // Provide the autogenerated implementation guts for the Op classes.
3093 #define GET_OP_CLASSES
3094 #include "circt/Dialect/Calyx/Calyx.cpp.inc"
assert(baseType &&"element must be base type")
static LogicalResult verifyPrimitiveOpType(PrimitiveOp instance, hw::HWModuleExternOp referencedPrimitive)
Verifies the port information in comparison with the referenced component of an instance.
Definition: CalyxOps.cpp:1689
static ParseResult parseComponentSignature(OpAsmParser &parser, OperationState &result, SmallVectorImpl< OpAsmParser::Argument > &ports, SmallVectorImpl< Type > &portTypes)
Parses the signature of a Calyx component.
Definition: CalyxOps.cpp:457
static LogicalResult verifyAssignOpValue(AssignOp op, bool isDestination)
Verifies the value of a given assignment operation.
Definition: CalyxOps.cpp:1482
static ParseResult parseParameterList(OpAsmParser &parser, SmallVector< Attribute > &parameters)
Parse an parameter list if present.
Definition: CalyxOps.cpp:1864
static SmallVector< PortInfo > getFilteredPorts(ComponentOp op, Pred p)
A helper function to return a filtered subset of a component's ports.
Definition: CalyxOps.cpp:679
static Op getControlOrWiresFrom(ComponentOp op)
This is a helper function that should only be used to get the WiresOp or ControlOp of a ComponentOp,...
Definition: CalyxOps.cpp:618
static LogicalResult verifyPrimitivePortDriving(AssignOp assign, GroupInterface group)
Verifies that certain ports of primitives are either driven or read together.
Definition: CalyxOps.cpp:1333
#define ImplBinPipeOpCellInterface(OpType, outName)
Definition: CalyxOps.cpp:2986
static bool portIsUsedInGroup(GroupInterface group, Value port, bool isDriven)
Determines whether the given port is used in the group.
Definition: CalyxOps.cpp:1223
static Value getBlockArgumentWithName(StringRef name, ComponentOp op)
Returns the Block argument with the given name from a ComponentOp.
Definition: CalyxOps.cpp:628
static ParseResult parsePortDefList(OpAsmParser &parser, OperationState &result, SmallVectorImpl< OpAsmParser::Argument > &ports, SmallVectorImpl< Type > &portTypes, SmallVectorImpl< NamedAttrList > &portAttrs)
Parses the ports of a Calyx component signature, and adds the corresponding port names to attrName.
Definition: CalyxOps.cpp:429
static std::string valueName(Operation *scopeOp, Value v)
Convenience function for getting the SSA name of v under the scope of operation scopeOp.
Definition: CalyxOps.cpp:121
static LogicalResult verifyNotComplexSource(Op op)
Verify that the value is not a "complex" value.
Definition: CalyxOps.cpp:104
static LogicalResult verifyInstanceOpType(InstanceOp instance, ComponentInterface referencedComponent)
Verifies the port information in comparison with the referenced component of an instance.
Definition: CalyxOps.cpp:1582
static LogicalResult collapseControl(OpTy controlOp, PatternRewriter &rewriter)
Definition: CalyxOps.cpp:312
static bool hasCommonTailPatternPreConditions(IfOpTy op)
Checks preconditions for the common tail pattern.
Definition: CalyxOps.cpp:2370
Direction convertHWDirectionToCalyx(hw::ModulePort::Direction direction)
Definition: CalyxOps.cpp:1800
static LogicalResult anyPortsReadByGroup(GroupInterface group, ValueRange ports)
Checks whether any ports are read within the group.
Definition: CalyxOps.cpp:1312
static bool hasControlRegion(Operation *op)
Returns whether the given operation has a control region.
Definition: CalyxOps.cpp:150
static std::optional< EnableOp > getLastEnableOp(OpTy parent)
Returns the last EnableOp within the child tree of 'parentSeqOp' or parentStaticSeqOp.
Definition: CalyxOps.cpp:2331
static LogicalResult emptyControl(OpTy controlOp, PatternRewriter &rewriter)
Definition: CalyxOps.cpp:330
static LogicalResult verifyControlBody(Operation *op)
Verifies the body of a ControlLikeOp.
Definition: CalyxOps.cpp:169
static void eraseControlWithConditional(OpTy op, PatternRewriter &rewriter)
A helper function to check whether the conditional needs to be erased to maintain a valid state of a ...
Definition: CalyxOps.cpp:369
static LogicalResult verifyInvokeOpValue(InvokeOp &op, Value &value, bool isDestination)
Definition: CalyxOps.cpp:2770
static void eraseControlWithGroupAndConditional(OpTy op, PatternRewriter &rewriter)
A helper function to check whether the conditional and group (if it exists) needs to be erased to mai...
Definition: CalyxOps.cpp:342
static ParseResult parseComponentInterface(OpAsmParser &parser, OperationState &result)
Definition: CalyxOps.cpp:503
static void printComponentInterface(OpAsmPrinter &p, ComponentInterface comp)
Definition: CalyxOps.cpp:387
static LogicalResult hasRequiredPorts(ComponentOp op)
Determines whether the given ComponentOp has all the required ports.
Definition: CalyxOps.cpp:704
static LogicalResult anyPortsDrivenByGroup(GroupInterface group, ValueRange ports)
Checks whether any ports are driven within the group.
Definition: CalyxOps.cpp:1292
static bool isPort(Value value)
Returns whether this value is either (1) a port on a ComponentOp or (2) a port on a cell interface.
Definition: CalyxOps.cpp:135
#define ImplBinOpCellInterface(OpType)
Definition: CalyxOps.cpp:3032
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition: CalyxOps.cpp:540
static LogicalResult portDrivenByGroup(GroupInterface groupOp, Value port)
Checks whether port is driven from within groupOp.
Definition: CalyxOps.cpp:1245
static LogicalResult zeroRepeat(OpTy op, PatternRewriter &rewriter)
Definition: CalyxOps.cpp:2624
static void buildComponentLike(OpBuilder &builder, OperationState &result, StringAttr name, ArrayRef< PortInfo > ports, bool combinational)
Definition: CalyxOps.cpp:548
static void getCellAsmResultNames(OpAsmSetValueNameFn setNameFn, Operation *op, ArrayRef< StringRef > portNames)
Gives each result of the cell a meaningful name in the form: <instance-name>.
Definition: CalyxOps.cpp:1435
static LogicalResult commonTailPatternWithSeq(IfOpTy ifOp, PatternRewriter &rewriter)
seq { if a with @G { if a with @G { seq { ...
Definition: CalyxOps.cpp:2394
static LogicalResult allPortsDrivenByGroup(GroupInterface group, ValueRange ports)
Checks whether all ports are driven within the group.
Definition: CalyxOps.cpp:1272
static LogicalResult verifyPortDirection(Operation *op, Value value, bool isDestination)
Determines whether the given direction is valid with the given inputs.
Definition: CalyxOps.cpp:1452
static LogicalResult isCombinational(Value value, GroupInterface group)
Verifies the defining operation of a value is combinational.
Definition: CalyxOps.cpp:1056
static size_t getHwModuleExtGoOrDonePortNumber(hw::HWModuleExternOp &moduleExternOp, bool isGo)
Definition: CalyxOps.cpp:2860
static bool isStaticControl(Operation *op)
Returns whether the given operation is a static control operator.
Definition: CalyxOps.cpp:156
static void printParameterList(OpAsmPrinter &p, Operation *op, ArrayAttr parameters)
Print a parameter list for a module or instance. Same format as HW dialect.
Definition: CalyxOps.cpp:1902
static DictionaryAttr cleanCalyxPortAttrs(OpBuilder builder, DictionaryAttr dict)
Returns a new DictionaryAttr containing only the calyx dialect attrs in the input DictionaryAttr.
Definition: CalyxOps.cpp:1825
static void printGroupPort(OpAsmPrinter &p, GroupPortType op)
Definition: CalyxOps.cpp:297
#define ImplUnaryOpCellInterface(OpType)
Definition: CalyxOps.cpp:3020
static ParseResult parseGroupPort(OpAsmParser &parser, OperationState &result)
Definition: CalyxOps.cpp:269
static LogicalResult verifyComplexLogic(InvokeOp &op, Value &value)
Definition: CalyxOps.cpp:2778
static llvm::StringMap< EnableOp > getAllEnableOpsInImmediateBody(OpTy parent)
Returns a mapping of {enabled Group name, EnableOp} for all EnableOps within the immediate ParOp's bo...
Definition: CalyxOps.cpp:2350
static LogicalResult commonTailPatternWithPar(OpTy controlOp, PatternRewriter &rewriter)
if a with @G { par { par { if a with @G { ...
Definition: CalyxOps.cpp:2444
std::map< std::string, WriteChannelPort & > writePorts
static SmallVector< Block *, 8 > intersection(SmallVectorImpl< Block * > &v1, SmallVectorImpl< Block * > &v2)
Calculate intersection of two vectors, returns a new vector.
static ParseResult parseType(Type &result, StringRef name, AsmParser &parser)
Parse a type defined by this dialect.
int32_t width
Definition: FIRRTL.cpp:36
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
@ InOut
Definition: HW.h:35
static InstancePath empty
static ParseResult parsePort(OpAsmParser &p, module_like_impl::PortParse &result)
Parse a single argument with the following syntax:
static Block * getBodyBlock(FModuleLike mod)
Signals that the following operation is combinational.
Definition: CalyxOps.h:55
static LogicalResult canonicalize(Op op, PatternRewriter &rewriter)
Definition: VerifOps.cpp:66
static LogicalResult verify(Value clock, bool eventExists, mlir::Location loc)
Definition: SVOps.cpp:2459
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
IntegerAttr packAttribute(MLIRContext *context, size_t nIns, size_t nOuts)
Returns an IntegerAttr containing the packed representation of the direction counts.
Definition: CalyxOps.cpp:59
static constexpr std::string_view clkPort
Definition: CalyxOps.h:34
LogicalResult verifyComponent(Operation *op)
A helper function to verify each operation with the Ccomponent trait.
Definition: CalyxOps.cpp:203
static constexpr std::string_view donePort
Definition: CalyxOps.h:32
LogicalResult verifyControlLikeOp(Operation *op)
A helper function to verify each control-like operation has a valid parent and, if applicable,...
Definition: CalyxOps.cpp:219
FloatingPointStandard
Definition: CalyxOps.h:70
LogicalResult verifyGroupInterface(Operation *op)
A helper function to verify each operation with the Group Interface trait.
Definition: CalyxOps.cpp:1413
LogicalResult verifyCell(Operation *op)
A helper function to verify each operation with the Cell trait.
Definition: CalyxOps.cpp:211
static constexpr std::string_view resetPort
Definition: CalyxOps.h:33
Direction
The direction of a Component or Cell port.
Definition: CalyxOps.h:76
LogicalResult verifyIf(Operation *op)
A helper function to verify each operation with the If trait.
Definition: CalyxOps.cpp:256
PortInfo getPortInfo(BlockArgument arg)
Returns port information for the block argument provided.
Definition: CalyxOps.cpp:142
static constexpr std::string_view goPort
Definition: CalyxOps.h:31
void getAsmResultNames(OpAsmSetValueNameFn setNameFn, StringRef instanceName, ArrayAttr resultNames, ValueRange results)
Suggest a name for each result value based on the saved result names attribute.
enum PEO uint32_t mlir::FailureOr< mlir::Type > evaluateParametricType(mlir::Location loc, mlir::ArrayAttr parameters, mlir::Type type, bool emitErrors=true)
Returns a resolved version of 'type' wherein any parameter reference has been evaluated based on the ...
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition: LLVM.h:182
This pattern collapses a calyx.seq or calyx.par operation when it contains exactly one calyx....
Definition: CalyxOps.cpp:78
LogicalResult matchAndRewrite(CtrlOp ctrlOp, PatternRewriter &rewriter) const override
Definition: CalyxOps.cpp:80
This pattern checks for one of two cases that will lead to IfOp deletion: (1) Then and Else bodies ar...
Definition: CalyxOps.cpp:2492
LogicalResult matchAndRewrite(IfOp ifOp, PatternRewriter &rewriter) const override
Definition: CalyxOps.cpp:2494
This pattern checks for one of two cases that will lead to StaticIfOp deletion: (1) Then and Else bod...
Definition: CalyxOps.cpp:2546
LogicalResult matchAndRewrite(StaticIfOp ifOp, PatternRewriter &rewriter) const override
Definition: CalyxOps.cpp:2548
This holds information about the port for either a Component or Cell.
Definition: CalyxOps.h:89
Direction direction
Definition: CalyxOps.h:92
DictionaryAttr attributes
Definition: CalyxOps.h:93