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