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