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