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 FloatingPointStandard CompareFOpIEEE754::getFloatingPointStandard() {
1214 }
1215 
1216 std::string AddFOpIEEE754::getCalyxLibraryName() { return "std_addFN"; }
1217 
1218 std::string MulFOpIEEE754::getCalyxLibraryName() { return "std_mulFN"; }
1219 
1220 std::string CompareFOpIEEE754::getCalyxLibraryName() { return "std_compareFN"; }
1221 
1222 //===----------------------------------------------------------------------===//
1223 // GroupInterface
1224 //===----------------------------------------------------------------------===//
1225 
1226 /// Determines whether the given port is used in the group. Its use depends on
1227 /// the `isDriven` value; if true, then the port should be a destination in an
1228 /// AssignOp. Otherwise, it should be the source, i.e. a read.
1229 static bool portIsUsedInGroup(GroupInterface group, Value port, bool isDriven) {
1230  return llvm::any_of(port.getUses(), [&](auto &&use) {
1231  auto assignOp = dyn_cast<AssignOp>(use.getOwner());
1232  if (assignOp == nullptr)
1233  return false;
1234 
1235  Operation *parent = assignOp->getParentOp();
1236  if (isa<WiresOp>(parent))
1237  // This is a continuous assignment.
1238  return false;
1239 
1240  // A port is used if it meet the criteria:
1241  // (1) it is a {source, destination} of an assignment.
1242  // (2) that assignment is found in the provided group.
1243 
1244  // If not driven, then read.
1245  Value expected = isDriven ? assignOp.getDest() : assignOp.getSrc();
1246  return expected == port && group == parent;
1247  });
1248 }
1249 
1250 /// Checks whether `port` is driven from within `groupOp`.
1251 static LogicalResult portDrivenByGroup(GroupInterface groupOp, Value port) {
1252  // Check if the port is driven by an assignOp from within `groupOp`.
1253  if (portIsUsedInGroup(groupOp, port, /*isDriven=*/true))
1254  return success();
1255 
1256  // If `port` is an output of a cell then we conservatively enforce that at
1257  // least one input port of the cell must be driven by the group.
1258  if (auto cell = dyn_cast<CellInterface>(port.getDefiningOp());
1259  cell && cell.direction(port) == calyx::Direction::Output)
1260  return groupOp.drivesAnyPort(cell.getInputPorts());
1261 
1262  return failure();
1263 }
1264 
1265 LogicalResult GroupOp::drivesPort(Value port) {
1266  return portDrivenByGroup(*this, port);
1267 }
1268 
1269 LogicalResult CombGroupOp::drivesPort(Value port) {
1270  return portDrivenByGroup(*this, port);
1271 }
1272 
1273 LogicalResult StaticGroupOp::drivesPort(Value port) {
1274  return portDrivenByGroup(*this, port);
1275 }
1276 
1277 /// Checks whether all ports are driven within the group.
1278 static LogicalResult allPortsDrivenByGroup(GroupInterface group,
1279  ValueRange ports) {
1280  return success(llvm::all_of(ports, [&](Value port) {
1281  return portIsUsedInGroup(group, port, /*isDriven=*/true);
1282  }));
1283 }
1284 
1285 LogicalResult GroupOp::drivesAllPorts(ValueRange ports) {
1286  return allPortsDrivenByGroup(*this, ports);
1287 }
1288 
1289 LogicalResult CombGroupOp::drivesAllPorts(ValueRange ports) {
1290  return allPortsDrivenByGroup(*this, ports);
1291 }
1292 
1293 LogicalResult StaticGroupOp::drivesAllPorts(ValueRange ports) {
1294  return allPortsDrivenByGroup(*this, ports);
1295 }
1296 
1297 /// Checks whether any ports are driven within the group.
1298 static LogicalResult anyPortsDrivenByGroup(GroupInterface group,
1299  ValueRange ports) {
1300  return success(llvm::any_of(ports, [&](Value port) {
1301  return portIsUsedInGroup(group, port, /*isDriven=*/true);
1302  }));
1303 }
1304 
1305 LogicalResult GroupOp::drivesAnyPort(ValueRange ports) {
1306  return anyPortsDrivenByGroup(*this, ports);
1307 }
1308 
1309 LogicalResult CombGroupOp::drivesAnyPort(ValueRange ports) {
1310  return anyPortsDrivenByGroup(*this, ports);
1311 }
1312 
1313 LogicalResult StaticGroupOp::drivesAnyPort(ValueRange ports) {
1314  return anyPortsDrivenByGroup(*this, ports);
1315 }
1316 
1317 /// Checks whether any ports are read within the group.
1318 static LogicalResult anyPortsReadByGroup(GroupInterface group,
1319  ValueRange ports) {
1320  return success(llvm::any_of(ports, [&](Value port) {
1321  return portIsUsedInGroup(group, port, /*isDriven=*/false);
1322  }));
1323 }
1324 
1325 LogicalResult GroupOp::readsAnyPort(ValueRange ports) {
1326  return anyPortsReadByGroup(*this, ports);
1327 }
1328 
1329 LogicalResult CombGroupOp::readsAnyPort(ValueRange ports) {
1330  return anyPortsReadByGroup(*this, ports);
1331 }
1332 
1333 LogicalResult StaticGroupOp::readsAnyPort(ValueRange ports) {
1334  return anyPortsReadByGroup(*this, ports);
1335 }
1336 
1337 /// Verifies that certain ports of primitives are either driven or read
1338 /// together.
1339 static LogicalResult verifyPrimitivePortDriving(AssignOp assign,
1340  GroupInterface group) {
1341  Operation *destDefiningOp = assign.getDest().getDefiningOp();
1342  if (destDefiningOp == nullptr)
1343  return success();
1344  auto destCell = dyn_cast<CellInterface>(destDefiningOp);
1345  if (destCell == nullptr)
1346  return success();
1347 
1348  LogicalResult verifyWrites =
1349  TypeSwitch<Operation *, LogicalResult>(destCell)
1350  .Case<RegisterOp>([&](auto op) {
1351  // We only want to verify this is written to if the {write enable,
1352  // in} port is driven.
1353  return succeeded(group.drivesAnyPort({op.getWriteEn(), op.getIn()}))
1354  ? group.drivesAllPorts({op.getWriteEn(), op.getIn()})
1355  : success();
1356  })
1357  .Case<MemoryOp>([&](auto op) {
1358  SmallVector<Value> requiredWritePorts;
1359  // If writing to memory, write_en, write_data, and all address ports
1360  // should be driven.
1361  requiredWritePorts.push_back(op.writeEn());
1362  requiredWritePorts.push_back(op.writeData());
1363  for (Value address : op.addrPorts())
1364  requiredWritePorts.push_back(address);
1365 
1366  // We only want to verify the write ports if either write_data or
1367  // write_en is driven.
1368  return succeeded(
1369  group.drivesAnyPort({op.writeData(), op.writeEn()}))
1370  ? group.drivesAllPorts(requiredWritePorts)
1371  : success();
1372  })
1373  .Case<AndLibOp, OrLibOp, XorLibOp, AddLibOp, SubLibOp, GtLibOp,
1374  LtLibOp, EqLibOp, NeqLibOp, GeLibOp, LeLibOp, LshLibOp,
1375  RshLibOp, SgtLibOp, SltLibOp, SeqLibOp, SneqLibOp, SgeLibOp,
1376  SleLibOp, SrshLibOp>([&](auto op) {
1377  Value lhs = op.getLeft(), rhs = op.getRight();
1378  return succeeded(group.drivesAnyPort({lhs, rhs}))
1379  ? group.drivesAllPorts({lhs, rhs})
1380  : success();
1381  })
1382  .Default([&](auto op) { return success(); });
1383 
1384  if (failed(verifyWrites))
1385  return group->emitOpError()
1386  << "with cell: " << destCell->getName() << " \""
1387  << destCell.instanceName()
1388  << "\" is performing a write and failed to drive all necessary "
1389  "ports.";
1390 
1391  Operation *srcDefiningOp = assign.getSrc().getDefiningOp();
1392  if (srcDefiningOp == nullptr)
1393  return success();
1394  auto srcCell = dyn_cast<CellInterface>(srcDefiningOp);
1395  if (srcCell == nullptr)
1396  return success();
1397 
1398  LogicalResult verifyReads =
1399  TypeSwitch<Operation *, LogicalResult>(srcCell)
1400  .Case<MemoryOp>([&](auto op) {
1401  // If reading memory, all address ports should be driven. Note that
1402  // we only want to verify the read ports if read_data is used in the
1403  // group.
1404  return succeeded(group.readsAnyPort({op.readData()}))
1405  ? group.drivesAllPorts(op.addrPorts())
1406  : success();
1407  })
1408  .Default([&](auto op) { return success(); });
1409 
1410  if (failed(verifyReads))
1411  return group->emitOpError() << "with cell: " << srcCell->getName() << " \""
1412  << srcCell.instanceName()
1413  << "\" is having a read performed upon it, and "
1414  "failed to drive all necessary ports.";
1415 
1416  return success();
1417 }
1418 
1419 LogicalResult calyx::verifyGroupInterface(Operation *op) {
1420  auto group = dyn_cast<GroupInterface>(op);
1421  if (group == nullptr)
1422  return success();
1423 
1424  for (auto &&groupOp : *group.getBody()) {
1425  auto assign = dyn_cast<AssignOp>(groupOp);
1426  if (assign == nullptr)
1427  continue;
1428  if (failed(verifyPrimitivePortDriving(assign, group)))
1429  return failure();
1430  }
1431 
1432  return success();
1433 }
1434 
1435 //===----------------------------------------------------------------------===//
1436 // Utilities for operations with the Cell trait.
1437 //===----------------------------------------------------------------------===//
1438 
1439 /// Gives each result of the cell a meaningful name in the form:
1440 /// <instance-name>.<port-name>
1441 static void getCellAsmResultNames(OpAsmSetValueNameFn setNameFn, Operation *op,
1442  ArrayRef<StringRef> portNames) {
1443  auto cellInterface = dyn_cast<CellInterface>(op);
1444  assert(cellInterface && "must implement the Cell interface");
1445 
1446  std::string prefix = cellInterface.instanceName().str() + ".";
1447  for (size_t i = 0, e = portNames.size(); i != e; ++i)
1448  setNameFn(op->getResult(i), prefix + portNames[i].str());
1449 }
1450 
1451 //===----------------------------------------------------------------------===//
1452 // AssignOp
1453 //===----------------------------------------------------------------------===//
1454 
1455 /// Determines whether the given direction is valid with the given inputs. The
1456 /// `isDestination` boolean is used to distinguish whether the value is a source
1457 /// or a destination.
1458 static LogicalResult verifyPortDirection(Operation *op, Value value,
1459  bool isDestination) {
1460  Operation *definingOp = value.getDefiningOp();
1461  bool isComponentPort = isa<BlockArgument>(value),
1462  isCellInterfacePort = isa_and_nonnull<CellInterface>(definingOp);
1463  assert((isComponentPort || isCellInterfacePort) && "Not a port.");
1464 
1465  PortInfo port = isComponentPort
1466  ? getPortInfo(cast<BlockArgument>(value))
1467  : cast<CellInterface>(definingOp).portInfo(value);
1468 
1469  bool isSource = !isDestination;
1470  // Component output ports and cell interface input ports should be driven.
1471  Direction validDirection =
1472  (isDestination && isComponentPort) || (isSource && isCellInterfacePort)
1474  : Direction::Input;
1475 
1476  return port.direction == validDirection
1477  ? success()
1478  : op->emitOpError()
1479  << "has a " << (isComponentPort ? "component" : "cell")
1480  << " port as the "
1481  << (isDestination ? "destination" : "source")
1482  << " with the incorrect direction.";
1483 }
1484 
1485 /// Verifies the value of a given assignment operation. The boolean
1486 /// `isDestination` is used to distinguish whether the destination
1487 /// or source of the AssignOp is to be verified.
1488 static LogicalResult verifyAssignOpValue(AssignOp op, bool isDestination) {
1489  bool isSource = !isDestination;
1490  Value value = isDestination ? op.getDest() : op.getSrc();
1491  if (isPort(value))
1492  return verifyPortDirection(op, value, isDestination);
1493 
1494  // A destination may also be the Go or Done hole of a GroupOp.
1495  if (isDestination && !isa<GroupGoOp, GroupDoneOp>(value.getDefiningOp()))
1496  return op->emitOpError(
1497  "has an invalid destination port. It must be drive-able.");
1498  else if (isSource)
1499  return verifyNotComplexSource(op);
1500 
1501  return success();
1502 }
1503 
1504 LogicalResult AssignOp::verify() {
1505  bool isDestination = true, isSource = false;
1506  if (failed(verifyAssignOpValue(*this, isDestination)))
1507  return failure();
1508  if (failed(verifyAssignOpValue(*this, isSource)))
1509  return failure();
1510 
1511  return success();
1512 }
1513 
1514 ParseResult AssignOp::parse(OpAsmParser &parser, OperationState &result) {
1515  OpAsmParser::UnresolvedOperand destination;
1516  if (parser.parseOperand(destination) || parser.parseEqual())
1517  return failure();
1518 
1519  // An AssignOp takes one of the two following forms:
1520  // (1) %<dest> = %<src> : <type>
1521  // (2) %<dest> = %<guard> ? %<src> : <type>
1522  OpAsmParser::UnresolvedOperand guardOrSource;
1523  if (parser.parseOperand(guardOrSource))
1524  return failure();
1525 
1526  // Since the guard is optional, we need to check if there is an accompanying
1527  // `?` symbol.
1528  OpAsmParser::UnresolvedOperand source;
1529  bool hasGuard = succeeded(parser.parseOptionalQuestion());
1530  if (hasGuard) {
1531  // The guard exists. Parse the source.
1532  if (parser.parseOperand(source))
1533  return failure();
1534  }
1535 
1536  Type type;
1537  if (parser.parseColonType(type) ||
1538  parser.resolveOperand(destination, type, result.operands))
1539  return failure();
1540 
1541  if (hasGuard) {
1542  Type i1Type = parser.getBuilder().getI1Type();
1543  // Since the guard is optional, it is listed last in the arguments of the
1544  // AssignOp. Therefore, we must parse the source first.
1545  if (parser.resolveOperand(source, type, result.operands) ||
1546  parser.resolveOperand(guardOrSource, i1Type, result.operands))
1547  return failure();
1548  } else {
1549  // This is actually a source.
1550  if (parser.resolveOperand(guardOrSource, type, result.operands))
1551  return failure();
1552  }
1553 
1554  return success();
1555 }
1556 
1557 void AssignOp::print(OpAsmPrinter &p) {
1558  p << " " << getDest() << " = ";
1559 
1560  Value bguard = getGuard(), source = getSrc();
1561  // The guard is optional.
1562  if (bguard)
1563  p << bguard << " ? ";
1564 
1565  // We only need to print a single type; the destination and source are
1566  // guaranteed to be the same type.
1567  p << source << " : " << source.getType();
1568 }
1569 
1570 //===----------------------------------------------------------------------===//
1571 // InstanceOp
1572 //===----------------------------------------------------------------------===//
1573 
1574 /// Lookup the component for the symbol. This returns null on
1575 /// invalid IR.
1576 ComponentInterface InstanceOp::getReferencedComponent() {
1577  auto module = (*this)->getParentOfType<ModuleOp>();
1578  if (!module)
1579  return nullptr;
1580 
1581  return module.lookupSymbol<ComponentInterface>(getComponentName());
1582 }
1583 
1584 /// Verifies the port information in comparison with the referenced component
1585 /// of an instance. This helper function avoids conducting a lookup for the
1586 /// referenced component twice.
1587 static LogicalResult
1588 verifyInstanceOpType(InstanceOp instance,
1589  ComponentInterface referencedComponent) {
1590  auto module = instance->getParentOfType<ModuleOp>();
1591  StringRef entryPointName =
1592  module->getAttrOfType<StringAttr>("calyx.entrypoint");
1593  if (instance.getComponentName() == entryPointName)
1594  return instance.emitOpError()
1595  << "cannot reference the entry-point component: '" << entryPointName
1596  << "'.";
1597 
1598  // Verify the instance result ports with those of its referenced component.
1599  SmallVector<PortInfo> componentPorts = referencedComponent.getPortInfo();
1600  size_t numPorts = componentPorts.size();
1601 
1602  size_t numResults = instance.getNumResults();
1603  if (numResults != numPorts)
1604  return instance.emitOpError()
1605  << "has a wrong number of results; expected: " << numPorts
1606  << " but got " << numResults;
1607 
1608  for (size_t i = 0; i != numResults; ++i) {
1609  auto resultType = instance.getResult(i).getType();
1610  auto expectedType = componentPorts[i].type;
1611  if (resultType == expectedType)
1612  continue;
1613  return instance.emitOpError()
1614  << "result type for " << componentPorts[i].name << " must be "
1615  << expectedType << ", but got " << resultType;
1616  }
1617  return success();
1618 }
1619 
1620 LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1621  Operation *op = *this;
1622  auto module = op->getParentOfType<ModuleOp>();
1623  Operation *referencedComponent =
1624  symbolTable.lookupNearestSymbolFrom(module, getComponentNameAttr());
1625  if (referencedComponent == nullptr)
1626  return emitError() << "referencing component: '" << getComponentName()
1627  << "', which does not exist.";
1628 
1629  Operation *shadowedComponentName =
1630  symbolTable.lookupNearestSymbolFrom(module, getSymNameAttr());
1631  if (shadowedComponentName != nullptr)
1632  return emitError() << "instance symbol: '" << instanceName()
1633  << "' is already a symbol for another component.";
1634 
1635  // Verify the referenced component is not instantiating itself.
1636  auto parentComponent = op->getParentOfType<ComponentOp>();
1637  if (parentComponent == referencedComponent)
1638  return emitError() << "recursive instantiation of its parent component: '"
1639  << getComponentName() << "'";
1640 
1641  assert(isa<ComponentInterface>(referencedComponent) &&
1642  "Should be a ComponentInterface.");
1643  return verifyInstanceOpType(*this,
1644  cast<ComponentInterface>(referencedComponent));
1645 }
1646 
1647 /// Provide meaningful names to the result values of an InstanceOp.
1649  getCellAsmResultNames(setNameFn, *this, this->portNames());
1650 }
1651 
1652 SmallVector<StringRef> InstanceOp::portNames() {
1653  SmallVector<StringRef> portNames;
1654  for (Attribute name : getReferencedComponent().getPortNames())
1655  portNames.push_back(cast<StringAttr>(name).getValue());
1656  return portNames;
1657 }
1658 
1659 SmallVector<Direction> InstanceOp::portDirections() {
1660  SmallVector<Direction> portDirections;
1661  for (const PortInfo &port : getReferencedComponent().getPortInfo())
1662  portDirections.push_back(port.direction);
1663  return portDirections;
1664 }
1665 
1666 SmallVector<DictionaryAttr> InstanceOp::portAttributes() {
1667  SmallVector<DictionaryAttr> portAttributes;
1668  for (const PortInfo &port : getReferencedComponent().getPortInfo())
1669  portAttributes.push_back(port.attributes);
1670  return portAttributes;
1671 }
1672 
1674  return isa<CombComponentOp>(getReferencedComponent());
1675 }
1676 
1677 //===----------------------------------------------------------------------===//
1678 // PrimitiveOp
1679 //===----------------------------------------------------------------------===//
1680 
1681 /// Lookup the component for the symbol. This returns null on
1682 /// invalid IR.
1683 hw::HWModuleExternOp PrimitiveOp::getReferencedPrimitive() {
1684  auto module = (*this)->getParentOfType<ModuleOp>();
1685  if (!module)
1686  return nullptr;
1687 
1688  return module.lookupSymbol<hw::HWModuleExternOp>(getPrimitiveName());
1689 }
1690 
1691 /// Verifies the port information in comparison with the referenced component
1692 /// of an instance. This helper function avoids conducting a lookup for the
1693 /// referenced component twice.
1694 static LogicalResult
1695 verifyPrimitiveOpType(PrimitiveOp instance,
1696  hw::HWModuleExternOp referencedPrimitive) {
1697  auto module = instance->getParentOfType<ModuleOp>();
1698  StringRef entryPointName =
1699  module->getAttrOfType<StringAttr>("calyx.entrypoint");
1700  if (instance.getPrimitiveName() == entryPointName)
1701  return instance.emitOpError()
1702  << "cannot reference the entry-point component: '" << entryPointName
1703  << "'.";
1704 
1705  // Verify the instance result ports with those of its referenced component.
1706  auto primitivePorts = referencedPrimitive.getPortList();
1707  size_t numPorts = primitivePorts.size();
1708 
1709  size_t numResults = instance.getNumResults();
1710  if (numResults != numPorts)
1711  return instance.emitOpError()
1712  << "has a wrong number of results; expected: " << numPorts
1713  << " but got " << numResults;
1714 
1715  // Verify parameters match up
1716  ArrayAttr modParameters = referencedPrimitive.getParameters();
1717  ArrayAttr parameters = instance.getParameters().value_or(ArrayAttr());
1718  size_t numExpected = modParameters.size();
1719  size_t numParams = parameters.size();
1720  if (numParams != numExpected)
1721  return instance.emitOpError()
1722  << "has the wrong number of parameters; expected: " << numExpected
1723  << " but got " << numParams;
1724 
1725  for (size_t i = 0; i != numExpected; ++i) {
1726  auto param = cast<circt::hw::ParamDeclAttr>(parameters[i]);
1727  auto modParam = cast<circt::hw::ParamDeclAttr>(modParameters[i]);
1728 
1729  auto paramName = param.getName();
1730  if (paramName != modParam.getName())
1731  return instance.emitOpError()
1732  << "parameter #" << i << " should have name " << modParam.getName()
1733  << " but has name " << paramName;
1734 
1735  if (param.getType() != modParam.getType())
1736  return instance.emitOpError()
1737  << "parameter " << paramName << " should have type "
1738  << modParam.getType() << " but has type " << param.getType();
1739 
1740  // All instance parameters must have a value. Specify the same value as
1741  // a module's default value if you want the default.
1742  if (!param.getValue())
1743  return instance.emitOpError("parameter ")
1744  << paramName << " must have a value";
1745  }
1746 
1747  for (size_t i = 0; i != numResults; ++i) {
1748  auto resultType = instance.getResult(i).getType();
1749  auto expectedType = primitivePorts[i].type;
1750  auto replacedType = hw::evaluateParametricType(
1751  instance.getLoc(), instance.getParametersAttr(), expectedType);
1752  if (failed(replacedType))
1753  return failure();
1754  if (resultType == replacedType)
1755  continue;
1756  return instance.emitOpError()
1757  << "result type for " << primitivePorts[i].name << " must be "
1758  << expectedType << ", but got " << resultType;
1759  }
1760  return success();
1761 }
1762 
1763 LogicalResult
1764 PrimitiveOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1765  Operation *op = *this;
1766  auto module = op->getParentOfType<ModuleOp>();
1767  Operation *referencedPrimitive =
1768  symbolTable.lookupNearestSymbolFrom(module, getPrimitiveNameAttr());
1769  if (referencedPrimitive == nullptr)
1770  return emitError() << "referencing primitive: '" << getPrimitiveName()
1771  << "', which does not exist.";
1772 
1773  Operation *shadowedPrimitiveName =
1774  symbolTable.lookupNearestSymbolFrom(module, getSymNameAttr());
1775  if (shadowedPrimitiveName != nullptr)
1776  return emitError() << "instance symbol: '" << instanceName()
1777  << "' is already a symbol for another primitive.";
1778 
1779  // Verify the referenced primitive is not instantiating itself.
1780  auto parentPrimitive = op->getParentOfType<hw::HWModuleExternOp>();
1781  if (parentPrimitive == referencedPrimitive)
1782  return emitError() << "recursive instantiation of its parent primitive: '"
1783  << getPrimitiveName() << "'";
1784 
1785  assert(isa<hw::HWModuleExternOp>(referencedPrimitive) &&
1786  "Should be a HardwareModuleExternOp.");
1787 
1788  return verifyPrimitiveOpType(*this,
1789  cast<hw::HWModuleExternOp>(referencedPrimitive));
1790 }
1791 
1792 /// Provide meaningful names to the result values of an PrimitiveOp.
1794  getCellAsmResultNames(setNameFn, *this, this->portNames());
1795 }
1796 
1797 SmallVector<StringRef> PrimitiveOp::portNames() {
1798  SmallVector<StringRef> portNames;
1799  auto ports = getReferencedPrimitive().getPortList();
1800  for (auto port : ports)
1801  portNames.push_back(port.name.getValue());
1802 
1803  return portNames;
1804 }
1805 
1807  switch (direction) {
1809  return Direction::Input;
1811  return Direction::Output;
1813  llvm_unreachable("InOut ports not supported by Calyx");
1814  }
1815  llvm_unreachable("Impossible port type");
1816 }
1817 
1818 SmallVector<Direction> PrimitiveOp::portDirections() {
1819  SmallVector<Direction> portDirections;
1820  auto ports = getReferencedPrimitive().getPortList();
1821  for (hw::PortInfo port : ports)
1822  portDirections.push_back(convertHWDirectionToCalyx(port.dir));
1823  return portDirections;
1824 }
1825 
1826 bool PrimitiveOp::isCombinational() { return false; }
1827 
1828 /// Returns a new DictionaryAttr containing only the calyx dialect attrs
1829 /// in the input DictionaryAttr. Also strips the 'calyx.' prefix from these
1830 /// attrs.
1831 static DictionaryAttr cleanCalyxPortAttrs(OpBuilder builder,
1832  DictionaryAttr dict) {
1833  if (!dict) {
1834  return dict;
1835  }
1836  llvm::SmallVector<NamedAttribute> attrs;
1837  for (NamedAttribute attr : dict) {
1838  Dialect *dialect = attr.getNameDialect();
1839  if (dialect == nullptr || !isa<CalyxDialect>(*dialect))
1840  continue;
1841  StringRef name = attr.getName().strref();
1842  StringAttr newName = builder.getStringAttr(std::get<1>(name.split(".")));
1843  attr.setName(newName);
1844  attrs.push_back(attr);
1845  }
1846  return builder.getDictionaryAttr(attrs);
1847 }
1848 
1849 // Grabs calyx port attributes from the HWModuleExternOp arg/result attributes.
1850 SmallVector<DictionaryAttr> PrimitiveOp::portAttributes() {
1851  SmallVector<DictionaryAttr> portAttributes;
1852  OpBuilder builder(getContext());
1853  hw::HWModuleExternOp prim = getReferencedPrimitive();
1854  auto argAttrs = prim.getAllInputAttrs();
1855  auto resAttrs = prim.getAllOutputAttrs();
1856  for (auto a : argAttrs)
1857  portAttributes.push_back(
1858  cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1859  for (auto a : resAttrs)
1860  portAttributes.push_back(
1861  cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1862  return portAttributes;
1863 }
1864 
1865 /// Parse an parameter list if present. Same format as HW dialect.
1866 /// module-parameter-list ::= `<` parameter-decl (`,` parameter-decl)* `>`
1867 /// parameter-decl ::= identifier `:` type
1868 /// parameter-decl ::= identifier `:` type `=` attribute
1869 ///
1870 static ParseResult parseParameterList(OpAsmParser &parser,
1871  SmallVector<Attribute> &parameters) {
1872 
1873  return parser.parseCommaSeparatedList(
1874  OpAsmParser::Delimiter::OptionalLessGreater, [&]() {
1875  std::string name;
1876  Type type;
1877  Attribute value;
1878 
1879  if (parser.parseKeywordOrString(&name) || parser.parseColonType(type))
1880  return failure();
1881 
1882  // Parse the default value if present.
1883  if (succeeded(parser.parseOptionalEqual())) {
1884  if (parser.parseAttribute(value, type))
1885  return failure();
1886  }
1887 
1888  auto &builder = parser.getBuilder();
1889  parameters.push_back(hw::ParamDeclAttr::get(
1890  builder.getContext(), builder.getStringAttr(name), type, value));
1891  return success();
1892  });
1893 }
1894 
1895 /// Shim to also use this for the InstanceOp custom parser.
1896 static ParseResult parseParameterList(OpAsmParser &parser,
1897  ArrayAttr &parameters) {
1898  SmallVector<Attribute> parseParameters;
1899  if (failed(parseParameterList(parser, parseParameters)))
1900  return failure();
1901 
1902  parameters = ArrayAttr::get(parser.getContext(), parseParameters);
1903 
1904  return success();
1905 }
1906 
1907 /// Print a parameter list for a module or instance. Same format as HW dialect.
1908 static void printParameterList(OpAsmPrinter &p, Operation *op,
1909  ArrayAttr parameters) {
1910  if (parameters.empty())
1911  return;
1912 
1913  p << '<';
1914  llvm::interleaveComma(parameters, p, [&](Attribute param) {
1915  auto paramAttr = cast<hw::ParamDeclAttr>(param);
1916  p << paramAttr.getName().getValue() << ": " << paramAttr.getType();
1917  if (auto value = paramAttr.getValue()) {
1918  p << " = ";
1919  p.printAttributeWithoutType(value);
1920  }
1921  });
1922  p << '>';
1923 }
1924 
1925 //===----------------------------------------------------------------------===//
1926 // GroupGoOp
1927 //===----------------------------------------------------------------------===//
1928 
1929 LogicalResult GroupGoOp::verify() { return verifyNotComplexSource(*this); }
1930 
1931 /// Provide meaningful names to the result value of a GroupGoOp.
1932 void GroupGoOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
1933  auto parent = (*this)->getParentOfType<GroupOp>();
1934  StringRef name = parent.getSymName();
1935  std::string resultName = name.str() + ".go";
1936  setNameFn(getResult(), resultName);
1937 }
1938 
1939 void GroupGoOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
1940 
1941 ParseResult GroupGoOp::parse(OpAsmParser &parser, OperationState &result) {
1942  if (parseGroupPort(parser, result))
1943  return failure();
1944 
1945  result.addTypes(parser.getBuilder().getI1Type());
1946  return success();
1947 }
1948 
1949 //===----------------------------------------------------------------------===//
1950 // GroupDoneOp
1951 //===----------------------------------------------------------------------===//
1952 
1953 LogicalResult GroupDoneOp::verify() {
1954  Operation *srcOp = getSrc().getDefiningOp();
1955  Value optionalGuard = getGuard();
1956  Operation *guardOp = optionalGuard ? optionalGuard.getDefiningOp() : nullptr;
1957  bool noGuard = (guardOp == nullptr);
1958 
1959  if (srcOp == nullptr)
1960  // This is a port of the parent component.
1961  return success();
1962 
1963  if (isa<hw::ConstantOp>(srcOp) && (noGuard || isa<hw::ConstantOp>(guardOp)))
1964  return emitOpError() << "with constant source"
1965  << (noGuard ? "" : " and constant guard")
1966  << ". This should be a combinational group.";
1967 
1968  return verifyNotComplexSource(*this);
1969 }
1970 
1971 void GroupDoneOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
1972 
1973 ParseResult GroupDoneOp::parse(OpAsmParser &parser, OperationState &result) {
1974  return parseGroupPort(parser, result);
1975 }
1976 
1977 //===----------------------------------------------------------------------===//
1978 // ConstantOp
1979 //===----------------------------------------------------------------------===//
1980 void ConstantOp::getAsmResultNames(
1981  function_ref<void(Value, StringRef)> setNameFn) {
1982  if (isa<FloatAttr>(getValue())) {
1983  setNameFn(getResult(), "cst");
1984  return;
1985  }
1986  auto intCst = llvm::dyn_cast<IntegerAttr>(getValue());
1987  auto intType = llvm::dyn_cast<IntegerType>(getType());
1988 
1989  // Sugar i1 constants with 'true' and 'false'.
1990  if (intType && intType.getWidth() == 1)
1991  return setNameFn(getResult(), intCst.getInt() > 0 ? "true" : "false");
1992 
1993  // Otherwise, build a complex name with the value and type.
1994  SmallString<32> specialNameBuffer;
1995  llvm::raw_svector_ostream specialName(specialNameBuffer);
1996  specialName << 'c' << intCst.getValue();
1997  if (intType)
1998  specialName << '_' << getType();
1999  setNameFn(getResult(), specialName.str());
2000 }
2001 
2002 LogicalResult ConstantOp::verify() {
2003  auto type = getType();
2004  assert(isa<IntegerType>(type) && "must be an IntegerType");
2005  // The value's bit width must match the return type bitwidth.
2006  if (auto valTyBitWidth = getValue().getType().getIntOrFloatBitWidth();
2007  valTyBitWidth != type.getIntOrFloatBitWidth()) {
2008  return emitOpError() << "value type bit width" << valTyBitWidth
2009  << " must match return type: "
2010  << type.getIntOrFloatBitWidth();
2011  }
2012  // Integer values must be signless.
2013  if (llvm::isa<IntegerType>(type) &&
2014  !llvm::cast<IntegerType>(type).isSignless())
2015  return emitOpError("integer return type must be signless");
2016  // Any float or integers attribute are acceptable.
2017  if (!llvm::isa<IntegerAttr, FloatAttr>(getValue())) {
2018  return emitOpError("value must be an integer or float attribute");
2019  }
2020 
2021  return success();
2022 }
2023 
2024 OpFoldResult calyx::ConstantOp::fold(FoldAdaptor adaptor) {
2025  return getValueAttr();
2026 }
2027 
2028 void calyx::ConstantOp::build(OpBuilder &builder, OperationState &state,
2029  StringRef symName, Attribute attr, Type type) {
2030  state.addAttribute(SymbolTable::getSymbolAttrName(),
2031  builder.getStringAttr(symName));
2032  state.addAttribute("value", attr);
2033  SmallVector<Type> types;
2034  types.push_back(type); // Out
2035  state.addTypes(types);
2036 }
2037 
2038 SmallVector<StringRef> ConstantOp::portNames() { return {"out"}; }
2039 
2040 SmallVector<Direction> ConstantOp::portDirections() { return {Output}; }
2041 
2042 SmallVector<DictionaryAttr> ConstantOp::portAttributes() {
2043  return {DictionaryAttr::get(getContext())};
2044 }
2045 
2046 bool ConstantOp::isCombinational() { return true; }
2047 
2048 //===----------------------------------------------------------------------===//
2049 // RegisterOp
2050 //===----------------------------------------------------------------------===//
2051 
2052 /// Provide meaningful names to the result values of a RegisterOp.
2054  getCellAsmResultNames(setNameFn, *this, this->portNames());
2055 }
2056 
2057 SmallVector<StringRef> RegisterOp::portNames() {
2058  return {"in", "write_en", clkPort, resetPort, "out", donePort};
2059 }
2060 
2061 SmallVector<Direction> RegisterOp::portDirections() {
2062  return {Input, Input, Input, Input, Output, Output};
2063 }
2064 
2065 SmallVector<DictionaryAttr> RegisterOp::portAttributes() {
2066  MLIRContext *context = getContext();
2067  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2068  NamedAttrList writeEn, clk, reset, done;
2069  writeEn.append(goPort, isSet);
2070  clk.append(clkPort, isSet);
2071  reset.append(resetPort, isSet);
2072  done.append(donePort, isSet);
2073  return {
2074  DictionaryAttr::get(context), // In
2075  writeEn.getDictionary(context), // Write enable
2076  clk.getDictionary(context), // Clk
2077  reset.getDictionary(context), // Reset
2078  DictionaryAttr::get(context), // Out
2079  done.getDictionary(context) // Done
2080  };
2081 }
2082 
2083 bool RegisterOp::isCombinational() { return false; }
2084 
2085 //===----------------------------------------------------------------------===//
2086 // MemoryOp
2087 //===----------------------------------------------------------------------===//
2088 
2089 /// Provide meaningful names to the result values of a MemoryOp.
2091  getCellAsmResultNames(setNameFn, *this, this->portNames());
2092 }
2093 
2094 SmallVector<StringRef> MemoryOp::portNames() {
2095  SmallVector<StringRef> portNames;
2096  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2097  auto nameAttr =
2098  StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2099  portNames.push_back(nameAttr.getValue());
2100  }
2101  portNames.append({"write_data", "write_en", clkPort, "read_data", donePort});
2102  return portNames;
2103 }
2104 
2105 SmallVector<Direction> MemoryOp::portDirections() {
2106  SmallVector<Direction> portDirections;
2107  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2108  portDirections.push_back(Input);
2109  portDirections.append({Input, Input, Input, Output, Output});
2110  return portDirections;
2111 }
2112 
2113 SmallVector<DictionaryAttr> MemoryOp::portAttributes() {
2114  SmallVector<DictionaryAttr> portAttributes;
2115  MLIRContext *context = getContext();
2116  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2117  portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2118 
2119  // Use a boolean to indicate this attribute is used.
2120  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2121  NamedAttrList writeEn, clk, reset, done;
2122  writeEn.append(goPort, isSet);
2123  clk.append(clkPort, isSet);
2124  done.append(donePort, isSet);
2125  portAttributes.append({DictionaryAttr::get(context), // In
2126  writeEn.getDictionary(context), // Write enable
2127  clk.getDictionary(context), // Clk
2128  DictionaryAttr::get(context), // Out
2129  done.getDictionary(context)} // Done
2130  );
2131  return portAttributes;
2132 }
2133 
2134 void MemoryOp::build(OpBuilder &builder, OperationState &state,
2135  StringRef instanceName, int64_t width,
2136  ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2137  state.addAttribute(SymbolTable::getSymbolAttrName(),
2138  builder.getStringAttr(instanceName));
2139  state.addAttribute("width", builder.getI64IntegerAttr(width));
2140  state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2141  state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2142  SmallVector<Type> types;
2143  for (int64_t size : addrSizes)
2144  types.push_back(builder.getIntegerType(size)); // Addresses
2145  types.push_back(builder.getIntegerType(width)); // Write data
2146  types.push_back(builder.getI1Type()); // Write enable
2147  types.push_back(builder.getI1Type()); // Clk
2148  types.push_back(builder.getIntegerType(width)); // Read data
2149  types.push_back(builder.getI1Type()); // Done
2150  state.addTypes(types);
2151 }
2152 
2153 LogicalResult MemoryOp::verify() {
2154  ArrayRef<Attribute> opSizes = getSizes().getValue();
2155  ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2156  size_t numDims = getSizes().size();
2157  size_t numAddrs = getAddrSizes().size();
2158  if (numDims != numAddrs)
2159  return emitOpError("mismatched number of dimensions (")
2160  << numDims << ") and address sizes (" << numAddrs << ")";
2161 
2162  size_t numExtraPorts = 5; // write data/enable, clk, and read data/done.
2163  if (getNumResults() != numAddrs + numExtraPorts)
2164  return emitOpError("incorrect number of address ports, expected ")
2165  << numAddrs;
2166 
2167  for (size_t i = 0; i < numDims; ++i) {
2168  int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2169  int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2170  if (llvm::Log2_64_Ceil(size) > addrSize)
2171  return emitOpError("address size (")
2172  << addrSize << ") for dimension " << i
2173  << " can't address the entire range (" << size << ")";
2174  }
2175 
2176  return success();
2177 }
2178 
2179 //===----------------------------------------------------------------------===//
2180 // SeqMemoryOp
2181 //===----------------------------------------------------------------------===//
2182 
2183 /// Provide meaningful names to the result values of a SeqMemoryOp.
2185  getCellAsmResultNames(setNameFn, *this, this->portNames());
2186 }
2187 
2188 SmallVector<StringRef> SeqMemoryOp::portNames() {
2189  SmallVector<StringRef> portNames;
2190  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2191  auto nameAttr =
2192  StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2193  portNames.push_back(nameAttr.getValue());
2194  }
2195  portNames.append({clkPort, "reset", "content_en", "write_en", "write_data",
2196  "read_data", "done"});
2197  return portNames;
2198 }
2199 
2200 SmallVector<Direction> SeqMemoryOp::portDirections() {
2201  SmallVector<Direction> portDirections;
2202  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2203  portDirections.push_back(Input);
2204  portDirections.append({Input, Input, Input, Input, Input, Output, Output});
2205  return portDirections;
2206 }
2207 
2208 SmallVector<DictionaryAttr> SeqMemoryOp::portAttributes() {
2209  SmallVector<DictionaryAttr> portAttributes;
2210  MLIRContext *context = getContext();
2211  for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2212  portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2213 
2214  OpBuilder builder(context);
2215  // Use a boolean to indicate this attribute is used.
2216  IntegerAttr isSet = IntegerAttr::get(builder.getIndexType(), 1);
2217  IntegerAttr isTwo = IntegerAttr::get(builder.getIndexType(), 2);
2218  NamedAttrList done, clk, reset, contentEn;
2219  done.append(donePort, isSet);
2220  clk.append(clkPort, isSet);
2221  clk.append(resetPort, isSet);
2222  contentEn.append(goPort, isTwo);
2223  portAttributes.append({clk.getDictionary(context), // Clk
2224  reset.getDictionary(context), // Reset
2225  contentEn.getDictionary(context), // Content enable
2226  DictionaryAttr::get(context), // Write enable
2227  DictionaryAttr::get(context), // Write data
2228  DictionaryAttr::get(context), // Read data
2229  done.getDictionary(context)} // Done
2230  );
2231  return portAttributes;
2232 }
2233 
2234 void SeqMemoryOp::build(OpBuilder &builder, OperationState &state,
2235  StringRef instanceName, int64_t width,
2236  ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2237  state.addAttribute(SymbolTable::getSymbolAttrName(),
2238  builder.getStringAttr(instanceName));
2239  state.addAttribute("width", builder.getI64IntegerAttr(width));
2240  state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2241  state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2242  SmallVector<Type> types;
2243  for (int64_t size : addrSizes)
2244  types.push_back(builder.getIntegerType(size)); // Addresses
2245  types.push_back(builder.getI1Type()); // Clk
2246  types.push_back(builder.getI1Type()); // Reset
2247  types.push_back(builder.getI1Type()); // Content enable
2248  types.push_back(builder.getI1Type()); // Write enable
2249  types.push_back(builder.getIntegerType(width)); // Write data
2250  types.push_back(builder.getIntegerType(width)); // Read data
2251  types.push_back(builder.getI1Type()); // Done
2252  state.addTypes(types);
2253 }
2254 
2255 LogicalResult SeqMemoryOp::verify() {
2256  ArrayRef<Attribute> opSizes = getSizes().getValue();
2257  ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2258  size_t numDims = getSizes().size();
2259  size_t numAddrs = getAddrSizes().size();
2260  if (numDims != numAddrs)
2261  return emitOpError("mismatched number of dimensions (")
2262  << numDims << ") and address sizes (" << numAddrs << ")";
2263 
2264  size_t numExtraPorts =
2265  7; // write data/enable, clk, reset, read data, content enable, and done.
2266  if (getNumResults() != numAddrs + numExtraPorts)
2267  return emitOpError("incorrect number of address ports, expected ")
2268  << numAddrs;
2269 
2270  for (size_t i = 0; i < numDims; ++i) {
2271  int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2272  int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2273  if (llvm::Log2_64_Ceil(size) > addrSize)
2274  return emitOpError("address size (")
2275  << addrSize << ") for dimension " << i
2276  << " can't address the entire range (" << size << ")";
2277  }
2278 
2279  return success();
2280 }
2281 
2282 //===----------------------------------------------------------------------===//
2283 // EnableOp
2284 //===----------------------------------------------------------------------===//
2285 LogicalResult EnableOp::verify() {
2286  auto component = (*this)->getParentOfType<ComponentOp>();
2287  auto wiresOp = component.getWiresOp();
2288  StringRef name = getGroupName();
2289 
2290  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(name);
2291  if (!groupOp)
2292  return emitOpError() << "with group '" << name
2293  << "', which does not exist.";
2294 
2295  if (isa<CombGroupOp>(groupOp))
2296  return emitOpError() << "with group '" << name
2297  << "', which is a combinational group.";
2298 
2299  return success();
2300 }
2301 
2302 //===----------------------------------------------------------------------===//
2303 // IfOp
2304 //===----------------------------------------------------------------------===//
2305 
2306 LogicalResult IfOp::verify() {
2307  std::optional<StringRef> optGroupName = getGroupName();
2308  if (!optGroupName) {
2309  // No combinational group was provided.
2310  return success();
2311  }
2312  auto component = (*this)->getParentOfType<ComponentOp>();
2313  WiresOp wiresOp = component.getWiresOp();
2314  StringRef groupName = *optGroupName;
2315  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2316  if (!groupOp)
2317  return emitOpError() << "with group '" << groupName
2318  << "', which does not exist.";
2319 
2320  if (isa<GroupOp>(groupOp))
2321  return emitOpError() << "with group '" << groupName
2322  << "', which is not a combinational group.";
2323 
2324  if (failed(groupOp.drivesPort(getCond())))
2325  return emitError() << "with conditional op: '"
2326  << valueName(component, getCond())
2327  << "' expected to be driven from group: '" << groupName
2328  << "' but no driver was found.";
2329 
2330  return success();
2331 }
2332 
2333 /// Returns the last EnableOp within the child tree of 'parentSeqOp' or
2334 /// `parentStaticSeqOp.` If no EnableOp was found (e.g. a "calyx.par" operation
2335 /// is present), returns None.
2336 template <typename OpTy>
2337 static std::optional<EnableOp> getLastEnableOp(OpTy parent) {
2338  static_assert(IsAny<OpTy, SeqOp, StaticSeqOp>(),
2339  "Should be a StaticSeqOp or SeqOp.");
2340  if (parent.getBodyBlock()->empty())
2341  return std::nullopt;
2342  auto &lastOp = parent.getBodyBlock()->back();
2343  if (auto enableOp = dyn_cast<EnableOp>(lastOp))
2344  return enableOp;
2345  if (auto seqOp = dyn_cast<SeqOp>(lastOp))
2346  return getLastEnableOp(seqOp);
2347  if (auto staticSeqOp = dyn_cast<StaticSeqOp>(lastOp))
2348  return getLastEnableOp(staticSeqOp);
2349 
2350  return std::nullopt;
2351 }
2352 
2353 /// Returns a mapping of {enabled Group name, EnableOp} for all EnableOps within
2354 /// the immediate ParOp's body.
2355 template <typename OpTy>
2356 static llvm::StringMap<EnableOp> getAllEnableOpsInImmediateBody(OpTy parent) {
2357  static_assert(IsAny<OpTy, ParOp, StaticParOp>(),
2358  "Should be a StaticParOp or ParOp.");
2359 
2360  llvm::StringMap<EnableOp> enables;
2361  Block *body = parent.getBodyBlock();
2362  for (EnableOp op : body->getOps<EnableOp>())
2363  enables.insert(std::pair(op.getGroupName(), op));
2364 
2365  return enables;
2366 }
2367 
2368 /// Checks preconditions for the common tail pattern. This canonicalization is
2369 /// stringent about not entering nested control operations, as this may cause
2370 /// unintentional changes in behavior.
2371 /// We only look for two cases: (1) both regions are ParOps, and
2372 /// (2) both regions are SeqOps. The case when these are different, e.g. ParOp
2373 /// and SeqOp, will only produce less optimal code, or even worse, change the
2374 /// behavior.
2375 template <typename IfOpTy, typename TailOpTy>
2376 static bool hasCommonTailPatternPreConditions(IfOpTy op) {
2377  static_assert(IsAny<TailOpTy, SeqOp, ParOp, StaticSeqOp, StaticParOp>(),
2378  "Should be a SeqOp, ParOp, StaticSeqOp, or StaticParOp.");
2379  static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2380  "Should be a IfOp or StaticIfOp.");
2381 
2382  if (!op.thenBodyExists() || !op.elseBodyExists())
2383  return false;
2384  if (op.getThenBody()->empty() || op.getElseBody()->empty())
2385  return false;
2386 
2387  Block *thenBody = op.getThenBody(), *elseBody = op.getElseBody();
2388  return isa<TailOpTy>(thenBody->front()) && isa<TailOpTy>(elseBody->front());
2389 }
2390 
2391 /// seq {
2392 /// if %a with @G { if %a with @G {
2393 /// seq { ... calyx.enable @A } seq { ... }
2394 /// else { -> } else {
2395 /// seq { ... calyx.enable @A } seq { ... }
2396 /// } }
2397 /// calyx.enable @A
2398 /// }
2399 template <typename IfOpTy, typename SeqOpTy>
2400 static LogicalResult commonTailPatternWithSeq(IfOpTy ifOp,
2401  PatternRewriter &rewriter) {
2402  static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2403  "Should be an IfOp or StaticIfOp.");
2404  static_assert(IsAny<SeqOpTy, SeqOp, StaticSeqOp>(),
2405  "Branches should be checking for an SeqOp or StaticSeqOp");
2406  if (!hasCommonTailPatternPreConditions<IfOpTy, SeqOpTy>(ifOp))
2407  return failure();
2408  auto thenControl = cast<SeqOpTy>(ifOp.getThenBody()->front()),
2409  elseControl = cast<SeqOpTy>(ifOp.getElseBody()->front());
2410 
2411  std::optional<EnableOp> lastThenEnableOp = getLastEnableOp(thenControl),
2412  lastElseEnableOp = getLastEnableOp(elseControl);
2413 
2414  if (!lastThenEnableOp || !lastElseEnableOp)
2415  return failure();
2416  if (lastThenEnableOp->getGroupName() != lastElseEnableOp->getGroupName())
2417  return failure();
2418 
2419  // Place the IfOp and pulled EnableOp inside a sequential region, in case
2420  // this IfOp is nested in a ParOp. This avoids unintentionally
2421  // parallelizing the pulled out EnableOps.
2422  rewriter.setInsertionPointAfter(ifOp);
2423  SeqOpTy seqOp = rewriter.create<SeqOpTy>(ifOp.getLoc());
2424  Block *body = seqOp.getBodyBlock();
2425  ifOp->remove();
2426  body->push_back(ifOp);
2427  rewriter.setInsertionPointToEnd(body);
2428  rewriter.create<EnableOp>(seqOp.getLoc(), lastThenEnableOp->getGroupName());
2429 
2430  // Erase the common EnableOp from the Then and Else regions.
2431  rewriter.eraseOp(*lastThenEnableOp);
2432  rewriter.eraseOp(*lastElseEnableOp);
2433  return success();
2434 }
2435 
2436 /// if %a with @G { par {
2437 /// par { if %a with @G {
2438 /// ... par { ... }
2439 /// calyx.enable @A } else {
2440 /// calyx.enable @B -> par { ... }
2441 /// } }
2442 /// } else { calyx.enable @A
2443 /// par { calyx.enable @B
2444 /// ... }
2445 /// calyx.enable @A
2446 /// calyx.enable @B
2447 /// }
2448 /// }
2449 template <typename OpTy, typename ParOpTy>
2450 static LogicalResult commonTailPatternWithPar(OpTy controlOp,
2451  PatternRewriter &rewriter) {
2452  static_assert(IsAny<OpTy, IfOp, StaticIfOp>(),
2453  "Should be an IfOp or StaticIfOp.");
2454  static_assert(IsAny<ParOpTy, ParOp, StaticParOp>(),
2455  "Branches should be checking for an ParOp or StaticParOp");
2456  if (!hasCommonTailPatternPreConditions<OpTy, ParOpTy>(controlOp))
2457  return failure();
2458  auto thenControl = cast<ParOpTy>(controlOp.getThenBody()->front()),
2459  elseControl = cast<ParOpTy>(controlOp.getElseBody()->front());
2460 
2461  llvm::StringMap<EnableOp> a = getAllEnableOpsInImmediateBody(thenControl),
2462  b = getAllEnableOpsInImmediateBody(elseControl);
2463  // Compute the intersection between `A` and `B`.
2464  SmallVector<StringRef> groupNames;
2465  for (auto aIndex = a.begin(); aIndex != a.end(); ++aIndex) {
2466  StringRef groupName = aIndex->getKey();
2467  auto bIndex = b.find(groupName);
2468  if (bIndex == b.end())
2469  continue;
2470  // This is also an element in B.
2471  groupNames.push_back(groupName);
2472  // Since these are being pulled out, erase them.
2473  rewriter.eraseOp(aIndex->getValue());
2474  rewriter.eraseOp(bIndex->getValue());
2475  }
2476 
2477  // Place the IfOp and EnableOp(s) inside a parallel region, in case this
2478  // IfOp is nested in a SeqOp. This avoids unintentionally sequentializing
2479  // the pulled out EnableOps.
2480  rewriter.setInsertionPointAfter(controlOp);
2481 
2482  ParOpTy parOp = rewriter.create<ParOpTy>(controlOp.getLoc());
2483  Block *body = parOp.getBodyBlock();
2484  controlOp->remove();
2485  body->push_back(controlOp);
2486  // Pull out the intersection between these two sets, and erase their
2487  // counterparts in the Then and Else regions.
2488  rewriter.setInsertionPointToEnd(body);
2489  for (StringRef groupName : groupNames)
2490  rewriter.create<EnableOp>(parOp.getLoc(), groupName);
2491 
2492  return success();
2493 }
2494 
2495 /// This pattern checks for one of two cases that will lead to IfOp deletion:
2496 /// (1) Then and Else bodies are both empty.
2497 /// (2) Then body is empty and Else body does not exist.
2500  LogicalResult matchAndRewrite(IfOp ifOp,
2501  PatternRewriter &rewriter) const override {
2502  if (!ifOp.getThenBody()->empty())
2503  return failure();
2504  if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2505  return failure();
2506 
2507  eraseControlWithGroupAndConditional(ifOp, rewriter);
2508 
2509  return success();
2510  }
2511 };
2512 
2513 void IfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2514  MLIRContext *context) {
2515  patterns.add<EmptyIfBody>(context);
2516  patterns.add(commonTailPatternWithPar<IfOp, ParOp>);
2517  patterns.add(commonTailPatternWithSeq<IfOp, SeqOp>);
2518 }
2519 
2520 //===----------------------------------------------------------------------===//
2521 // StaticIfOp
2522 //===----------------------------------------------------------------------===//
2523 LogicalResult StaticIfOp::verify() {
2524  if (elseBodyExists()) {
2525  auto *elseBod = getElseBody();
2526  auto &elseOps = elseBod->getOperations();
2527  // should only have one Operation, static, in the else branch
2528  for (Operation &op : elseOps) {
2529  if (!isStaticControl(&op)) {
2530  return op.emitOpError(
2531  "static if's else branch has non static control within it");
2532  }
2533  }
2534  }
2535 
2536  auto *thenBod = getThenBody();
2537  auto &thenOps = thenBod->getOperations();
2538  for (Operation &op : thenOps) {
2539  // should only have one, static, Operation in the then branch
2540  if (!isStaticControl(&op)) {
2541  return op.emitOpError(
2542  "static if's then branch has non static control within it");
2543  }
2544  }
2545 
2546  return success();
2547 }
2548 
2549 /// This pattern checks for one of two cases that will lead to StaticIfOp
2550 /// deletion: (1) Then and Else bodies are both empty. (2) Then body is empty
2551 /// and Else body does not exist.
2554  LogicalResult matchAndRewrite(StaticIfOp ifOp,
2555  PatternRewriter &rewriter) const override {
2556  if (!ifOp.getThenBody()->empty())
2557  return failure();
2558  if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2559  return failure();
2560 
2561  eraseControlWithConditional(ifOp, rewriter);
2562 
2563  return success();
2564  }
2565 };
2566 
2567 void StaticIfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2568  MLIRContext *context) {
2569  patterns.add<EmptyStaticIfBody>(context);
2570  patterns.add(commonTailPatternWithPar<StaticIfOp, StaticParOp>);
2571  patterns.add(commonTailPatternWithSeq<StaticIfOp, StaticSeqOp>);
2572 }
2573 
2574 //===----------------------------------------------------------------------===//
2575 // WhileOp
2576 //===----------------------------------------------------------------------===//
2577 LogicalResult WhileOp::verify() {
2578  auto component = (*this)->getParentOfType<ComponentOp>();
2579  auto wiresOp = component.getWiresOp();
2580 
2581  std::optional<StringRef> optGroupName = getGroupName();
2582  if (!optGroupName) {
2583  /// No combinational group was provided
2584  return success();
2585  }
2586  StringRef groupName = *optGroupName;
2587  auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2588  if (!groupOp)
2589  return emitOpError() << "with group '" << groupName
2590  << "', which does not exist.";
2591 
2592  if (isa<GroupOp>(groupOp))
2593  return emitOpError() << "with group '" << groupName
2594  << "', which is not a combinational group.";
2595 
2596  if (failed(groupOp.drivesPort(getCond())))
2597  return emitError() << "conditional op: '" << valueName(component, getCond())
2598  << "' expected to be driven from group: '" << groupName
2599  << "' but no driver was found.";
2600 
2601  return success();
2602 }
2603 
2604 LogicalResult WhileOp::canonicalize(WhileOp whileOp,
2605  PatternRewriter &rewriter) {
2606  if (whileOp.getBodyBlock()->empty()) {
2607  eraseControlWithGroupAndConditional(whileOp, rewriter);
2608  return success();
2609  }
2610 
2611  return failure();
2612 }
2613 
2614 //===----------------------------------------------------------------------===//
2615 // StaticRepeatOp
2616 //===----------------------------------------------------------------------===//
2617 LogicalResult StaticRepeatOp::verify() {
2618  for (auto &&bodyOp : (*this).getRegion().front()) {
2619  // there should only be one bodyOp for each StaticRepeatOp
2620  if (!isStaticControl(&bodyOp)) {
2621  return bodyOp.emitOpError(
2622  "static repeat has non static control within it");
2623  }
2624  }
2625 
2626  return success();
2627 }
2628 
2629 template <typename OpTy>
2630 static LogicalResult zeroRepeat(OpTy op, PatternRewriter &rewriter) {
2631  static_assert(IsAny<OpTy, RepeatOp, StaticRepeatOp>(),
2632  "Should be a RepeatOp or StaticPRepeatOp");
2633  if (op.getCount() == 0) {
2634  Block *controlBody = op.getBodyBlock();
2635  for (auto &op : make_early_inc_range(*controlBody))
2636  op.erase();
2637 
2638  rewriter.eraseOp(op);
2639  return success();
2640  }
2641 
2642  return failure();
2643 }
2644 
2645 void StaticRepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2646  MLIRContext *context) {
2647  patterns.add(emptyControl<StaticRepeatOp>);
2648  patterns.add(zeroRepeat<StaticRepeatOp>);
2649 }
2650 
2651 //===----------------------------------------------------------------------===//
2652 // RepeatOp
2653 //===----------------------------------------------------------------------===//
2654 void RepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2655  MLIRContext *context) {
2656  patterns.add(emptyControl<RepeatOp>);
2657  patterns.add(zeroRepeat<RepeatOp>);
2658 }
2659 
2660 //===----------------------------------------------------------------------===//
2661 // InvokeOp
2662 //===----------------------------------------------------------------------===//
2663 
2664 // Parse the parameter list of invoke.
2665 static ParseResult
2666 parseParameterList(OpAsmParser &parser, OperationState &result,
2667  SmallVectorImpl<OpAsmParser::UnresolvedOperand> &ports,
2668  SmallVectorImpl<OpAsmParser::UnresolvedOperand> &inputs,
2669  SmallVectorImpl<Attribute> &portNames,
2670  SmallVectorImpl<Attribute> &inputNames,
2671  SmallVectorImpl<Type> &types) {
2672  OpAsmParser::UnresolvedOperand port;
2673  OpAsmParser::UnresolvedOperand input;
2674  Type type;
2675  auto parseParameter = [&]() -> ParseResult {
2676  if (parser.parseOperand(port) || parser.parseEqual() ||
2677  parser.parseOperand(input))
2678  return failure();
2679  ports.push_back(port);
2680  portNames.push_back(StringAttr::get(parser.getContext(), port.name));
2681  inputs.push_back(input);
2682  inputNames.push_back(StringAttr::get(parser.getContext(), input.name));
2683  return success();
2684  };
2685  if (parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2686  parseParameter))
2687  return failure();
2688  if (parser.parseArrow())
2689  return failure();
2690  auto parseType = [&]() -> ParseResult {
2691  if (parser.parseType(type))
2692  return failure();
2693  types.push_back(type);
2694  return success();
2695  };
2696  return parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2697  parseType);
2698 }
2699 
2700 ParseResult InvokeOp::parse(OpAsmParser &parser, OperationState &result) {
2701  StringAttr componentName;
2702  SmallVector<OpAsmParser::UnresolvedOperand, 4> ports;
2703  SmallVector<OpAsmParser::UnresolvedOperand, 4> inputs;
2704  SmallVector<Attribute> portNames;
2705  SmallVector<Attribute> inputNames;
2706  SmallVector<Type, 4> types;
2707  if (parser.parseSymbolName(componentName))
2708  return failure();
2709  FlatSymbolRefAttr callee = FlatSymbolRefAttr::get(componentName);
2710  SMLoc loc = parser.getCurrentLocation();
2711 
2712  SmallVector<Attribute, 4> refCells;
2713  if (succeeded(parser.parseOptionalLSquare())) {
2714  if (parser.parseCommaSeparatedList([&]() -> ParseResult {
2715  std::string refCellName;
2716  std::string externalMem;
2717  NamedAttrList refCellAttr;
2718  if (parser.parseKeywordOrString(&refCellName) ||
2719  parser.parseEqual() || parser.parseKeywordOrString(&externalMem))
2720  return failure();
2721  auto externalMemAttr =
2722  SymbolRefAttr::get(parser.getContext(), externalMem);
2723  refCellAttr.append(StringAttr::get(parser.getContext(), refCellName),
2724  externalMemAttr);
2725  refCells.push_back(
2726  DictionaryAttr::get(parser.getContext(), refCellAttr));
2727  return success();
2728  }) ||
2729  parser.parseRSquare())
2730  return failure();
2731  }
2732  result.addAttribute("refCellsMap",
2733  ArrayAttr::get(parser.getContext(), refCells));
2734 
2735  result.addAttribute("callee", callee);
2736  if (parseParameterList(parser, result, ports, inputs, portNames, inputNames,
2737  types))
2738  return failure();
2739  if (parser.resolveOperands(ports, types, loc, result.operands))
2740  return failure();
2741  if (parser.resolveOperands(inputs, types, loc, result.operands))
2742  return failure();
2743  result.addAttribute("portNames",
2744  ArrayAttr::get(parser.getContext(), portNames));
2745  result.addAttribute("inputNames",
2746  ArrayAttr::get(parser.getContext(), inputNames));
2747  return success();
2748 }
2749 
2750 void InvokeOp::print(OpAsmPrinter &p) {
2751  p << " @" << getCallee() << "[";
2752  auto refCellNamesMap = getRefCellsMap();
2753  llvm::interleaveComma(refCellNamesMap, p, [&](Attribute attr) {
2754  auto dictAttr = cast<DictionaryAttr>(attr);
2755  llvm::interleaveComma(dictAttr, p, [&](NamedAttribute namedAttr) {
2756  auto refCellName = namedAttr.getName().str();
2757  auto externalMem =
2758  cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
2759  p << refCellName << " = " << externalMem;
2760  });
2761  });
2762  p << "](";
2763 
2764  auto ports = getPorts();
2765  auto inputs = getInputs();
2766  llvm::interleaveComma(llvm::zip(ports, inputs), p, [&](auto arg) {
2767  p << std::get<0>(arg) << " = " << std::get<1>(arg);
2768  });
2769  p << ") -> (";
2770  llvm::interleaveComma(ports, p, [&](auto port) { p << port.getType(); });
2771  p << ")";
2772 }
2773 
2774 // Check the direction of one of the ports in one of the connections of an
2775 // InvokeOp.
2776 static LogicalResult verifyInvokeOpValue(InvokeOp &op, Value &value,
2777  bool isDestination) {
2778  if (isPort(value))
2779  return verifyPortDirection(op, value, isDestination);
2780  return success();
2781 }
2782 
2783 // Checks if the value comes from complex logic.
2784 static LogicalResult verifyComplexLogic(InvokeOp &op, Value &value) {
2785  // Refer to the above function verifyNotComplexSource for its role.
2786  Operation *operation = value.getDefiningOp();
2787  if (operation == nullptr)
2788  return success();
2789  if (auto *dialect = operation->getDialect(); isa<comb::CombDialect>(dialect))
2790  return failure();
2791  return success();
2792 }
2793 
2794 // Get the go port of the invoked component.
2795 Value InvokeOp::getInstGoValue() {
2796  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2797  Operation *operation = componentOp.lookupSymbol(getCallee());
2798  Value ret = nullptr;
2799  llvm::TypeSwitch<Operation *>(operation)
2800  .Case<RegisterOp>([&](auto op) { ret = operation->getResult(1); })
2801  .Case<MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2802  RemSPipeLibOp, RemUPipeLibOp>(
2803  [&](auto op) { ret = operation->getResult(2); })
2804  .Case<InstanceOp>([&](auto op) {
2805  auto portInfo = op.getReferencedComponent().getPortInfo();
2806  for (auto [portInfo, res] :
2807  llvm::zip(portInfo, operation->getResults())) {
2808  if (portInfo.hasAttribute(goPort))
2809  ret = res;
2810  }
2811  })
2812  .Case<PrimitiveOp>([&](auto op) {
2813  auto moduleExternOp = op.getReferencedPrimitive();
2814  auto argAttrs = moduleExternOp.getAllInputAttrs();
2815  for (auto [attr, res] : llvm::zip(argAttrs, op.getResults())) {
2816  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2817  if (!dictAttr.empty()) {
2818  if (dictAttr.begin()->getName().getValue() == "calyx.go")
2819  ret = res;
2820  }
2821  }
2822  }
2823  });
2824  return ret;
2825 }
2826 
2827 // Get the done port of the invoked component.
2828 Value InvokeOp::getInstDoneValue() {
2829  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2830  Operation *operation = componentOp.lookupSymbol(getCallee());
2831  Value ret = nullptr;
2832  llvm::TypeSwitch<Operation *>(operation)
2833  .Case<RegisterOp, MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2834  RemSPipeLibOp, RemUPipeLibOp>([&](auto op) {
2835  size_t doneIdx = operation->getResults().size() - 1;
2836  ret = operation->getResult(doneIdx);
2837  })
2838  .Case<InstanceOp>([&](auto op) {
2839  InstanceOp instanceOp = cast<InstanceOp>(operation);
2840  auto portInfo = instanceOp.getReferencedComponent().getPortInfo();
2841  for (auto [portInfo, res] :
2842  llvm::zip(portInfo, operation->getResults())) {
2843  if (portInfo.hasAttribute(donePort))
2844  ret = res;
2845  }
2846  })
2847  .Case<PrimitiveOp>([&](auto op) {
2848  PrimitiveOp primOp = cast<PrimitiveOp>(operation);
2849  auto moduleExternOp = primOp.getReferencedPrimitive();
2850  auto resAttrs = moduleExternOp.getAllOutputAttrs();
2851  for (auto [attr, res] : llvm::zip(resAttrs, primOp.getResults())) {
2852  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2853  if (!dictAttr.empty()) {
2854  if (dictAttr.begin()->getName().getValue() == "calyx.done")
2855  ret = res;
2856  }
2857  }
2858  }
2859  });
2860  return ret;
2861 }
2862 
2863 // A helper function that gets the number of go or done ports in
2864 // hw.module.extern.
2865 static size_t
2867  bool isGo) {
2868  size_t ret = 0;
2869  std::string str = isGo ? "calyx.go" : "calyx.done";
2870  for (Attribute attr : moduleExternOp.getAllInputAttrs()) {
2871  if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2872  ret = llvm::count_if(dictAttr, [&](NamedAttribute iter) {
2873  return iter.getName().getValue() == str;
2874  });
2875  }
2876  }
2877  return ret;
2878 }
2879 
2880 LogicalResult InvokeOp::verify() {
2881  ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2882  StringRef callee = getCallee();
2883  Operation *operation = componentOp.lookupSymbol(callee);
2884  // The referenced symbol does not exist.
2885  if (!operation)
2886  return emitOpError() << "with instance '@" << callee
2887  << "', which does not exist.";
2888  // The argument list of invoke is empty.
2889  if (getInputs().empty() && getRefCellsMap().empty()) {
2890  return emitOpError() << "'@" << callee
2891  << "' has zero input and output port connections and "
2892  "has no passing-by-reference cells; "
2893  "expected at least one.";
2894  }
2895  size_t goPortNum = 0, donePortNum = 0;
2896  // They both have a go port and a done port, but the "go" port for
2897  // registers and memrey should be "write_en" port.
2898  llvm::TypeSwitch<Operation *>(operation)
2899  .Case<RegisterOp, DivSPipeLibOp, DivUPipeLibOp, MemoryOp, MultPipeLibOp,
2900  RemSPipeLibOp, RemUPipeLibOp>(
2901  [&](auto op) { goPortNum = 1, donePortNum = 1; })
2902  .Case<InstanceOp>([&](auto op) {
2903  auto portInfo = op.getReferencedComponent().getPortInfo();
2904  for (PortInfo info : portInfo) {
2905  if (info.hasAttribute(goPort))
2906  ++goPortNum;
2907  if (info.hasAttribute(donePort))
2908  ++donePortNum;
2909  }
2910  })
2911  .Case<PrimitiveOp>([&](auto op) {
2912  auto moduleExternOp = op.getReferencedPrimitive();
2913  // Get the number of go ports and done ports by their attrubutes.
2914  goPortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, true);
2915  donePortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, false);
2916  });
2917  // If the number of go ports and done ports is wrong.
2918  if (goPortNum != 1 && donePortNum != 1)
2919  return emitOpError()
2920  << "'@" << callee << "'"
2921  << " is a combinational component and cannot be invoked, which must "
2922  "have single go port and single done port.";
2923 
2924  auto ports = getPorts();
2925  auto inputs = getInputs();
2926  // We have verified earlier that the instance has a go and a done port.
2927  Value goValue = getInstGoValue();
2928  Value doneValue = getInstDoneValue();
2929  for (auto [port, input, portName, inputName] :
2930  llvm::zip(ports, inputs, getPortNames(), getInputNames())) {
2931  // Check the direction of these destination ports.
2932  // 'calyx.invoke' op '@r0' has input '%r.out', which is a source port. The
2933  // inputs are required to be destination ports.
2934  if (failed(verifyInvokeOpValue(*this, port, true)))
2935  return emitOpError() << "'@" << callee << "' has input '"
2936  << cast<StringAttr>(portName).getValue()
2937  << "', which is a source port. The inputs are "
2938  "required to be destination ports.";
2939  // The go port should not appear in the parameter list.
2940  if (port == goValue)
2941  return emitOpError() << "the go or write_en port of '@" << callee
2942  << "' cannot appear here.";
2943  // Check the direction of these source ports.
2944  if (failed(verifyInvokeOpValue(*this, input, false)))
2945  return emitOpError() << "'@" << callee << "' has output '"
2946  << cast<StringAttr>(inputName).getValue()
2947  << "', which is a destination port. The inputs are "
2948  "required to be source ports.";
2949  if (failed(verifyComplexLogic(*this, input)))
2950  return emitOpError() << "'@" << callee << "' has '"
2951  << cast<StringAttr>(inputName).getValue()
2952  << "', which is not a port or constant. Complex "
2953  "logic should be conducted in the guard.";
2954  if (input == doneValue)
2955  return emitOpError() << "the done port of '@" << callee
2956  << "' cannot appear here.";
2957  // Check if the connection uses the callee's port.
2958  if (port.getDefiningOp() != operation && input.getDefiningOp() != operation)
2959  return emitOpError() << "the connection "
2960  << cast<StringAttr>(portName).getValue() << " = "
2961  << cast<StringAttr>(inputName).getValue()
2962  << " is not defined as an input port of '@" << callee
2963  << "'.";
2964  }
2965  return success();
2966 }
2967 
2968 //===----------------------------------------------------------------------===//
2969 // Calyx library ops
2970 //===----------------------------------------------------------------------===//
2971 
2972 LogicalResult PadLibOp::verify() {
2973  unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
2974  unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
2975  if (inBits >= outBits)
2976  return emitOpError("expected input bits (")
2977  << inBits << ')' << " to be less than output bits (" << outBits
2978  << ')';
2979  return success();
2980 }
2981 
2982 LogicalResult SliceLibOp::verify() {
2983  unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
2984  unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
2985  if (inBits <= outBits)
2986  return emitOpError("expected input bits (")
2987  << inBits << ')' << " to be greater than output bits (" << outBits
2988  << ')';
2989  return success();
2990 }
2991 
2992 #define ImplBinPipeOpCellInterface(OpType, outName) \
2993  SmallVector<StringRef> OpType::portNames() { \
2994  return {clkPort, resetPort, goPort, "left", "right", outName, donePort}; \
2995  } \
2996  \
2997  SmallVector<Direction> OpType::portDirections() { \
2998  return {Input, Input, Input, Input, Input, Output, Output}; \
2999  } \
3000  \
3001  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3002  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3003  } \
3004  \
3005  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3006  MLIRContext *context = getContext(); \
3007  IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); \
3008  NamedAttrList go, clk, reset, done; \
3009  go.append(goPort, isSet); \
3010  clk.append(clkPort, isSet); \
3011  reset.append(resetPort, isSet); \
3012  done.append(donePort, isSet); \
3013  return { \
3014  clk.getDictionary(context), /* Clk */ \
3015  reset.getDictionary(context), /* Reset */ \
3016  go.getDictionary(context), /* Go */ \
3017  DictionaryAttr::get(context), /* Lhs */ \
3018  DictionaryAttr::get(context), /* Rhs */ \
3019  DictionaryAttr::get(context), /* Out */ \
3020  done.getDictionary(context) /* Done */ \
3021  }; \
3022  } \
3023  \
3024  bool OpType::isCombinational() { return false; }
3025 
3026 #define ImplUnaryOpCellInterface(OpType) \
3027  SmallVector<StringRef> OpType::portNames() { return {"in", "out"}; } \
3028  SmallVector<Direction> OpType::portDirections() { return {Input, Output}; } \
3029  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3030  return {DictionaryAttr::get(getContext()), \
3031  DictionaryAttr::get(getContext())}; \
3032  } \
3033  bool OpType::isCombinational() { return true; } \
3034  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3035  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3036  }
3037 
3038 #define ImplBinOpCellInterface(OpType) \
3039  SmallVector<StringRef> OpType::portNames() { \
3040  return {"left", "right", "out"}; \
3041  } \
3042  SmallVector<Direction> OpType::portDirections() { \
3043  return {Input, Input, Output}; \
3044  } \
3045  void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3046  getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3047  } \
3048  bool OpType::isCombinational() { return true; } \
3049  SmallVector<DictionaryAttr> OpType::portAttributes() { \
3050  return {DictionaryAttr::get(getContext()), \
3051  DictionaryAttr::get(getContext()), \
3052  DictionaryAttr::get(getContext())}; \
3053  }
3054 
3055 // clang-format off
3056 ImplBinPipeOpCellInterface(MultPipeLibOp, "out")
3057 ImplBinPipeOpCellInterface(DivUPipeLibOp, "out_quotient")
3058 ImplBinPipeOpCellInterface(DivSPipeLibOp, "out_quotient")
3059 ImplBinPipeOpCellInterface(RemUPipeLibOp, "out_remainder")
3060 ImplBinPipeOpCellInterface(RemSPipeLibOp, "out_remainder")
3061 
3062 ImplUnaryOpCellInterface(PadLibOp)
3063 ImplUnaryOpCellInterface(SliceLibOp)
3064 ImplUnaryOpCellInterface(NotLibOp)
3065 ImplUnaryOpCellInterface(WireLibOp)
3066 ImplUnaryOpCellInterface(ExtSILibOp)
3067 
3068 ImplBinOpCellInterface(LtLibOp)
3069 ImplBinOpCellInterface(GtLibOp)
3070 ImplBinOpCellInterface(EqLibOp)
3071 ImplBinOpCellInterface(NeqLibOp)
3072 ImplBinOpCellInterface(GeLibOp)
3073 ImplBinOpCellInterface(LeLibOp)
3074 ImplBinOpCellInterface(SltLibOp)
3075 ImplBinOpCellInterface(SgtLibOp)
3076 ImplBinOpCellInterface(SeqLibOp)
3077 ImplBinOpCellInterface(SneqLibOp)
3078 ImplBinOpCellInterface(SgeLibOp)
3079 ImplBinOpCellInterface(SleLibOp)
3080 
3081 ImplBinOpCellInterface(AddLibOp)
3082 ImplBinOpCellInterface(SubLibOp)
3083 ImplBinOpCellInterface(ShruLibOp)
3084 ImplBinOpCellInterface(RshLibOp)
3085 ImplBinOpCellInterface(SrshLibOp)
3086 ImplBinOpCellInterface(LshLibOp)
3087 ImplBinOpCellInterface(AndLibOp)
3088 ImplBinOpCellInterface(OrLibOp)
3089 ImplBinOpCellInterface(XorLibOp)
3090 // clang-format on
3091 
3092 //===----------------------------------------------------------------------===//
3093 // TableGen generated logic.
3094 //===----------------------------------------------------------------------===//
3095 
3096 #include "circt/Dialect/Calyx/CalyxInterfaces.cpp.inc"
3097 
3098 // Provide the autogenerated implementation guts for the Op classes.
3099 #define GET_OP_CLASSES
3100 #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:1695
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:1488
static ParseResult parseParameterList(OpAsmParser &parser, SmallVector< Attribute > &parameters)
Parse an parameter list if present.
Definition: CalyxOps.cpp:1870
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:1339
#define ImplBinPipeOpCellInterface(OpType, outName)
Definition: CalyxOps.cpp:2992
static bool portIsUsedInGroup(GroupInterface group, Value port, bool isDriven)
Determines whether the given port is used in the group.
Definition: CalyxOps.cpp:1229
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:1588
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:2376
Direction convertHWDirectionToCalyx(hw::ModulePort::Direction direction)
Definition: CalyxOps.cpp:1806
static LogicalResult anyPortsReadByGroup(GroupInterface group, ValueRange ports)
Checks whether any ports are read within the group.
Definition: CalyxOps.cpp:1318
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:2337
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:2776
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:1298
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:3038
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:1251
static LogicalResult zeroRepeat(OpTy op, PatternRewriter &rewriter)
Definition: CalyxOps.cpp:2630
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:1441
static LogicalResult commonTailPatternWithSeq(IfOpTy ifOp, PatternRewriter &rewriter)
seq { if a with @G { if a with @G { seq { ...
Definition: CalyxOps.cpp:2400
static LogicalResult allPortsDrivenByGroup(GroupInterface group, ValueRange ports)
Checks whether all ports are driven within the group.
Definition: CalyxOps.cpp:1278
static LogicalResult verifyPortDirection(Operation *op, Value value, bool isDestination)
Determines whether the given direction is valid with the given inputs.
Definition: CalyxOps.cpp:1458
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:2866
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:1908
static DictionaryAttr cleanCalyxPortAttrs(OpBuilder builder, DictionaryAttr dict)
Returns a new DictionaryAttr containing only the calyx dialect attrs in the input DictionaryAttr.
Definition: CalyxOps.cpp:1831
static void printGroupPort(OpAsmPrinter &p, GroupPortType op)
Definition: CalyxOps.cpp:297
#define ImplUnaryOpCellInterface(OpType)
Definition: CalyxOps.cpp:3026
static ParseResult parseGroupPort(OpAsmParser &parser, OperationState &result)
Definition: CalyxOps.cpp:269
static LogicalResult verifyComplexLogic(InvokeOp &op, Value &value)
Definition: CalyxOps.cpp:2784
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:2356
static LogicalResult commonTailPatternWithPar(OpTy controlOp, PatternRewriter &rewriter)
if a with @G { par { par { if a with @G { ...
Definition: CalyxOps.cpp:2450
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:40
@ 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:2467
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:1419
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:2498
LogicalResult matchAndRewrite(IfOp ifOp, PatternRewriter &rewriter) const override
Definition: CalyxOps.cpp:2500
This pattern checks for one of two cases that will lead to StaticIfOp deletion: (1) Then and Else bod...
Definition: CalyxOps.cpp:2552
LogicalResult matchAndRewrite(StaticIfOp ifOp, PatternRewriter &rewriter) const override
Definition: CalyxOps.cpp:2554
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