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