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() {
660 return getControlOrWiresFrom<ControlOp>(*this);
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) {
712 printComponentInterface<ComponentOp>(p, *this);
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) {
846 printComponentInterface<CombComponentOp>(p, *this);
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>);
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>);
957}
958
959//===----------------------------------------------------------------------===//
960// ParOp
961//===----------------------------------------------------------------------===//
962
963LogicalResult ParOp::verify() {
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>);
984}
985
986//===----------------------------------------------------------------------===//
987// StaticParOp
988//===----------------------------------------------------------------------===//
989
990LogicalResult StaticParOp::verify() {
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>);
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() {
1224 return FloatingPointStandard::IEEE754;
1225}
1226
1227FloatingPointStandard MulFOpIEEE754::getFloatingPointStandard() {
1228 return FloatingPointStandard::IEEE754;
1229}
1230
1231FloatingPointStandard CompareFOpIEEE754::getFloatingPointStandard() {
1232 return FloatingPointStandard::IEEE754;
1233}
1234
1235FloatingPointStandard FpToIntOpIEEE754::getFloatingPointStandard() {
1236 return FloatingPointStandard::IEEE754;
1237}
1238
1239FloatingPointStandard IntToFpOpIEEE754::getFloatingPointStandard() {
1240 return FloatingPointStandard::IEEE754;
1241}
1242
1243FloatingPointStandard DivSqrtOpIEEE754::getFloatingPointStandard() {
1244 return FloatingPointStandard::IEEE754;
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)
1509 ? Direction::Output
1510 : Direction::Input;
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) {
1844 case hw::ModulePort::Direction::Input:
1845 return Direction::Input;
1846 case hw::ModulePort::Direction::Output:
1847 return Direction::Output;
1848 case hw::ModulePort::Direction::InOut:
1849 llvm_unreachable("InOut ports not supported by Calyx");
1850 }
1851 llvm_unreachable("Impossible port type");
1852}
1853
1854SmallVector<Direction> PrimitiveOp::portDirections() {
1855 SmallVector<Direction> portDirections;
1856 auto ports = getReferencedPrimitive().getPortList();
1857 for (hw::PortInfo port : ports)
1858 portDirections.push_back(convertHWDirectionToCalyx(port.dir));
1859 return portDirections;
1860}
1861
1862bool PrimitiveOp::isCombinational() { return false; }
1863
1864/// Returns a new DictionaryAttr containing only the calyx dialect attrs
1865/// in the input DictionaryAttr. Also strips the 'calyx.' prefix from these
1866/// attrs.
1867static DictionaryAttr cleanCalyxPortAttrs(OpBuilder builder,
1868 DictionaryAttr dict) {
1869 if (!dict) {
1870 return dict;
1871 }
1872 llvm::SmallVector<NamedAttribute> attrs;
1873 for (NamedAttribute attr : dict) {
1874 Dialect *dialect = attr.getNameDialect();
1875 if (dialect == nullptr || !isa<CalyxDialect>(*dialect))
1876 continue;
1877 StringRef name = attr.getName().strref();
1878 StringAttr newName = builder.getStringAttr(std::get<1>(name.split(".")));
1879 attr.setName(newName);
1880 attrs.push_back(attr);
1881 }
1882 return builder.getDictionaryAttr(attrs);
1883}
1884
1885// Grabs calyx port attributes from the HWModuleExternOp arg/result attributes.
1886SmallVector<DictionaryAttr> PrimitiveOp::portAttributes() {
1887 SmallVector<DictionaryAttr> portAttributes;
1888 OpBuilder builder(getContext());
1889 hw::HWModuleExternOp prim = getReferencedPrimitive();
1890 auto argAttrs = prim.getAllInputAttrs();
1891 auto resAttrs = prim.getAllOutputAttrs();
1892 for (auto a : argAttrs)
1893 portAttributes.push_back(
1894 cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1895 for (auto a : resAttrs)
1896 portAttributes.push_back(
1897 cleanCalyxPortAttrs(builder, cast_or_null<DictionaryAttr>(a)));
1898 return portAttributes;
1899}
1900
1901/// Parse an parameter list if present. Same format as HW dialect.
1902/// module-parameter-list ::= `<` parameter-decl (`,` parameter-decl)* `>`
1903/// parameter-decl ::= identifier `:` type
1904/// parameter-decl ::= identifier `:` type `=` attribute
1905///
1906static ParseResult parseParameterList(OpAsmParser &parser,
1907 SmallVector<Attribute> &parameters) {
1908
1909 return parser.parseCommaSeparatedList(
1910 OpAsmParser::Delimiter::OptionalLessGreater, [&]() {
1911 std::string name;
1912 Type type;
1913 Attribute value;
1914
1915 if (parser.parseKeywordOrString(&name) || parser.parseColonType(type))
1916 return failure();
1917
1918 // Parse the default value if present.
1919 if (succeeded(parser.parseOptionalEqual())) {
1920 if (parser.parseAttribute(value, type))
1921 return failure();
1922 }
1923
1924 auto &builder = parser.getBuilder();
1925 parameters.push_back(hw::ParamDeclAttr::get(
1926 builder.getContext(), builder.getStringAttr(name), type, value));
1927 return success();
1928 });
1929}
1930
1931/// Shim to also use this for the InstanceOp custom parser.
1932static ParseResult parseParameterList(OpAsmParser &parser,
1933 ArrayAttr &parameters) {
1934 SmallVector<Attribute> parseParameters;
1935 if (failed(parseParameterList(parser, parseParameters)))
1936 return failure();
1937
1938 parameters = ArrayAttr::get(parser.getContext(), parseParameters);
1939
1940 return success();
1941}
1942
1943/// Print a parameter list for a module or instance. Same format as HW dialect.
1944static void printParameterList(OpAsmPrinter &p, Operation *op,
1945 ArrayAttr parameters) {
1946 if (parameters.empty())
1947 return;
1948
1949 p << '<';
1950 llvm::interleaveComma(parameters, p, [&](Attribute param) {
1951 auto paramAttr = cast<hw::ParamDeclAttr>(param);
1952 p << paramAttr.getName().getValue() << ": " << paramAttr.getType();
1953 if (auto value = paramAttr.getValue()) {
1954 p << " = ";
1955 p.printAttributeWithoutType(value);
1956 }
1957 });
1958 p << '>';
1959}
1960
1961//===----------------------------------------------------------------------===//
1962// GroupGoOp
1963//===----------------------------------------------------------------------===//
1964
1965LogicalResult GroupGoOp::verify() { return verifyNotComplexSource(*this); }
1966
1967/// Provide meaningful names to the result value of a GroupGoOp.
1968void GroupGoOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
1969 auto parent = (*this)->getParentOfType<GroupOp>();
1970 StringRef name = parent.getSymName();
1971 std::string resultName = name.str() + ".go";
1972 setNameFn(getResult(), resultName);
1973}
1974
1975void GroupGoOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
1976
1977ParseResult GroupGoOp::parse(OpAsmParser &parser, OperationState &result) {
1978 if (parseGroupPort(parser, result))
1979 return failure();
1980
1981 result.addTypes(parser.getBuilder().getI1Type());
1982 return success();
1983}
1984
1985//===----------------------------------------------------------------------===//
1986// GroupDoneOp
1987//===----------------------------------------------------------------------===//
1988
1989LogicalResult GroupDoneOp::verify() {
1990 Operation *srcOp = getSrc().getDefiningOp();
1991 Value optionalGuard = getGuard();
1992 Operation *guardOp = optionalGuard ? optionalGuard.getDefiningOp() : nullptr;
1993 bool noGuard = (guardOp == nullptr);
1994
1995 if (srcOp == nullptr)
1996 // This is a port of the parent component.
1997 return success();
1998
1999 if (isa<hw::ConstantOp>(srcOp) && (noGuard || isa<hw::ConstantOp>(guardOp)))
2000 return emitOpError() << "with constant source"
2001 << (noGuard ? "" : " and constant guard")
2002 << ". This should be a combinational group.";
2003
2004 return verifyNotComplexSource(*this);
2005}
2006
2007void GroupDoneOp::print(OpAsmPrinter &p) { printGroupPort(p, *this); }
2008
2009ParseResult GroupDoneOp::parse(OpAsmParser &parser, OperationState &result) {
2010 return parseGroupPort(parser, result);
2011}
2012
2013//===----------------------------------------------------------------------===//
2014// ConstantOp
2015//===----------------------------------------------------------------------===//
2016void ConstantOp::getAsmResultNames(
2017 function_ref<void(Value, StringRef)> setNameFn) {
2018 if (isa<FloatAttr>(getValue())) {
2019 setNameFn(getResult(), "cst");
2020 return;
2021 }
2022 auto intCst = llvm::dyn_cast<IntegerAttr>(getValue());
2023 auto intType = llvm::dyn_cast<IntegerType>(getType());
2024
2025 // Sugar i1 constants with 'true' and 'false'.
2026 if (intType && intType.getWidth() == 1)
2027 return setNameFn(getResult(), intCst.getInt() > 0 ? "true" : "false");
2028
2029 // Otherwise, build a complex name with the value and type.
2030 SmallString<32> specialNameBuffer;
2031 llvm::raw_svector_ostream specialName(specialNameBuffer);
2032 specialName << 'c' << intCst.getValue();
2033 if (intType)
2034 specialName << '_' << getType();
2035 setNameFn(getResult(), specialName.str());
2036}
2037
2038LogicalResult ConstantOp::verify() {
2039 auto type = getType();
2040 assert(isa<IntegerType>(type) && "must be an IntegerType");
2041 // The value's bit width must match the return type bitwidth.
2042 if (auto valTyBitWidth = getValue().getType().getIntOrFloatBitWidth();
2043 valTyBitWidth != type.getIntOrFloatBitWidth()) {
2044 return emitOpError() << "value type bit width" << valTyBitWidth
2045 << " must match return type: "
2046 << type.getIntOrFloatBitWidth();
2047 }
2048 // Integer values must be signless.
2049 if (llvm::isa<IntegerType>(type) &&
2050 !llvm::cast<IntegerType>(type).isSignless())
2051 return emitOpError("integer return type must be signless");
2052 // Any float or integers attribute are acceptable.
2053 if (!llvm::isa<IntegerAttr, FloatAttr>(getValue())) {
2054 return emitOpError("value must be an integer or float attribute");
2055 }
2056
2057 return success();
2058}
2059
2060OpFoldResult calyx::ConstantOp::fold(FoldAdaptor adaptor) {
2061 return getValueAttr();
2062}
2063
2064void calyx::ConstantOp::build(OpBuilder &builder, OperationState &state,
2065 StringRef symName, Attribute attr, Type type) {
2066 state.addAttribute(ConstantOp::getSymNameAttrName(state.name),
2067 builder.getStringAttr(symName));
2068 state.addAttribute("value", attr);
2069 SmallVector<Type> types;
2070 types.push_back(type); // Out
2071 state.addTypes(types);
2072}
2073
2074SmallVector<StringRef> ConstantOp::portNames() { return {"out"}; }
2075
2076SmallVector<Direction> ConstantOp::portDirections() { return {Output}; }
2077
2078SmallVector<DictionaryAttr> ConstantOp::portAttributes() {
2079 return {DictionaryAttr::get(getContext())};
2080}
2081
2082bool ConstantOp::isCombinational() { return true; }
2083
2084//===----------------------------------------------------------------------===//
2085// RegisterOp
2086//===----------------------------------------------------------------------===//
2087
2088/// Provide meaningful names to the result values of a RegisterOp.
2089void RegisterOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
2090 getCellAsmResultNames(setNameFn, *this, this->portNames());
2091}
2092
2093SmallVector<StringRef> RegisterOp::portNames() {
2094 return {"in", "write_en", clkPort, resetPort, "out", donePort};
2095}
2096
2097SmallVector<Direction> RegisterOp::portDirections() {
2098 return {Input, Input, Input, Input, Output, Output};
2099}
2100
2101SmallVector<DictionaryAttr> RegisterOp::portAttributes() {
2102 MLIRContext *context = getContext();
2103 IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2104 NamedAttrList writeEn, clk, reset, done;
2105 writeEn.append(goPort, isSet);
2106 clk.append(clkPort, isSet);
2107 reset.append(resetPort, isSet);
2108 done.append(donePort, isSet);
2109 return {
2110 DictionaryAttr::get(context), // In
2111 writeEn.getDictionary(context), // Write enable
2112 clk.getDictionary(context), // Clk
2113 reset.getDictionary(context), // Reset
2114 DictionaryAttr::get(context), // Out
2115 done.getDictionary(context) // Done
2116 };
2117}
2118
2119bool RegisterOp::isCombinational() { return false; }
2120
2121//===----------------------------------------------------------------------===//
2122// MemoryOp
2123//===----------------------------------------------------------------------===//
2124
2125/// Provide meaningful names to the result values of a MemoryOp.
2126void MemoryOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
2127 getCellAsmResultNames(setNameFn, *this, this->portNames());
2128}
2129
2130SmallVector<StringRef> MemoryOp::portNames() {
2131 SmallVector<StringRef> portNames;
2132 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2133 auto nameAttr =
2134 StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2135 portNames.push_back(nameAttr.getValue());
2136 }
2137 portNames.append({"write_data", "write_en", clkPort, "read_data", donePort});
2138 return portNames;
2139}
2140
2141SmallVector<Direction> MemoryOp::portDirections() {
2142 SmallVector<Direction> portDirections;
2143 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2144 portDirections.push_back(Input);
2145 portDirections.append({Input, Input, Input, Output, Output});
2146 return portDirections;
2147}
2148
2149SmallVector<DictionaryAttr> MemoryOp::portAttributes() {
2150 SmallVector<DictionaryAttr> portAttributes;
2151 MLIRContext *context = getContext();
2152 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2153 portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2154
2155 // Use a boolean to indicate this attribute is used.
2156 IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1);
2157 NamedAttrList writeEn, clk, reset, done;
2158 writeEn.append(goPort, isSet);
2159 clk.append(clkPort, isSet);
2160 done.append(donePort, isSet);
2161 portAttributes.append({DictionaryAttr::get(context), // In
2162 writeEn.getDictionary(context), // Write enable
2163 clk.getDictionary(context), // Clk
2164 DictionaryAttr::get(context), // Out
2165 done.getDictionary(context)} // Done
2166 );
2167 return portAttributes;
2168}
2169
2170void MemoryOp::build(OpBuilder &builder, OperationState &state,
2171 StringRef instanceName, int64_t width,
2172 ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2173 state.addAttribute(MemoryOp::getSymNameAttrName(state.name),
2174 builder.getStringAttr(instanceName));
2175 state.addAttribute("width", builder.getI64IntegerAttr(width));
2176 state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2177 state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2178 SmallVector<Type> types;
2179 for (int64_t size : addrSizes)
2180 types.push_back(builder.getIntegerType(size)); // Addresses
2181 types.push_back(builder.getIntegerType(width)); // Write data
2182 types.push_back(builder.getI1Type()); // Write enable
2183 types.push_back(builder.getI1Type()); // Clk
2184 types.push_back(builder.getIntegerType(width)); // Read data
2185 types.push_back(builder.getI1Type()); // Done
2186 state.addTypes(types);
2187}
2188
2189LogicalResult MemoryOp::verify() {
2190 ArrayRef<Attribute> opSizes = getSizes().getValue();
2191 ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2192 size_t numDims = getSizes().size();
2193 size_t numAddrs = getAddrSizes().size();
2194 if (numDims != numAddrs)
2195 return emitOpError("mismatched number of dimensions (")
2196 << numDims << ") and address sizes (" << numAddrs << ")";
2197
2198 size_t numExtraPorts = 5; // write data/enable, clk, and read data/done.
2199 if (getNumResults() != numAddrs + numExtraPorts)
2200 return emitOpError("incorrect number of address ports, expected ")
2201 << numAddrs;
2202
2203 for (size_t i = 0; i < numDims; ++i) {
2204 int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2205 int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2206 if (llvm::Log2_64_Ceil(size) > addrSize)
2207 return emitOpError("address size (")
2208 << addrSize << ") for dimension " << i
2209 << " can't address the entire range (" << size << ")";
2210 }
2211
2212 return success();
2213}
2214
2215//===----------------------------------------------------------------------===//
2216// SeqMemoryOp
2217//===----------------------------------------------------------------------===//
2218
2219/// Provide meaningful names to the result values of a SeqMemoryOp.
2220void SeqMemoryOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
2221 getCellAsmResultNames(setNameFn, *this, this->portNames());
2222}
2223
2224SmallVector<StringRef> SeqMemoryOp::portNames() {
2225 SmallVector<StringRef> portNames;
2226 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i) {
2227 auto nameAttr =
2228 StringAttr::get(this->getContext(), "addr" + std::to_string(i));
2229 portNames.push_back(nameAttr.getValue());
2230 }
2231 portNames.append({clkPort, "reset", "content_en", "write_en", "write_data",
2232 "read_data", "done"});
2233 return portNames;
2234}
2235
2236SmallVector<Direction> SeqMemoryOp::portDirections() {
2237 SmallVector<Direction> portDirections;
2238 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2239 portDirections.push_back(Input);
2240 portDirections.append({Input, Input, Input, Input, Input, Output, Output});
2241 return portDirections;
2242}
2243
2244SmallVector<DictionaryAttr> SeqMemoryOp::portAttributes() {
2245 SmallVector<DictionaryAttr> portAttributes;
2246 MLIRContext *context = getContext();
2247 for (size_t i = 0, e = getAddrSizes().size(); i != e; ++i)
2248 portAttributes.push_back(DictionaryAttr::get(context)); // Addresses
2249
2250 OpBuilder builder(context);
2251 // Use a boolean to indicate this attribute is used.
2252 IntegerAttr isSet = IntegerAttr::get(builder.getIndexType(), 1);
2253 IntegerAttr isTwo = IntegerAttr::get(builder.getIndexType(), 2);
2254 NamedAttrList done, clk, reset, contentEn;
2255 done.append(donePort, isSet);
2256 clk.append(clkPort, isSet);
2257 reset.append(resetPort, isSet);
2258 contentEn.append(goPort, isTwo);
2259 portAttributes.append({clk.getDictionary(context), // Clk
2260 reset.getDictionary(context), // Reset
2261 contentEn.getDictionary(context), // Content enable
2262 DictionaryAttr::get(context), // Write enable
2263 DictionaryAttr::get(context), // Write data
2264 DictionaryAttr::get(context), // Read data
2265 done.getDictionary(context)} // Done
2266 );
2267 return portAttributes;
2268}
2269
2270void SeqMemoryOp::build(OpBuilder &builder, OperationState &state,
2271 StringRef instanceName, int64_t width,
2272 ArrayRef<int64_t> sizes, ArrayRef<int64_t> addrSizes) {
2273 state.addAttribute(SeqMemoryOp::getSymNameAttrName(state.name),
2274 builder.getStringAttr(instanceName));
2275 state.addAttribute("width", builder.getI64IntegerAttr(width));
2276 state.addAttribute("sizes", builder.getI64ArrayAttr(sizes));
2277 state.addAttribute("addrSizes", builder.getI64ArrayAttr(addrSizes));
2278 SmallVector<Type> types;
2279 for (int64_t size : addrSizes)
2280 types.push_back(builder.getIntegerType(size)); // Addresses
2281 types.push_back(builder.getI1Type()); // Clk
2282 types.push_back(builder.getI1Type()); // Reset
2283 types.push_back(builder.getI1Type()); // Content enable
2284 types.push_back(builder.getI1Type()); // Write enable
2285 types.push_back(builder.getIntegerType(width)); // Write data
2286 types.push_back(builder.getIntegerType(width)); // Read data
2287 types.push_back(builder.getI1Type()); // Done
2288 state.addTypes(types);
2289}
2290
2291LogicalResult SeqMemoryOp::verify() {
2292 ArrayRef<Attribute> opSizes = getSizes().getValue();
2293 ArrayRef<Attribute> opAddrSizes = getAddrSizes().getValue();
2294 size_t numDims = getSizes().size();
2295 size_t numAddrs = getAddrSizes().size();
2296 if (numDims != numAddrs)
2297 return emitOpError("mismatched number of dimensions (")
2298 << numDims << ") and address sizes (" << numAddrs << ")";
2299
2300 size_t numExtraPorts =
2301 7; // write data/enable, clk, reset, read data, content enable, and done.
2302 if (getNumResults() != numAddrs + numExtraPorts)
2303 return emitOpError("incorrect number of address ports, expected ")
2304 << numAddrs;
2305
2306 for (size_t i = 0; i < numDims; ++i) {
2307 int64_t size = cast<IntegerAttr>(opSizes[i]).getInt();
2308 int64_t addrSize = cast<IntegerAttr>(opAddrSizes[i]).getInt();
2309 if (llvm::Log2_64_Ceil(size) > addrSize)
2310 return emitOpError("address size (")
2311 << addrSize << ") for dimension " << i
2312 << " can't address the entire range (" << size << ")";
2313 }
2314
2315 return success();
2316}
2317
2318//===----------------------------------------------------------------------===//
2319// EnableOp
2320//===----------------------------------------------------------------------===//
2321LogicalResult EnableOp::verify() {
2322 auto component = (*this)->getParentOfType<ComponentOp>();
2323 auto wiresOp = component.getWiresOp();
2324 StringRef name = getGroupName();
2325
2326 auto groupOp = wiresOp.lookupSymbol<GroupInterface>(name);
2327 if (!groupOp)
2328 return emitOpError() << "with group '" << name
2329 << "', which does not exist.";
2330
2331 if (isa<CombGroupOp>(groupOp))
2332 return emitOpError() << "with group '" << name
2333 << "', which is a combinational group.";
2334
2335 return success();
2336}
2337
2338//===----------------------------------------------------------------------===//
2339// IfOp
2340//===----------------------------------------------------------------------===//
2341
2342LogicalResult IfOp::verify() {
2343 std::optional<StringRef> optGroupName = getGroupName();
2344 if (!optGroupName) {
2345 // No combinational group was provided.
2346 return success();
2347 }
2348 auto component = (*this)->getParentOfType<ComponentOp>();
2349 WiresOp wiresOp = component.getWiresOp();
2350 StringRef groupName = *optGroupName;
2351 auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2352 if (!groupOp)
2353 return emitOpError() << "with group '" << groupName
2354 << "', which does not exist.";
2355
2356 if (isa<GroupOp>(groupOp))
2357 return emitOpError() << "with group '" << groupName
2358 << "', which is not a combinational group.";
2359
2360 if (failed(groupOp.drivesPort(getCond())))
2361 return emitError() << "with conditional op: '"
2362 << valueName(component, getCond())
2363 << "' expected to be driven from group: '" << groupName
2364 << "' but no driver was found.";
2365
2366 return success();
2367}
2368
2369/// Returns the last EnableOp within the child tree of 'parentSeqOp' or
2370/// `parentStaticSeqOp.` If no EnableOp was found (e.g. a "calyx.par" operation
2371/// is present), returns None.
2372template <typename OpTy>
2373static std::optional<EnableOp> getLastEnableOp(OpTy parent) {
2374 static_assert(IsAny<OpTy, SeqOp, StaticSeqOp>(),
2375 "Should be a StaticSeqOp or SeqOp.");
2376 if (parent.getBodyBlock()->empty())
2377 return std::nullopt;
2378 auto &lastOp = parent.getBodyBlock()->back();
2379 if (auto enableOp = dyn_cast<EnableOp>(lastOp))
2380 return enableOp;
2381 if (auto seqOp = dyn_cast<SeqOp>(lastOp))
2382 return getLastEnableOp(seqOp);
2383 if (auto staticSeqOp = dyn_cast<StaticSeqOp>(lastOp))
2384 return getLastEnableOp(staticSeqOp);
2385
2386 return std::nullopt;
2387}
2388
2389/// Returns a mapping of {enabled Group name, EnableOp} for all EnableOps within
2390/// the immediate ParOp's body.
2391template <typename OpTy>
2394 static_assert(IsAny<OpTy, ParOp, StaticParOp>(),
2395 "Should be a StaticParOp or ParOp.");
2396
2398 Block *body = parent.getBodyBlock();
2399 for (EnableOp op : body->getOps<EnableOp>())
2400 enables.insert(std::pair(op.getGroupNameAttr().getAttr(), op));
2401
2402 return enables;
2403}
2404
2405/// Checks preconditions for the common tail pattern. This canonicalization is
2406/// stringent about not entering nested control operations, as this may cause
2407/// unintentional changes in behavior.
2408/// We only look for two cases: (1) both regions are ParOps, and
2409/// (2) both regions are SeqOps. The case when these are different, e.g. ParOp
2410/// and SeqOp, will only produce less optimal code, or even worse, change the
2411/// behavior.
2412template <typename IfOpTy, typename TailOpTy>
2414 static_assert(IsAny<TailOpTy, SeqOp, ParOp, StaticSeqOp, StaticParOp>(),
2415 "Should be a SeqOp, ParOp, StaticSeqOp, or StaticParOp.");
2416 static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2417 "Should be a IfOp or StaticIfOp.");
2418
2419 if (!op.thenBodyExists() || !op.elseBodyExists())
2420 return false;
2421 if (op.getThenBody()->empty() || op.getElseBody()->empty())
2422 return false;
2423
2424 Block *thenBody = op.getThenBody(), *elseBody = op.getElseBody();
2425 return isa<TailOpTy>(thenBody->front()) && isa<TailOpTy>(elseBody->front());
2426}
2427
2428/// seq {
2429/// if %a with @G { if %a with @G {
2430/// seq { ... calyx.enable @A } seq { ... }
2431/// else { -> } else {
2432/// seq { ... calyx.enable @A } seq { ... }
2433/// } }
2434/// calyx.enable @A
2435/// }
2436template <typename IfOpTy, typename SeqOpTy>
2437static LogicalResult commonTailPatternWithSeq(IfOpTy ifOp,
2438 PatternRewriter &rewriter) {
2439 static_assert(IsAny<IfOpTy, IfOp, StaticIfOp>(),
2440 "Should be an IfOp or StaticIfOp.");
2441 static_assert(IsAny<SeqOpTy, SeqOp, StaticSeqOp>(),
2442 "Branches should be checking for an SeqOp or StaticSeqOp");
2443 if (!hasCommonTailPatternPreConditions<IfOpTy, SeqOpTy>(ifOp))
2444 return failure();
2445 auto thenControl = cast<SeqOpTy>(ifOp.getThenBody()->front()),
2446 elseControl = cast<SeqOpTy>(ifOp.getElseBody()->front());
2447
2448 std::optional<EnableOp> lastThenEnableOp = getLastEnableOp(thenControl),
2449 lastElseEnableOp = getLastEnableOp(elseControl);
2450
2451 if (!lastThenEnableOp || !lastElseEnableOp)
2452 return failure();
2453 if (lastThenEnableOp->getGroupName() != lastElseEnableOp->getGroupName())
2454 return failure();
2455
2456 // Place the IfOp and pulled EnableOp inside a sequential region, in case
2457 // this IfOp is nested in a ParOp. This avoids unintentionally
2458 // parallelizing the pulled out EnableOps.
2459 rewriter.setInsertionPointAfter(ifOp);
2460 SeqOpTy seqOp = SeqOpTy::create(rewriter, ifOp.getLoc());
2461 Block *body = seqOp.getBodyBlock();
2462 rewriter.moveOpBefore(ifOp, body, body->end());
2463 rewriter.setInsertionPointToEnd(body);
2464 EnableOp::create(rewriter, seqOp.getLoc(), lastThenEnableOp->getGroupName());
2465
2466 // Erase the common EnableOp from the Then and Else regions.
2467 rewriter.eraseOp(*lastThenEnableOp);
2468 rewriter.eraseOp(*lastElseEnableOp);
2469 return success();
2470}
2471
2472/// if %a with @G { par {
2473/// par { if %a with @G {
2474/// ... par { ... }
2475/// calyx.enable @A } else {
2476/// calyx.enable @B -> par { ... }
2477/// } }
2478/// } else { calyx.enable @A
2479/// par { calyx.enable @B
2480/// ... }
2481/// calyx.enable @A
2482/// calyx.enable @B
2483/// }
2484/// }
2485template <typename OpTy, typename ParOpTy>
2486static LogicalResult commonTailPatternWithPar(OpTy controlOp,
2487 PatternRewriter &rewriter) {
2488 static_assert(IsAny<OpTy, IfOp, StaticIfOp>(),
2489 "Should be an IfOp or StaticIfOp.");
2490 static_assert(IsAny<ParOpTy, ParOp, StaticParOp>(),
2491 "Branches should be checking for an ParOp or StaticParOp");
2492 if (!hasCommonTailPatternPreConditions<OpTy, ParOpTy>(controlOp))
2493 return failure();
2494 auto thenControl = cast<ParOpTy>(controlOp.getThenBody()->front()),
2495 elseControl = cast<ParOpTy>(controlOp.getElseBody()->front());
2496
2497 auto a = getAllEnableOpsInImmediateBody(thenControl);
2498 auto b = getAllEnableOpsInImmediateBody(elseControl);
2499 SmallVector<StringRef> groupNames;
2500 // Compute the intersection between `A` and `B`.
2501 for (auto [groupName, aEnable] : a) {
2502 auto bIndex = b.find(groupName);
2503 if (bIndex == b.end())
2504 continue;
2505 // This is also an element in B.
2506 groupNames.push_back(groupName.getValue());
2507 // Since these are being pulled out, erase them.
2508 rewriter.eraseOp(aEnable);
2509 rewriter.eraseOp(bIndex->second);
2510 }
2511
2512 // Place the IfOp and EnableOp(s) inside a parallel region, in case this
2513 // IfOp is nested in a SeqOp. This avoids unintentionally sequentializing
2514 // the pulled out EnableOps.
2515 rewriter.setInsertionPointAfter(controlOp);
2516
2517 ParOpTy parOp = ParOpTy::create(rewriter, controlOp.getLoc());
2518 Block *body = parOp.getBodyBlock();
2519 controlOp->remove();
2520 body->push_back(controlOp);
2521 // Pull out the intersection between these two sets, and erase their
2522 // counterparts in the Then and Else regions.
2523 rewriter.setInsertionPointToEnd(body);
2524 for (StringRef groupName : groupNames)
2525 EnableOp::create(rewriter, parOp.getLoc(), groupName);
2526
2527 return success();
2528}
2529
2530/// This pattern checks for one of two cases that will lead to IfOp deletion:
2531/// (1) Then and Else bodies are both empty.
2532/// (2) Then body is empty and Else body does not exist.
2535 LogicalResult matchAndRewrite(IfOp ifOp,
2536 PatternRewriter &rewriter) const override {
2537 if (!ifOp.getThenBody()->empty())
2538 return failure();
2539 if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2540 return failure();
2541
2543
2544 return success();
2545 }
2546};
2547
2548void IfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2549 MLIRContext *context) {
2551 patterns.add(commonTailPatternWithPar<IfOp, ParOp>);
2552 patterns.add(commonTailPatternWithSeq<IfOp, SeqOp>);
2553}
2554
2555//===----------------------------------------------------------------------===//
2556// StaticIfOp
2557//===----------------------------------------------------------------------===//
2558LogicalResult StaticIfOp::verify() {
2559 if (elseBodyExists()) {
2560 auto *elseBod = getElseBody();
2561 auto &elseOps = elseBod->getOperations();
2562 // should only have one Operation, static, in the else branch
2563 for (Operation &op : elseOps) {
2564 if (!isStaticControl(&op)) {
2565 return op.emitOpError(
2566 "static if's else branch has non static control within it");
2567 }
2568 }
2569 }
2570
2571 auto *thenBod = getThenBody();
2572 auto &thenOps = thenBod->getOperations();
2573 for (Operation &op : thenOps) {
2574 // should only have one, static, Operation in the then branch
2575 if (!isStaticControl(&op)) {
2576 return op.emitOpError(
2577 "static if's then branch has non static control within it");
2578 }
2579 }
2580
2581 return success();
2582}
2583
2584/// This pattern checks for one of two cases that will lead to StaticIfOp
2585/// deletion: (1) Then and Else bodies are both empty. (2) Then body is empty
2586/// and Else body does not exist.
2589 LogicalResult matchAndRewrite(StaticIfOp ifOp,
2590 PatternRewriter &rewriter) const override {
2591 if (!ifOp.getThenBody()->empty())
2592 return failure();
2593 if (ifOp.elseBodyExists() && !ifOp.getElseBody()->empty())
2594 return failure();
2595
2596 eraseControlWithConditional(ifOp, rewriter);
2597
2598 return success();
2599 }
2600};
2601
2602void StaticIfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2603 MLIRContext *context) {
2605 patterns.add(commonTailPatternWithPar<StaticIfOp, StaticParOp>);
2606 patterns.add(commonTailPatternWithSeq<StaticIfOp, StaticSeqOp>);
2607}
2608
2609//===----------------------------------------------------------------------===//
2610// WhileOp
2611//===----------------------------------------------------------------------===//
2612LogicalResult WhileOp::verify() {
2613 auto component = (*this)->getParentOfType<ComponentOp>();
2614 auto wiresOp = component.getWiresOp();
2615
2616 std::optional<StringRef> optGroupName = getGroupName();
2617 if (!optGroupName) {
2618 /// No combinational group was provided
2619 return success();
2620 }
2621 StringRef groupName = *optGroupName;
2622 auto groupOp = wiresOp.lookupSymbol<GroupInterface>(groupName);
2623 if (!groupOp)
2624 return emitOpError() << "with group '" << groupName
2625 << "', which does not exist.";
2626
2627 if (isa<GroupOp>(groupOp))
2628 return emitOpError() << "with group '" << groupName
2629 << "', which is not a combinational group.";
2630
2631 if (failed(groupOp.drivesPort(getCond())))
2632 return emitError() << "conditional op: '" << valueName(component, getCond())
2633 << "' expected to be driven from group: '" << groupName
2634 << "' but no driver was found.";
2635
2636 return success();
2637}
2638
2639LogicalResult WhileOp::canonicalize(WhileOp whileOp,
2640 PatternRewriter &rewriter) {
2641 if (whileOp.getBodyBlock()->empty()) {
2642 eraseControlWithGroupAndConditional(whileOp, rewriter);
2643 return success();
2644 }
2645
2646 return failure();
2647}
2648
2649//===----------------------------------------------------------------------===//
2650// StaticRepeatOp
2651//===----------------------------------------------------------------------===//
2652LogicalResult StaticRepeatOp::verify() {
2653 for (auto &&bodyOp : (*this).getRegion().front()) {
2654 // there should only be one bodyOp for each StaticRepeatOp
2655 if (!isStaticControl(&bodyOp)) {
2656 return bodyOp.emitOpError(
2657 "static repeat has non static control within it");
2658 }
2659 }
2660
2661 return success();
2662}
2663
2664template <typename OpTy>
2665static LogicalResult zeroRepeat(OpTy op, PatternRewriter &rewriter) {
2666 static_assert(IsAny<OpTy, RepeatOp, StaticRepeatOp>(),
2667 "Should be a RepeatOp or StaticPRepeatOp");
2668 if (op.getCount() == 0) {
2669 rewriter.eraseOp(op);
2670 return success();
2671 }
2672
2673 return failure();
2674}
2675
2676void StaticRepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2677 MLIRContext *context) {
2678 patterns.add(emptyControl<StaticRepeatOp>);
2679 patterns.add(zeroRepeat<StaticRepeatOp>);
2680}
2681
2682//===----------------------------------------------------------------------===//
2683// RepeatOp
2684//===----------------------------------------------------------------------===//
2685void RepeatOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
2686 MLIRContext *context) {
2687 patterns.add(emptyControl<RepeatOp>);
2688 patterns.add(zeroRepeat<RepeatOp>);
2689}
2690
2691//===----------------------------------------------------------------------===//
2692// InvokeOp
2693//===----------------------------------------------------------------------===//
2694
2695// Parse the parameter list of invoke.
2696static ParseResult
2697parseParameterList(OpAsmParser &parser, OperationState &result,
2698 SmallVectorImpl<OpAsmParser::UnresolvedOperand> &ports,
2699 SmallVectorImpl<OpAsmParser::UnresolvedOperand> &inputs,
2700 SmallVectorImpl<Attribute> &portNames,
2701 SmallVectorImpl<Attribute> &inputNames,
2702 SmallVectorImpl<Type> &types) {
2703 OpAsmParser::UnresolvedOperand port;
2704 OpAsmParser::UnresolvedOperand input;
2705 Type type;
2706 auto parseParameter = [&]() -> ParseResult {
2707 if (parser.parseOperand(port) || parser.parseEqual() ||
2708 parser.parseOperand(input))
2709 return failure();
2710 ports.push_back(port);
2711 portNames.push_back(StringAttr::get(parser.getContext(), port.name));
2712 inputs.push_back(input);
2713 inputNames.push_back(StringAttr::get(parser.getContext(), input.name));
2714 return success();
2715 };
2716 if (parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2717 parseParameter))
2718 return failure();
2719 if (parser.parseArrow())
2720 return failure();
2721 auto parseType = [&]() -> ParseResult {
2722 if (parser.parseType(type))
2723 return failure();
2724 types.push_back(type);
2725 return success();
2726 };
2727 return parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Paren,
2728 parseType);
2729}
2730
2731ParseResult InvokeOp::parse(OpAsmParser &parser, OperationState &result) {
2732 StringAttr componentName;
2733 SmallVector<OpAsmParser::UnresolvedOperand, 4> ports;
2734 SmallVector<OpAsmParser::UnresolvedOperand, 4> inputs;
2735 SmallVector<Attribute> portNames;
2736 SmallVector<Attribute> inputNames;
2737 SmallVector<Type, 4> types;
2738 if (parser.parseSymbolName(componentName))
2739 return failure();
2740 FlatSymbolRefAttr callee = FlatSymbolRefAttr::get(componentName);
2741 SMLoc loc = parser.getCurrentLocation();
2742
2743 SmallVector<Attribute, 4> refCells;
2744 if (succeeded(parser.parseOptionalLSquare())) {
2745 if (parser.parseCommaSeparatedList([&]() -> ParseResult {
2746 std::string refCellName;
2747 std::string externalMem;
2748 NamedAttrList refCellAttr;
2749 if (parser.parseKeywordOrString(&refCellName) ||
2750 parser.parseEqual() || parser.parseKeywordOrString(&externalMem))
2751 return failure();
2752 auto externalMemAttr =
2753 SymbolRefAttr::get(parser.getContext(), externalMem);
2754 refCellAttr.append(StringAttr::get(parser.getContext(), refCellName),
2755 externalMemAttr);
2756 refCells.push_back(
2757 DictionaryAttr::get(parser.getContext(), refCellAttr));
2758 return success();
2759 }) ||
2760 parser.parseRSquare())
2761 return failure();
2762 }
2763 result.addAttribute("refCellsMap",
2764 ArrayAttr::get(parser.getContext(), refCells));
2765
2766 result.addAttribute("callee", callee);
2767 if (parseParameterList(parser, result, ports, inputs, portNames, inputNames,
2768 types))
2769 return failure();
2770 if (parser.resolveOperands(ports, types, loc, result.operands))
2771 return failure();
2772 if (parser.resolveOperands(inputs, types, loc, result.operands))
2773 return failure();
2774 result.addAttribute("portNames",
2775 ArrayAttr::get(parser.getContext(), portNames));
2776 result.addAttribute("inputNames",
2777 ArrayAttr::get(parser.getContext(), inputNames));
2778 return success();
2779}
2780
2781void InvokeOp::print(OpAsmPrinter &p) {
2782 p << " @" << getCallee() << "[";
2783 auto refCellNamesMap = getRefCellsMap();
2784 llvm::interleaveComma(refCellNamesMap, p, [&](Attribute attr) {
2785 auto dictAttr = cast<DictionaryAttr>(attr);
2786 llvm::interleaveComma(dictAttr, p, [&](NamedAttribute namedAttr) {
2787 auto refCellName = namedAttr.getName().str();
2788 auto externalMem =
2789 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
2790 p << refCellName << " = " << externalMem;
2791 });
2792 });
2793 p << "](";
2794
2795 auto ports = getPorts();
2796 auto inputs = getInputs();
2797 llvm::interleaveComma(llvm::zip(ports, inputs), p, [&](auto arg) {
2798 p << std::get<0>(arg) << " = " << std::get<1>(arg);
2799 });
2800 p << ") -> (";
2801 llvm::interleaveComma(ports, p, [&](auto port) { p << port.getType(); });
2802 p << ")";
2803}
2804
2805// Check the direction of one of the ports in one of the connections of an
2806// InvokeOp.
2807static LogicalResult verifyInvokeOpValue(InvokeOp &op, Value &value,
2808 bool isDestination) {
2809 if (isPort(value))
2810 return verifyPortDirection(op, value, isDestination);
2811 return success();
2812}
2813
2814// Checks if the value comes from complex logic.
2815static LogicalResult verifyComplexLogic(InvokeOp &op, Value &value) {
2816 // Refer to the above function verifyNotComplexSource for its role.
2817 Operation *operation = value.getDefiningOp();
2818 if (operation == nullptr)
2819 return success();
2820 if (auto *dialect = operation->getDialect(); isa<comb::CombDialect>(dialect))
2821 return failure();
2822 return success();
2823}
2824
2825// Look up a cell by its instance name. Cells are no longer symbols, so they
2826// cannot be found through the component's symbol table.
2827static Operation *lookupCell(ComponentOp componentOp, StringRef name) {
2828 for (auto cell : componentOp.getOps<CellInterface>())
2829 if (cell.instanceName() == name)
2830 return cell;
2831 return nullptr;
2832}
2833
2834// Get the go port of the invoked component.
2835Value InvokeOp::getInstGoValue() {
2836 ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2837 Operation *operation = lookupCell(componentOp, getCallee());
2838 Value ret = nullptr;
2839 llvm::TypeSwitch<Operation *>(operation)
2840 .Case<RegisterOp>([&](auto op) { ret = operation->getResult(1); })
2841 .Case<MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2842 RemSPipeLibOp, RemUPipeLibOp>(
2843 [&](auto op) { ret = operation->getResult(2); })
2844 .Case<InstanceOp>([&](auto op) {
2845 auto portInfo = op.getReferencedComponent().getPortInfo();
2846 for (auto [portInfo, res] :
2847 llvm::zip(portInfo, operation->getResults())) {
2848 if (portInfo.hasAttribute(goPort))
2849 ret = res;
2850 }
2851 })
2852 .Case<PrimitiveOp>([&](auto op) {
2853 auto moduleExternOp = op.getReferencedPrimitive();
2854 auto argAttrs = moduleExternOp.getAllInputAttrs();
2855 for (auto [attr, res] : llvm::zip(argAttrs, op.getResults())) {
2856 if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2857 if (!dictAttr.empty()) {
2858 if (dictAttr.begin()->getName().getValue() == "calyx.go")
2859 ret = res;
2860 }
2861 }
2862 }
2863 });
2864 return ret;
2865}
2866
2867// Get the done port of the invoked component.
2868Value InvokeOp::getInstDoneValue() {
2869 ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2870 Operation *operation = lookupCell(componentOp, getCallee());
2871 Value ret = nullptr;
2872 llvm::TypeSwitch<Operation *>(operation)
2873 .Case<RegisterOp, MemoryOp, DivSPipeLibOp, DivUPipeLibOp, MultPipeLibOp,
2874 RemSPipeLibOp, RemUPipeLibOp>([&](auto op) {
2875 size_t doneIdx = operation->getResults().size() - 1;
2876 ret = operation->getResult(doneIdx);
2877 })
2878 .Case<InstanceOp>([&](auto op) {
2879 InstanceOp instanceOp = cast<InstanceOp>(operation);
2880 auto portInfo = instanceOp.getReferencedComponent().getPortInfo();
2881 for (auto [portInfo, res] :
2882 llvm::zip(portInfo, operation->getResults())) {
2883 if (portInfo.hasAttribute(donePort))
2884 ret = res;
2885 }
2886 })
2887 .Case<PrimitiveOp>([&](auto op) {
2888 PrimitiveOp primOp = cast<PrimitiveOp>(operation);
2889 auto moduleExternOp = primOp.getReferencedPrimitive();
2890 auto resAttrs = moduleExternOp.getAllOutputAttrs();
2891 for (auto [attr, res] : llvm::zip(resAttrs, primOp.getResults())) {
2892 if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2893 if (!dictAttr.empty()) {
2894 if (dictAttr.begin()->getName().getValue() == "calyx.done")
2895 ret = res;
2896 }
2897 }
2898 }
2899 });
2900 return ret;
2901}
2902
2903// A helper function that gets the number of go or done ports in
2904// hw.module.extern.
2905static size_t
2907 bool isGo) {
2908 size_t ret = 0;
2909 std::string str = isGo ? "calyx.go" : "calyx.done";
2910 for (Attribute attr : moduleExternOp.getAllInputAttrs()) {
2911 if (DictionaryAttr dictAttr = dyn_cast<DictionaryAttr>(attr)) {
2912 ret = llvm::count_if(dictAttr, [&](NamedAttribute iter) {
2913 return iter.getName().getValue() == str;
2914 });
2915 }
2916 }
2917 return ret;
2918}
2919
2920LogicalResult InvokeOp::verify() {
2921 ComponentOp componentOp = (*this)->getParentOfType<ComponentOp>();
2922 StringRef callee = getCallee();
2923 Operation *operation = lookupCell(componentOp, callee);
2924 // The referenced symbol does not exist.
2925 if (!operation)
2926 return emitOpError() << "with instance '@" << callee
2927 << "', which does not exist.";
2928 // The argument list of invoke is empty.
2929 if (getInputs().empty() && getRefCellsMap().empty()) {
2930 return emitOpError() << "'@" << callee
2931 << "' has zero input and output port connections and "
2932 "has no passing-by-reference cells; "
2933 "expected at least one.";
2934 }
2935 size_t goPortNum = 0, donePortNum = 0;
2936 // They both have a go port and a done port, but the "go" port for
2937 // registers and memrey should be "write_en" port.
2938 llvm::TypeSwitch<Operation *>(operation)
2939 .Case<RegisterOp, DivSPipeLibOp, DivUPipeLibOp, MemoryOp, MultPipeLibOp,
2940 RemSPipeLibOp, RemUPipeLibOp>(
2941 [&](auto op) { goPortNum = 1, donePortNum = 1; })
2942 .Case<InstanceOp>([&](auto op) {
2943 auto portInfo = op.getReferencedComponent().getPortInfo();
2944 for (PortInfo info : portInfo) {
2945 if (info.hasAttribute(goPort))
2946 ++goPortNum;
2947 if (info.hasAttribute(donePort))
2948 ++donePortNum;
2949 }
2950 })
2951 .Case<PrimitiveOp>([&](auto op) {
2952 auto moduleExternOp = op.getReferencedPrimitive();
2953 // Get the number of go ports and done ports by their attrubutes.
2954 goPortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, true);
2955 donePortNum = getHwModuleExtGoOrDonePortNumber(moduleExternOp, false);
2956 });
2957 // If the number of go ports and done ports is wrong.
2958 if (goPortNum != 1 && donePortNum != 1)
2959 return emitOpError()
2960 << "'@" << callee << "'"
2961 << " is a combinational component and cannot be invoked, which must "
2962 "have single go port and single done port.";
2963
2964 auto ports = getPorts();
2965 auto inputs = getInputs();
2966 // We have verified earlier that the instance has a go and a done port.
2967 Value goValue = getInstGoValue();
2968 Value doneValue = getInstDoneValue();
2969 for (auto [port, input, portName, inputName] :
2970 llvm::zip(ports, inputs, getPortNames(), getInputNames())) {
2971 // Check the direction of these destination ports.
2972 // 'calyx.invoke' op '@r0' has input '%r.out', which is a source port. The
2973 // inputs are required to be destination ports.
2974 if (failed(verifyInvokeOpValue(*this, port, true)))
2975 return emitOpError() << "'@" << callee << "' has input '"
2976 << cast<StringAttr>(portName).getValue()
2977 << "', which is a source port. The inputs are "
2978 "required to be destination ports.";
2979 // The go port should not appear in the parameter list.
2980 if (port == goValue)
2981 return emitOpError() << "the go or write_en port of '@" << callee
2982 << "' cannot appear here.";
2983 // Check the direction of these source ports.
2984 if (failed(verifyInvokeOpValue(*this, input, false)))
2985 return emitOpError() << "'@" << callee << "' has output '"
2986 << cast<StringAttr>(inputName).getValue()
2987 << "', which is a destination port. The inputs are "
2988 "required to be source ports.";
2989 if (failed(verifyComplexLogic(*this, input)))
2990 return emitOpError() << "'@" << callee << "' has '"
2991 << cast<StringAttr>(inputName).getValue()
2992 << "', which is not a port or constant. Complex "
2993 "logic should be conducted in the guard.";
2994 if (input == doneValue)
2995 return emitOpError() << "the done port of '@" << callee
2996 << "' cannot appear here.";
2997 // Check if the connection uses the callee's port.
2998 if (port.getDefiningOp() != operation && input.getDefiningOp() != operation)
2999 return emitOpError() << "the connection "
3000 << cast<StringAttr>(portName).getValue() << " = "
3001 << cast<StringAttr>(inputName).getValue()
3002 << " is not defined as an input port of '@" << callee
3003 << "'.";
3004 }
3005 return success();
3006}
3007
3008//===----------------------------------------------------------------------===//
3009// Calyx library ops
3010//===----------------------------------------------------------------------===//
3011
3012LogicalResult PadLibOp::verify() {
3013 unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
3014 unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
3015 if (inBits >= outBits)
3016 return emitOpError("expected input bits (")
3017 << inBits << ')' << " to be less than output bits (" << outBits
3018 << ')';
3019 return success();
3020}
3021
3022LogicalResult SliceLibOp::verify() {
3023 unsigned inBits = getResult(0).getType().getIntOrFloatBitWidth();
3024 unsigned outBits = getResult(1).getType().getIntOrFloatBitWidth();
3025 if (inBits <= outBits)
3026 return emitOpError("expected input bits (")
3027 << inBits << ')' << " to be greater than output bits (" << outBits
3028 << ')';
3029 return success();
3030}
3031
3032#define ImplBinPipeOpCellInterface(OpType, outName) \
3033 SmallVector<StringRef> OpType::portNames() { \
3034 return {clkPort, resetPort, goPort, "left", "right", outName, donePort}; \
3035 } \
3036 \
3037 SmallVector<Direction> OpType::portDirections() { \
3038 return {Input, Input, Input, Input, Input, Output, Output}; \
3039 } \
3040 \
3041 void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3042 getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3043 } \
3044 \
3045 SmallVector<DictionaryAttr> OpType::portAttributes() { \
3046 MLIRContext *context = getContext(); \
3047 IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); \
3048 NamedAttrList go, clk, reset, done; \
3049 go.append(goPort, isSet); \
3050 clk.append(clkPort, isSet); \
3051 reset.append(resetPort, isSet); \
3052 done.append(donePort, isSet); \
3053 return { \
3054 clk.getDictionary(context), /* Clk */ \
3055 reset.getDictionary(context), /* Reset */ \
3056 go.getDictionary(context), /* Go */ \
3057 DictionaryAttr::get(context), /* Lhs */ \
3058 DictionaryAttr::get(context), /* Rhs */ \
3059 DictionaryAttr::get(context), /* Out */ \
3060 done.getDictionary(context) /* Done */ \
3061 }; \
3062 } \
3063 \
3064 bool OpType::isCombinational() { return false; }
3065
3066#define ImplUnaryOpCellInterface(OpType) \
3067 SmallVector<StringRef> OpType::portNames() { return {"in", "out"}; } \
3068 SmallVector<Direction> OpType::portDirections() { return {Input, Output}; } \
3069 SmallVector<DictionaryAttr> OpType::portAttributes() { \
3070 return {DictionaryAttr::get(getContext()), \
3071 DictionaryAttr::get(getContext())}; \
3072 } \
3073 bool OpType::isCombinational() { return true; } \
3074 void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3075 getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3076 }
3077
3078#define ImplBinOpCellInterface(OpType) \
3079 SmallVector<StringRef> OpType::portNames() { \
3080 return {"left", "right", "out"}; \
3081 } \
3082 SmallVector<Direction> OpType::portDirections() { \
3083 return {Input, Input, Output}; \
3084 } \
3085 void OpType::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { \
3086 getCellAsmResultNames(setNameFn, *this, this->portNames()); \
3087 } \
3088 bool OpType::isCombinational() { return true; } \
3089 SmallVector<DictionaryAttr> OpType::portAttributes() { \
3090 return {DictionaryAttr::get(getContext()), \
3091 DictionaryAttr::get(getContext()), \
3092 DictionaryAttr::get(getContext())}; \
3093 }
3094
3095// clang-format off
3096ImplBinPipeOpCellInterface(MultPipeLibOp, "out")
3097ImplBinPipeOpCellInterface(DivUPipeLibOp, "out_quotient")
3098ImplBinPipeOpCellInterface(DivSPipeLibOp, "out_quotient")
3099ImplBinPipeOpCellInterface(RemUPipeLibOp, "out_remainder")
3100ImplBinPipeOpCellInterface(RemSPipeLibOp, "out_remainder")
3101
3103ImplUnaryOpCellInterface(SliceLibOp)
3105ImplUnaryOpCellInterface(WireLibOp)
3106ImplUnaryOpCellInterface(ExtSILibOp)
3107
3111ImplBinOpCellInterface(NeqLibOp)
3114ImplBinOpCellInterface(SltLibOp)
3115ImplBinOpCellInterface(SgtLibOp)
3116ImplBinOpCellInterface(SeqLibOp)
3117ImplBinOpCellInterface(SneqLibOp)
3118ImplBinOpCellInterface(SgeLibOp)
3119ImplBinOpCellInterface(SleLibOp)
3120
3121ImplBinOpCellInterface(AddLibOp)
3122ImplBinOpCellInterface(SubLibOp)
3123ImplBinOpCellInterface(ShruLibOp)
3124ImplBinOpCellInterface(RshLibOp)
3125ImplBinOpCellInterface(SrshLibOp)
3126ImplBinOpCellInterface(LshLibOp)
3127ImplBinOpCellInterface(AndLibOp)
3129ImplBinOpCellInterface(XorLibOp)
3130// clang-format on
3131
3132//===----------------------------------------------------------------------===//
3133// TableGen generated logic.
3134//===----------------------------------------------------------------------===//
3135
3136#include "circt/Dialect/Calyx/CalyxInterfaces.cpp.inc"
3137
3138// Provide the autogenerated implementation guts for the Op classes.
3139#define GET_OP_CLASSES
3140#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 { -> } else { seq { ...
static LogicalResult allPortsDrivenByGroup(GroupInterface group, ValueRange ports)
Checks whether all ports are driven within the group.
static LogicalResult verifyPortDirection(Operation *op, Value value, bool isDestination)
Determines whether the given direction is valid with the given inputs.
static size_t getHwModuleExtGoOrDonePortNumber(hw::HWModuleExternOp &moduleExternOp, bool isGo)
static bool isStaticControl(Operation *op)
Returns whether the given operation is a static control operator.
Definition CalyxOps.cpp: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.
static ParseResult parsePort(OpAsmParser &p, module_like_impl::PortParse &result)
Parse a single argument with the following syntax:
static Block * getBodyBlock(FModuleLike mod)
static InstancePath empty
Signals that the following operation is combinational.
Definition CalyxOps.h:55
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp: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:59
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition LLVM.h: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
This holds the name, type, direction of a module's ports.