Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
SCFToCalyx.cpp
Go to the documentation of this file.
1//===- SCFToCalyx.cpp - SCF to Calyx pass entry point -----------*- 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 the main SCF to Calyx conversion pass implementation.
10//
11//===----------------------------------------------------------------------===//
12
19#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
20#include "mlir/Conversion/LLVMCommon/Pattern.h"
21#include "mlir/Dialect/Arith/IR/Arith.h"
22#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
23#include "mlir/Dialect/Func/IR/FuncOps.h"
24#include "mlir/Dialect/Math/IR/Math.h"
25#include "mlir/Dialect/MemRef/IR/MemRef.h"
26#include "mlir/Dialect/SCF/IR/SCF.h"
27#include "mlir/IR/AsmState.h"
28#include "mlir/IR/Matchers.h"
29#include "mlir/Pass/Pass.h"
30#include "mlir/Support/LogicalResult.h"
31#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
32#include "llvm/ADT/TypeSwitch.h"
33#include "llvm/Support/LogicalResult.h"
34#include "llvm/Support/raw_os_ostream.h"
35#include "llvm/Support/raw_ostream.h"
36#include <algorithm>
37#include <filesystem>
38#include <fstream>
39
40#include <locale>
41#include <numeric>
42#include <variant>
43
44namespace circt {
45#define GEN_PASS_DEF_SCFTOCALYX
46#include "circt/Conversion/Passes.h.inc"
47} // namespace circt
48
49using namespace llvm;
50using namespace mlir;
51using namespace mlir::arith;
52using namespace mlir::cf;
53using namespace mlir::func;
54namespace circt {
55class ComponentLoweringStateInterface;
56namespace scftocalyx {
57
58static constexpr std::string_view unrolledParallelAttr = "calyx.unroll";
59
60//===----------------------------------------------------------------------===//
61// Utility types
62//===----------------------------------------------------------------------===//
63
64class ScfWhileOp : public calyx::WhileOpInterface<scf::WhileOp> {
65public:
66 explicit ScfWhileOp(scf::WhileOp op)
67 : calyx::WhileOpInterface<scf::WhileOp>(op) {}
68
69 Block::BlockArgListType getBodyArgs() override {
70 return getOperation().getAfterArguments();
71 }
72
73 Block *getBodyBlock() override { return &getOperation().getAfter().front(); }
74
75 Block *getConditionBlock() override {
76 return &getOperation().getBefore().front();
77 }
78
79 Value getConditionValue() override {
80 return getOperation().getConditionOp().getOperand(0);
81 }
82
83 std::optional<int64_t> getBound() override { return std::nullopt; }
84};
85
86class ScfForOp : public calyx::RepeatOpInterface<scf::ForOp> {
87public:
88 explicit ScfForOp(scf::ForOp op) : calyx::RepeatOpInterface<scf::ForOp>(op) {}
89
90 Block::BlockArgListType getBodyArgs() override {
91 return getOperation().getRegion().getArguments();
92 }
93
94 Block *getBodyBlock() override {
95 return &getOperation().getRegion().getBlocks().front();
96 }
97
98 std::optional<int64_t> getBound() override {
99 return constantTripCount(getOperation().getLowerBound(),
100 getOperation().getUpperBound(),
101 getOperation().getStep());
102 }
103};
104
105//===----------------------------------------------------------------------===//
106// Lowering state classes
107//===----------------------------------------------------------------------===//
108
110 scf::IfOp ifOp;
111};
112
114 /// While operation to schedule.
116};
117
119 /// For operation to schedule.
121 /// Bound
122 uint64_t bound;
123};
124
126 /// Instance for invoking.
127 calyx::InstanceOp instanceOp;
128 // CallOp for getting the arguments.
129 func::CallOp callOp;
130};
131
133 /// Parallel operation to schedule.
134 scf::ParallelOp parOp;
135};
136
137/// A variant of types representing scheduleable operations.
139 std::variant<calyx::GroupOp, WhileScheduleable, ForScheduleable,
141
143public:
144 void setCondReg(scf::IfOp op, calyx::RegisterOp regOp) {
145 Operation *operation = op.getOperation();
146 [[maybe_unused]] auto [it, succeeded] =
147 condReg.insert(std::make_pair(operation, regOp));
148 assert(succeeded &&
149 "A condition register was already set for this scf::IfOp!");
150 }
151
152 calyx::RegisterOp getCondReg(scf::IfOp op) {
153 auto it = condReg.find(op.getOperation());
154 if (it != condReg.end())
155 return it->second;
156 return nullptr;
157 }
158
159 void setThenGroup(scf::IfOp op, calyx::GroupOp group) {
160 Operation *operation = op.getOperation();
161 assert(thenGroup.count(operation) == 0 &&
162 "A then group was already set for this scf::IfOp!\n");
163 thenGroup[operation] = group;
164 }
165
166 calyx::GroupOp getThenGroup(scf::IfOp op) {
167 auto it = thenGroup.find(op.getOperation());
168 assert(it != thenGroup.end() &&
169 "No then group was set for this scf::IfOp!\n");
170 return it->second;
171 }
172
173 void setElseGroup(scf::IfOp op, calyx::GroupOp group) {
174 Operation *operation = op.getOperation();
175 assert(elseGroup.count(operation) == 0 &&
176 "An else group was already set for this scf::IfOp!\n");
177 elseGroup[operation] = group;
178 }
179
180 calyx::GroupOp getElseGroup(scf::IfOp op) {
181 auto it = elseGroup.find(op.getOperation());
182 assert(it != elseGroup.end() &&
183 "No else group was set for this scf::IfOp!\n");
184 return it->second;
185 }
186
187 void setResultRegs(scf::IfOp op, calyx::RegisterOp reg, unsigned idx) {
188 assert(resultRegs[op.getOperation()].count(idx) == 0 &&
189 "A register was already registered for the given yield result.\n");
190 assert(idx < op->getNumOperands());
191 resultRegs[op.getOperation()][idx] = reg;
192 }
193
194 const DenseMap<unsigned, calyx::RegisterOp> &getResultRegs(scf::IfOp op) {
195 return resultRegs[op.getOperation()];
196 }
197
198 calyx::RegisterOp getResultRegs(scf::IfOp op, unsigned idx) {
199 auto regs = getResultRegs(op);
200 auto it = regs.find(idx);
201 assert(it != regs.end() && "resultReg not found");
202 return it->second;
203 }
204
205private:
206 // The register to hold the result of a non-combinational guard.
207 DenseMap<Operation *, calyx::RegisterOp> condReg;
208 DenseMap<Operation *, calyx::GroupOp> thenGroup;
209 DenseMap<Operation *, calyx::GroupOp> elseGroup;
210 DenseMap<Operation *, DenseMap<unsigned, calyx::RegisterOp>> resultRegs;
211};
212
215public:
216 SmallVector<calyx::GroupOp> getWhileLoopInitGroups(ScfWhileOp op) {
217 return getLoopInitGroups(std::move(op));
218 }
220 OpBuilder &builder, ScfWhileOp op, calyx::ComponentOp componentOp,
221 Twine uniqueSuffix, MutableArrayRef<OpOperand> ops) {
222 return buildLoopIterArgAssignments(builder, std::move(op), componentOp,
223 uniqueSuffix, ops);
224 }
225 void addWhileLoopIterReg(ScfWhileOp op, calyx::RegisterOp reg, unsigned idx) {
226 return addLoopIterReg(std::move(op), reg, idx);
227 }
228 const DenseMap<unsigned, calyx::RegisterOp> &
230 return getLoopIterRegs(std::move(op));
231 }
232 void setWhileLoopLatchGroup(ScfWhileOp op, calyx::GroupOp group) {
233 return setLoopLatchGroup(std::move(op), group);
234 }
236 return getLoopLatchGroup(std::move(op));
237 }
239 SmallVector<calyx::GroupOp> groups) {
240 return setLoopInitGroups(std::move(op), std::move(groups));
241 }
242};
243
246public:
247 SmallVector<calyx::GroupOp> getForLoopInitGroups(ScfForOp op) {
248 return getLoopInitGroups(std::move(op));
249 }
251 OpBuilder &builder, ScfForOp op, calyx::ComponentOp componentOp,
252 Twine uniqueSuffix, MutableArrayRef<OpOperand> ops) {
253 return buildLoopIterArgAssignments(builder, std::move(op), componentOp,
254 uniqueSuffix, ops);
255 }
256 void addForLoopIterReg(ScfForOp op, calyx::RegisterOp reg, unsigned idx) {
257 return addLoopIterReg(std::move(op), reg, idx);
258 }
259 const DenseMap<unsigned, calyx::RegisterOp> &getForLoopIterRegs(ScfForOp op) {
260 return getLoopIterRegs(std::move(op));
261 }
262 calyx::RegisterOp getForLoopIterReg(ScfForOp op, unsigned idx) {
263 return getLoopIterReg(std::move(op), idx);
264 }
265 void setForLoopLatchGroup(ScfForOp op, calyx::GroupOp group) {
266 return setLoopLatchGroup(std::move(op), group);
267 }
268 calyx::GroupOp getForLoopLatchGroup(ScfForOp op) {
269 return getLoopLatchGroup(std::move(op));
270 }
271 void setForLoopInitGroups(ScfForOp op, SmallVector<calyx::GroupOp> groups) {
272 return setLoopInitGroups(std::move(op), std::move(groups));
273 }
274};
275
276/// Stores the state information for condition checks involving sequential
277/// computation.
279public:
280 void setSeqResReg(Operation *op, calyx::RegisterOp reg) {
281 [[maybe_unused]] auto cellOp = dyn_cast<calyx::CellInterface>(op);
282 assert(cellOp && !cellOp.isCombinational());
283 [[maybe_unused]] auto [it, succeeded] =
284 resultRegs.insert(std::make_pair(op, reg));
285 assert(succeeded &&
286 "A register was already set for this sequential operation!");
287 }
288 // Get the register for a specific pipe operation
289 calyx::RegisterOp getSeqResReg(Operation *op) {
290 auto it = resultRegs.find(op);
291 assert(it != resultRegs.end() &&
292 "No register was set for this sequential operation!");
293 return it->second;
294 }
295
296private:
297 // Maps the result of a sequential operation to the register that stores
298 // the result.
299 DenseMap<Operation *, calyx::RegisterOp> resultRegs;
300};
301
302/// Handles the current state of lowering of a Calyx component. It is mainly
303/// used as a key/value store for recording information during partial lowering,
304/// which is required at later lowering passes.
315
316//===----------------------------------------------------------------------===//
317// Conversion patterns
318//===----------------------------------------------------------------------===//
319
320/// Iterate through the operations of a source function and instantiate
321/// components or primitives based on the type of the operations.
323public:
324 BuildOpGroups(MLIRContext *context, LogicalResult &resRef,
326 DenseMap<mlir::func::FuncOp, calyx::ComponentOp> &map,
328 mlir::Pass::Option<std::string> &writeJsonOpt)
329 : FuncOpPartialLoweringPattern(context, resRef, patternState, map, state),
330 writeJson(writeJsonOpt) {}
331 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
332
333 LogicalResult
335 PatternRewriter &rewriter) const override {
336 /// We walk the operations of the funcOp to ensure that all def's have
337 /// been visited before their uses.
338 bool opBuiltSuccessfully = true;
339 funcOp.walk([&](Operation *_op) {
340 opBuiltSuccessfully &=
341 TypeSwitch<mlir::Operation *, bool>(_op)
342 .template Case<arith::ConstantOp, ReturnOp, BranchOpInterface,
343 /// SCF
344 scf::YieldOp, scf::WhileOp, scf::ForOp, scf::IfOp,
345 scf::ParallelOp, scf::ReduceOp,
346 scf::ExecuteRegionOp,
347 /// memref
348 memref::AllocOp, memref::AllocaOp, memref::LoadOp,
349 memref::StoreOp, memref::GetGlobalOp,
350 /// standard arithmetic
351 AddIOp, SubIOp, CmpIOp, ShLIOp, ShRUIOp, ShRSIOp,
352 AndIOp, XOrIOp, OrIOp, ExtUIOp, ExtSIOp, TruncIOp,
353 MulIOp, DivUIOp, DivSIOp, RemUIOp, RemSIOp,
354 /// floating point
355 AddFOp, SubFOp, MulFOp, CmpFOp, FPToSIOp, SIToFPOp,
356 DivFOp, math::SqrtOp, math::AbsFOp,
357 /// others
358 SelectOp, IndexCastOp, BitcastOp, CallOp>(
359 [&](auto op) { return buildOp(rewriter, op).succeeded(); })
360 .template Case<FuncOp, scf::ConditionOp>([&](auto) {
361 /// Skip: these special cases will be handled separately.
362 return true;
363 })
364 .Default([&](auto op) {
365 op->emitError() << "Unhandled operation during BuildOpGroups()";
366 return false;
367 });
368
369 return opBuiltSuccessfully ? WalkResult::advance()
370 : WalkResult::interrupt();
371 });
372
373 if (!writeJson.empty()) {
374 auto &extMemData = getState<ComponentLoweringState>().getExtMemData();
375 if (extMemData.getAsObject()->empty())
376 return success();
377
378 if (auto fileLoc = dyn_cast<mlir::FileLineColLoc>(funcOp->getLoc())) {
379 std::string filename = fileLoc.getFilename().str();
380 std::filesystem::path path(filename);
381 std::string jsonFileName = writeJson.getValue() + ".json";
382 auto outFileName = path.parent_path().append(jsonFileName);
383 std::ofstream outFile(outFileName);
384
385 if (!outFile.is_open()) {
386 llvm::errs() << "Unable to open file: " << outFileName.string()
387 << " for writing\n";
388 return failure();
389 }
390 llvm::raw_os_ostream llvmOut(outFile);
391 llvm::json::OStream jsonOS(llvmOut, /*IndentSize=*/2);
392 jsonOS.value(extMemData);
393 jsonOS.flush();
394 outFile.close();
395 }
396 }
397
398 return success(opBuiltSuccessfully);
399 }
400
401private:
402 mlir::Pass::Option<std::string> &writeJson;
403 /// Op builder specializations.
404 LogicalResult buildOp(PatternRewriter &rewriter, scf::YieldOp yieldOp) const;
405 LogicalResult buildOp(PatternRewriter &rewriter,
406 BranchOpInterface brOp) const;
407 LogicalResult buildOp(PatternRewriter &rewriter,
408 arith::ConstantOp constOp) const;
409 LogicalResult buildOp(PatternRewriter &rewriter, SelectOp op) const;
410 LogicalResult buildOp(PatternRewriter &rewriter, AddIOp op) const;
411 LogicalResult buildOp(PatternRewriter &rewriter, SubIOp op) const;
412 LogicalResult buildOp(PatternRewriter &rewriter, MulIOp op) const;
413 LogicalResult buildOp(PatternRewriter &rewriter, DivUIOp op) const;
414 LogicalResult buildOp(PatternRewriter &rewriter, DivSIOp op) const;
415 LogicalResult buildOp(PatternRewriter &rewriter, RemUIOp op) const;
416 LogicalResult buildOp(PatternRewriter &rewriter, RemSIOp op) const;
417 LogicalResult buildOp(PatternRewriter &rewriter, AddFOp op) const;
418 LogicalResult buildOp(PatternRewriter &rewriter, SubFOp op) const;
419 LogicalResult buildOp(PatternRewriter &rewriter, MulFOp op) const;
420 LogicalResult buildOp(PatternRewriter &rewriter, CmpFOp op) const;
421 LogicalResult buildOp(PatternRewriter &rewriter, FPToSIOp op) const;
422 LogicalResult buildOp(PatternRewriter &rewriter, SIToFPOp op) const;
423 LogicalResult buildOp(PatternRewriter &rewriter, DivFOp op) const;
424 LogicalResult buildOp(PatternRewriter &rewriter, math::SqrtOp op) const;
425 LogicalResult buildOp(PatternRewriter &rewriter, math::AbsFOp op) const;
426 LogicalResult buildOp(PatternRewriter &rewriter, ShRUIOp op) const;
427 LogicalResult buildOp(PatternRewriter &rewriter, ShRSIOp op) const;
428 LogicalResult buildOp(PatternRewriter &rewriter, ShLIOp op) const;
429 LogicalResult buildOp(PatternRewriter &rewriter, AndIOp op) const;
430 LogicalResult buildOp(PatternRewriter &rewriter, OrIOp op) const;
431 LogicalResult buildOp(PatternRewriter &rewriter, XOrIOp op) const;
432 LogicalResult buildOp(PatternRewriter &rewriter, CmpIOp op) const;
433 LogicalResult buildOp(PatternRewriter &rewriter, TruncIOp op) const;
434 LogicalResult buildOp(PatternRewriter &rewriter, ExtUIOp op) const;
435 LogicalResult buildOp(PatternRewriter &rewriter, ExtSIOp op) const;
436 LogicalResult buildOp(PatternRewriter &rewriter, ReturnOp op) const;
437 LogicalResult buildOp(PatternRewriter &rewriter, IndexCastOp op) const;
438 LogicalResult buildOp(PatternRewriter &rewriter, BitcastOp op) const;
439 LogicalResult buildOp(PatternRewriter &rewriter, memref::AllocOp op) const;
440 LogicalResult buildOp(PatternRewriter &rewriter, memref::AllocaOp op) const;
441 LogicalResult buildOp(PatternRewriter &rewriter,
442 memref::GetGlobalOp op) const;
443 LogicalResult buildOp(PatternRewriter &rewriter, memref::LoadOp op) const;
444 LogicalResult buildOp(PatternRewriter &rewriter, memref::StoreOp op) const;
445 LogicalResult buildOp(PatternRewriter &rewriter, scf::WhileOp whileOp) const;
446 LogicalResult buildOp(PatternRewriter &rewriter, scf::ForOp forOp) const;
447 LogicalResult buildOp(PatternRewriter &rewriter, scf::IfOp ifOp) const;
448 LogicalResult buildOp(PatternRewriter &rewriter,
449 scf::ReduceOp reduceOp) const;
450 LogicalResult buildOp(PatternRewriter &rewriter,
451 scf::ParallelOp parallelOp) const;
452 LogicalResult buildOp(PatternRewriter &rewriter,
453 scf::ExecuteRegionOp executeRegionOp) const;
454 LogicalResult buildOp(PatternRewriter &rewriter, CallOp callOp) const;
455
456 // Sets up the necessary state and resources for a `CmpIOp` in
457 // `buildLibraryBinaryPipeOp` if `cmpIOp` has sequential logic based on its
458 // operands.
459 template <typename TCalyxLibOp>
460 void setupCmpIOp(PatternRewriter &rewriter, CmpIOp cmpIOp, Operation *group,
461 calyx::RegisterOp &condReg, calyx::RegisterOp &resReg,
462 TCalyxLibOp calyxOp) const {
463 bool lhsIsSeqOp = calyx::parentIsSeqCell(cmpIOp.getLhs());
464 [[maybe_unused]] bool rhsIsSeqOp = calyx::parentIsSeqCell(cmpIOp.getRhs());
465
466 StringRef opName = cmpIOp.getOperationName().split(".").second;
467 Type width = cmpIOp.getResult().getType();
468
469 condReg = createRegister(
470 cmpIOp.getLoc(), rewriter, getComponent(),
471 width.getIntOrFloatBitWidth(),
472 getState<ComponentLoweringState>().getUniqueName(opName));
473
474 for (auto *user : cmpIOp->getUsers()) {
475 if (auto ifOp = dyn_cast<scf::IfOp>(user))
476 getState<ComponentLoweringState>().setCondReg(ifOp, condReg);
477 }
478
479 assert(
480 lhsIsSeqOp != rhsIsSeqOp &&
481 "unexpected sequential operation on both sides; please open an issue");
482 // If `cmpIOp`'s lhs/rhs operand is the result of a sequential operation,
483 // its result will be stored in a register.
484 resReg =
485 cast<calyx::RegisterOp>(lhsIsSeqOp ? cmpIOp.getLhs().getDefiningOp()
486 : cmpIOp.getRhs().getDefiningOp());
487
488 auto groupOp = cast<calyx::GroupOp>(group);
489 getState<ComponentLoweringState>().addBlockScheduleable(cmpIOp->getBlock(),
490 groupOp);
491
492 rewriter.setInsertionPointToEnd(groupOp.getBodyBlock());
493 auto loc = cmpIOp.getLoc();
494 assert(
495 (isa<calyx::EqLibOp, calyx::NeqLibOp, calyx::SleLibOp, calyx::SltLibOp,
496 calyx::LeLibOp, calyx::LtLibOp, calyx::GeLibOp, calyx::GtLibOp,
497 calyx::SgeLibOp, calyx::SgtLibOp>(calyxOp.getOperation())) &&
498 "Must be a Calyx comparison library operation.");
499 int64_t outputIndex = 2;
500 calyx::AssignOp::create(rewriter, loc, condReg.getIn(),
501 calyxOp.getResult(outputIndex));
502 calyx::AssignOp::create(
503 rewriter, loc, condReg.getWriteEn(),
504 createConstant(loc, rewriter,
505 getState<ComponentLoweringState>().getComponentOp(), 1,
506 1));
507 calyx::GroupDoneOp::create(rewriter, loc, condReg.getDone());
508
509 getState<ComponentLoweringState>().addSeqGuardCmpLibOp(cmpIOp);
510 }
511
512 template <typename CmpILibOp>
513 LogicalResult buildCmpIOpHelper(PatternRewriter &rewriter, CmpIOp op) const {
514 bool isIfOpGuard = std::any_of(op->getUsers().begin(), op->getUsers().end(),
515 [](auto op) { return isa<scf::IfOp>(op); });
516 bool isSeqCondCheck = isIfOpGuard && (calyx::parentIsSeqCell(op.getLhs()) ||
517 calyx::parentIsSeqCell(op.getRhs()));
518
519 if (isSeqCondCheck)
520 return buildLibraryOp<calyx::GroupOp, CmpILibOp>(rewriter, op);
521 return buildLibraryOp<calyx::CombGroupOp, CmpILibOp>(rewriter, op);
522 }
523
524 /// buildLibraryOp will build a TCalyxLibOp inside a TGroupOp based on the
525 /// source operation TSrcOp.
526 template <typename TGroupOp, typename TCalyxLibOp, typename TSrcOp>
527 LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op,
528 TypeRange srcTypes, TypeRange dstTypes) const {
529 SmallVector<Type> types;
530 for (Type srcType : srcTypes)
531 types.push_back(calyx::toBitVector(srcType));
532 for (Type dstType : dstTypes)
533 types.push_back(calyx::toBitVector(dstType));
534
535 auto calyxOp =
536 getState<ComponentLoweringState>().getNewLibraryOpInstance<TCalyxLibOp>(
537 rewriter, op.getLoc(), types);
538
539 auto directions = calyxOp.portDirections();
540 SmallVector<Value, 4> opInputPorts;
541 SmallVector<Value, 4> opOutputPorts;
542 for (auto dir : enumerate(directions)) {
543 if (dir.value() == calyx::Direction::Input)
544 opInputPorts.push_back(calyxOp.getResult(dir.index()));
545 else
546 opOutputPorts.push_back(calyxOp.getResult(dir.index()));
547 }
548 assert(
549 opInputPorts.size() == op->getNumOperands() &&
550 opOutputPorts.size() == op->getNumResults() &&
551 "Expected an equal number of in/out ports in the Calyx library op with "
552 "respect to the number of operands/results of the source operation.");
553
554 /// Create assignments to the inputs of the library op.
555 auto group = createGroupForOp<TGroupOp>(rewriter, op);
556
557 bool isSeqCondCheck = isa<calyx::GroupOp>(group);
558 calyx::RegisterOp condReg = nullptr, resReg = nullptr;
559 if (isa<CmpIOp>(op) && isSeqCondCheck) {
560 auto cmpIOp = cast<CmpIOp>(op);
561 setupCmpIOp(rewriter, cmpIOp, group, condReg, resReg, calyxOp);
562 }
563
564 rewriter.setInsertionPointToEnd(group.getBodyBlock());
565
566 for (auto dstOp : enumerate(opInputPorts)) {
567 auto srcOp = calyx::parentIsSeqCell(dstOp.value())
568 ? condReg.getOut()
569 : op->getOperand(dstOp.index());
570 calyx::AssignOp::create(rewriter, op.getLoc(), dstOp.value(), srcOp);
571 }
572
573 /// Replace the result values of the source operator with the new operator.
574 for (auto res : enumerate(opOutputPorts)) {
575 getState<ComponentLoweringState>().registerEvaluatingGroup(res.value(),
576 group);
577 auto dstOp = isSeqCondCheck ? condReg.getOut() : res.value();
578 op->getResult(res.index()).replaceAllUsesWith(dstOp);
579 }
580
581 return success();
582 }
583
584 /// buildLibraryOp which provides in- and output types based on the operands
585 /// and results of the op argument.
586 template <typename TGroupOp, typename TCalyxLibOp, typename TSrcOp>
587 LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op) const {
588 return buildLibraryOp<TGroupOp, TCalyxLibOp, TSrcOp>(
589 rewriter, op, op.getOperandTypes(), op->getResultTypes());
590 }
591
592 /// Creates a group named by the basic block which the input op resides in.
593 template <typename TGroupOp>
594 TGroupOp createGroupForOp(PatternRewriter &rewriter, Operation *op) const {
595 Block *block = op->getBlock();
596 auto groupName = getState<ComponentLoweringState>().getUniqueName(
597 loweringState().blockName(block));
598 return calyx::createGroup<TGroupOp>(
599 rewriter, getState<ComponentLoweringState>().getComponentOp(),
600 op->getLoc(), groupName);
601 }
602
603 /// buildLibraryBinaryPipeOp will build a TCalyxLibBinaryPipeOp, to
604 /// deal with MulIOp, DivUIOp and RemUIOp.
605 template <typename TOpType, typename TSrcOp>
606 LogicalResult buildLibraryBinaryPipeOp(PatternRewriter &rewriter, TSrcOp op,
607 TOpType opPipe, Value out) const {
608 StringRef opName = TSrcOp::getOperationName().split(".").second;
609 Location loc = op.getLoc();
610 Type width = op.getResult().getType();
611 auto reg = createRegister(
612 op.getLoc(), rewriter, getComponent(), width.getIntOrFloatBitWidth(),
613 getState<ComponentLoweringState>().getUniqueName(opName));
614
615 // Operation pipelines are not combinational, so a GroupOp is required.
616 auto group = createGroupForOp<calyx::GroupOp>(rewriter, op);
617 OpBuilder builder(group->getRegion(0));
618 getState<ComponentLoweringState>().addBlockScheduleable(op->getBlock(),
619 group);
620
621 rewriter.setInsertionPointToEnd(group.getBodyBlock());
622 if constexpr (std::is_same_v<TSrcOp, math::SqrtOp>)
623 // According to the Hardfloat library: "If sqrtOp is 1, the operation is
624 // the square root of a, and operand b is ignored."
625 calyx::AssignOp::create(rewriter, loc, opPipe.getLeft(), op.getOperand());
626 else {
627 calyx::AssignOp::create(rewriter, loc, opPipe.getLeft(), op.getLhs());
628 calyx::AssignOp::create(rewriter, loc, opPipe.getRight(), op.getRhs());
629 }
630 // Write the output to this register.
631 calyx::AssignOp::create(rewriter, loc, reg.getIn(), out);
632 // The write enable port is high when the pipeline is done.
633 calyx::AssignOp::create(rewriter, loc, reg.getWriteEn(), opPipe.getDone());
634 // Set pipelineOp to high as long as its done signal is not high.
635 // This prevents the pipelineOP from executing for the cycle that we write
636 // to register. To get !(pipelineOp.done) we do 1 xor pipelineOp.done
637 hw::ConstantOp c1 = createConstant(loc, rewriter, getComponent(), 1, 1);
638 calyx::AssignOp::create(
639 rewriter, loc, opPipe.getGo(), c1,
640 comb::createOrFoldNot(group.getLoc(), opPipe.getDone(), builder));
641 // The group is done when the register write is complete.
642 calyx::GroupDoneOp::create(rewriter, loc, reg.getDone());
643
644 // Pass the result from the source operation to register holding the resullt
645 // from the Calyx primitive.
646 op.getResult().replaceAllUsesWith(reg.getOut());
647
648 if (isa<calyx::AddFOpIEEE754>(opPipe)) {
649 auto opFOp = cast<calyx::AddFOpIEEE754>(opPipe);
650 hw::ConstantOp subOp;
651 if (isa<arith::AddFOp>(op)) {
652 subOp = createConstant(loc, rewriter, getComponent(), /*width=*/1,
653 /*subtract=*/0);
654 } else {
655 subOp = createConstant(loc, rewriter, getComponent(), /*width=*/1,
656 /*subtract=*/1);
657 }
658 calyx::AssignOp::create(rewriter, loc, opFOp.getSubOp(), subOp);
659 } else if (auto opFOp =
660 dyn_cast<calyx::DivSqrtOpIEEE754>(opPipe.getOperation())) {
661 bool isSqrt = !isa<arith::DivFOp>(op);
662 hw::ConstantOp sqrtOp =
663 createConstant(loc, rewriter, getComponent(), /*width=*/1, isSqrt);
664 calyx::AssignOp::create(rewriter, loc, opFOp.getSqrtOp(), sqrtOp);
665 }
666
667 // Register the values for the pipeline.
668 getState<ComponentLoweringState>().registerEvaluatingGroup(out, group);
669 getState<ComponentLoweringState>().registerEvaluatingGroup(opPipe.getLeft(),
670 group);
671 getState<ComponentLoweringState>().registerEvaluatingGroup(
672 opPipe.getRight(), group);
673
674 getState<ComponentLoweringState>().setSeqResReg(out.getDefiningOp(), reg);
675
676 return success();
677 }
678
679 template <typename TCalyxLibOp, typename TSrcOp>
680 LogicalResult buildFpIntTypeCastOp(PatternRewriter &rewriter, TSrcOp op,
681 unsigned inputWidth, unsigned outputWidth,
682 StringRef signedPort) const {
683 Location loc = op.getLoc();
684 IntegerType one = rewriter.getI1Type(),
685 inWidth = rewriter.getIntegerType(inputWidth),
686 outWidth = rewriter.getIntegerType(outputWidth);
687 auto calyxOp =
688 getState<ComponentLoweringState>().getNewLibraryOpInstance<TCalyxLibOp>(
689 rewriter, loc, {one, one, one, inWidth, one, outWidth, one});
690 hw::ConstantOp c1 = createConstant(loc, rewriter, getComponent(), 1, 1);
691 StringRef opName = op.getOperationName().split(".").second;
692 rewriter.setInsertionPointToStart(getComponent().getBodyBlock());
693 auto reg = createRegister(
694 loc, rewriter, getComponent(), outWidth.getIntOrFloatBitWidth(),
695 getState<ComponentLoweringState>().getUniqueName(opName));
696
697 auto group = createGroupForOp<calyx::GroupOp>(rewriter, op);
698 OpBuilder builder(group->getRegion(0));
699 getState<ComponentLoweringState>().addBlockScheduleable(op->getBlock(),
700 group);
701
702 rewriter.setInsertionPointToEnd(group.getBodyBlock());
703 calyx::AssignOp::create(rewriter, loc, calyxOp.getIn(), op.getIn());
704 if (isa<calyx::FpToIntOpIEEE754>(calyxOp)) {
705 calyx::AssignOp::create(
706 rewriter, loc, cast<calyx::FpToIntOpIEEE754>(calyxOp).getSignedOut(),
707 c1);
708 } else if (isa<calyx::IntToFpOpIEEE754>(calyxOp)) {
709 calyx::AssignOp::create(
710 rewriter, loc, cast<calyx::IntToFpOpIEEE754>(calyxOp).getSignedIn(),
711 c1);
712 }
713 op.getResult().replaceAllUsesWith(reg.getOut());
714
715 calyx::AssignOp::create(rewriter, loc, reg.getIn(), calyxOp.getOut());
716 calyx::AssignOp::create(rewriter, loc, reg.getWriteEn(), c1);
717
718 calyx::AssignOp::create(
719 rewriter, loc, calyxOp.getGo(), c1,
720 comb::createOrFoldNot(loc, calyxOp.getDone(), builder));
721 calyx::GroupDoneOp::create(rewriter, loc, reg.getDone());
722
723 return success();
724 }
725
726 /// Creates assignments within the provided group to the address ports of the
727 /// memoryOp based on the provided addressValues.
728 void assignAddressPorts(PatternRewriter &rewriter, Location loc,
729 calyx::GroupInterface group,
730 calyx::MemoryInterface memoryInterface,
731 Operation::operand_range addressValues) const {
732 IRRewriter::InsertionGuard guard(rewriter);
733 rewriter.setInsertionPointToEnd(group.getBody());
734 auto addrPorts = memoryInterface.addrPorts();
735 if (addressValues.empty()) {
736 assert(
737 addrPorts.size() == 1 &&
738 "We expected a 1 dimensional memory of size 1 because there were no "
739 "address assignment values");
740 // Assign to address 1'd0 in memory.
741 calyx::AssignOp::create(
742 rewriter, loc, addrPorts[0],
743 createConstant(loc, rewriter, getComponent(), 1, 0));
744 } else {
745 assert(addrPorts.size() == addressValues.size() &&
746 "Mismatch between number of address ports of the provided memory "
747 "and address assignment values");
748 for (auto address : enumerate(addressValues))
749 calyx::AssignOp::create(rewriter, loc, addrPorts[address.index()],
750 address.value());
751 }
752 }
753
754 calyx::RegisterOp createSignalRegister(PatternRewriter &rewriter,
755 Value signal, bool invert,
756 StringRef nameSuffix,
757 calyx::CompareFOpIEEE754 calyxCmpFOp,
758 calyx::GroupOp group) const {
759 Location loc = calyxCmpFOp.getLoc();
760 IntegerType one = rewriter.getI1Type();
761 auto component = getComponent();
762 OpBuilder builder(group->getRegion(0));
763 auto reg = createRegister(
764 loc, rewriter, component, 1,
765 getState<ComponentLoweringState>().getUniqueName(nameSuffix));
766 calyx::AssignOp::create(rewriter, loc, reg.getWriteEn(),
767 calyxCmpFOp.getDone());
768 if (invert) {
769 auto notLibOp = getState<ComponentLoweringState>()
770 .getNewLibraryOpInstance<calyx::NotLibOp>(
771 rewriter, loc, {one, one});
772 calyx::AssignOp::create(rewriter, loc, notLibOp.getIn(), signal);
773 calyx::AssignOp::create(rewriter, loc, reg.getIn(), notLibOp.getOut());
774 getState<ComponentLoweringState>().registerEvaluatingGroup(
775 notLibOp.getOut(), group);
776 } else
777 calyx::AssignOp::create(rewriter, loc, reg.getIn(), signal);
778 return reg;
779 };
780};
781
782LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
783 memref::LoadOp loadOp) const {
784 Value memref = loadOp.getMemref();
785 auto memoryInterface =
786 getState<ComponentLoweringState>().getMemoryInterface(memref);
787 auto group = createGroupForOp<calyx::GroupOp>(rewriter, loadOp);
788 assignAddressPorts(rewriter, loadOp.getLoc(), group, memoryInterface,
789 loadOp.getIndices());
790
791 rewriter.setInsertionPointToEnd(group.getBodyBlock());
792
793 bool needReg = true;
794 Value res;
795 Value regWriteEn =
796 createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
797 if (memoryInterface.readEnOpt().has_value()) {
798 auto oneI1 =
799 calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
800 calyx::AssignOp::create(rewriter, loadOp.getLoc(), memoryInterface.readEn(),
801 oneI1);
802 regWriteEn = memoryInterface.done();
803 if (calyx::noStoresToMemory(memref) &&
805 // Single load from memory; we do not need to write the output to a
806 // register. The readData value will be held until readEn is asserted
807 // again
808 needReg = false;
809 calyx::GroupDoneOp::create(rewriter, loadOp.getLoc(),
810 memoryInterface.done());
811 // We refrain from replacing the loadOp result with
812 // memoryInterface.readData, since multiple loadOp's need to be converted
813 // to a single memory's ReadData. If this replacement is done now, we lose
814 // the link between which SSA memref::LoadOp values map to which groups
815 // for loading a value from the Calyx memory. At this point of lowering,
816 // we keep the memref::LoadOp SSA value, and do value replacement _after_
817 // control has been generated (see LateSSAReplacement). This is *vital*
818 // for things such as calyx::InlineCombGroups to be able to properly track
819 // which memory assignment groups belong to which accesses.
820 res = loadOp.getResult();
821 }
822 } else if (memoryInterface.contentEnOpt().has_value()) {
823 auto oneI1 =
824 calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 1);
825 auto zeroI1 =
826 calyx::createConstant(loadOp.getLoc(), rewriter, getComponent(), 1, 0);
827 calyx::AssignOp::create(rewriter, loadOp.getLoc(),
828 memoryInterface.contentEn(), oneI1);
829 calyx::AssignOp::create(rewriter, loadOp.getLoc(),
830 memoryInterface.writeEn(), zeroI1);
831 regWriteEn = memoryInterface.done();
832 if (calyx::noStoresToMemory(memref) &&
834 // Single load from memory; we do not need to write the output to a
835 // register. The readData value will be held until contentEn is asserted
836 // again
837 needReg = false;
838 calyx::GroupDoneOp::create(rewriter, loadOp.getLoc(),
839 memoryInterface.done());
840 // We refrain from replacing the loadOp result with
841 // memoryInterface.readData, since multiple loadOp's need to be converted
842 // to a single memory's ReadData. If this replacement is done now, we lose
843 // the link between which SSA memref::LoadOp values map to which groups
844 // for loading a value from the Calyx memory. At this point of lowering,
845 // we keep the memref::LoadOp SSA value, and do value replacement _after_
846 // control has been generated (see LateSSAReplacement). This is *vital*
847 // for things such as calyx::InlineCombGroups to be able to properly track
848 // which memory assignment groups belong to which accesses.
849 res = loadOp.getResult();
850 }
851 }
852
853 if (needReg) {
854 // Multiple loads from the same memory; In this case, we _may_ have a
855 // structural hazard in the design we generate. To get around this, we
856 // conservatively place a register in front of each load operation, and
857 // replace all uses of the loaded value with the register output. Reading
858 // for sequential memories will cause a read to take at least 2 cycles,
859 // but it will usually be better because combinational reads on memories
860 // can significantly decrease the maximum achievable frequency.
861 auto reg = createRegister(
862 loadOp.getLoc(), rewriter, getComponent(),
863 loadOp.getMemRefType().getElementTypeBitWidth(),
864 getState<ComponentLoweringState>().getUniqueName("load"));
865 rewriter.setInsertionPointToEnd(group.getBodyBlock());
866 calyx::AssignOp::create(rewriter, loadOp.getLoc(), reg.getIn(),
867 memoryInterface.readData());
868 calyx::AssignOp::create(rewriter, loadOp.getLoc(), reg.getWriteEn(),
869 regWriteEn);
870 calyx::GroupDoneOp::create(rewriter, loadOp.getLoc(), reg.getDone());
871 loadOp.getResult().replaceAllUsesWith(reg.getOut());
872 res = reg.getOut();
873 }
874
875 getState<ComponentLoweringState>().registerEvaluatingGroup(res, group);
876 getState<ComponentLoweringState>().addBlockScheduleable(loadOp->getBlock(),
877 group);
878 return success();
879}
880
881LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
882 memref::StoreOp storeOp) const {
883 auto memoryInterface = getState<ComponentLoweringState>().getMemoryInterface(
884 storeOp.getMemref());
885 auto group = createGroupForOp<calyx::GroupOp>(rewriter, storeOp);
886
887 // This is a sequential group, so register it as being scheduleable for the
888 // block.
889 getState<ComponentLoweringState>().addBlockScheduleable(storeOp->getBlock(),
890 group);
891 assignAddressPorts(rewriter, storeOp.getLoc(), group, memoryInterface,
892 storeOp.getIndices());
893 rewriter.setInsertionPointToEnd(group.getBodyBlock());
894 calyx::AssignOp::create(rewriter, storeOp.getLoc(),
895 memoryInterface.writeData(),
896 storeOp.getValueToStore());
897 calyx::AssignOp::create(
898 rewriter, storeOp.getLoc(), memoryInterface.writeEn(),
899 createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
900 if (memoryInterface.contentEnOpt().has_value()) {
901 // If memory has content enable, it must be asserted when writing
902 calyx::AssignOp::create(
903 rewriter, storeOp.getLoc(), memoryInterface.contentEn(),
904 createConstant(storeOp.getLoc(), rewriter, getComponent(), 1, 1));
905 }
906 calyx::GroupDoneOp::create(rewriter, storeOp.getLoc(),
907 memoryInterface.done());
908
909 return success();
910}
911
912LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
913 MulIOp mul) const {
914 Location loc = mul.getLoc();
915 Type width = mul.getResult().getType(), one = rewriter.getI1Type();
916 auto mulPipe =
917 getState<ComponentLoweringState>()
918 .getNewLibraryOpInstance<calyx::MultPipeLibOp>(
919 rewriter, loc, {one, one, one, width, width, width, one});
920 return buildLibraryBinaryPipeOp<calyx::MultPipeLibOp>(
921 rewriter, mul, mulPipe,
922 /*out=*/mulPipe.getOut());
923}
924
925LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
926 DivUIOp div) const {
927 Location loc = div.getLoc();
928 Type width = div.getResult().getType(), one = rewriter.getI1Type();
929 auto divPipe =
930 getState<ComponentLoweringState>()
931 .getNewLibraryOpInstance<calyx::DivUPipeLibOp>(
932 rewriter, loc, {one, one, one, width, width, width, one});
933 return buildLibraryBinaryPipeOp<calyx::DivUPipeLibOp>(
934 rewriter, div, divPipe,
935 /*out=*/divPipe.getOut());
936}
937
938LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
939 DivSIOp div) const {
940 Location loc = div.getLoc();
941 Type width = div.getResult().getType(), one = rewriter.getI1Type();
942 auto divPipe =
943 getState<ComponentLoweringState>()
944 .getNewLibraryOpInstance<calyx::DivSPipeLibOp>(
945 rewriter, loc, {one, one, one, width, width, width, one});
946 return buildLibraryBinaryPipeOp<calyx::DivSPipeLibOp>(
947 rewriter, div, divPipe,
948 /*out=*/divPipe.getOut());
949}
950
951LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
952 RemUIOp rem) const {
953 Location loc = rem.getLoc();
954 Type width = rem.getResult().getType(), one = rewriter.getI1Type();
955 auto remPipe =
956 getState<ComponentLoweringState>()
957 .getNewLibraryOpInstance<calyx::RemUPipeLibOp>(
958 rewriter, loc, {one, one, one, width, width, width, one});
959 return buildLibraryBinaryPipeOp<calyx::RemUPipeLibOp>(
960 rewriter, rem, remPipe,
961 /*out=*/remPipe.getOut());
962}
963
964LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
965 RemSIOp rem) const {
966 Location loc = rem.getLoc();
967 Type width = rem.getResult().getType(), one = rewriter.getI1Type();
968 auto remPipe =
969 getState<ComponentLoweringState>()
970 .getNewLibraryOpInstance<calyx::RemSPipeLibOp>(
971 rewriter, loc, {one, one, one, width, width, width, one});
972 return buildLibraryBinaryPipeOp<calyx::RemSPipeLibOp>(
973 rewriter, rem, remPipe,
974 /*out=*/remPipe.getOut());
975}
976
977LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
978 AddFOp addf) const {
979 Location loc = addf.getLoc();
980 IntegerType one = rewriter.getI1Type(), three = rewriter.getIntegerType(3),
981 five = rewriter.getIntegerType(5),
982 width = rewriter.getIntegerType(
983 addf.getType().getIntOrFloatBitWidth());
984 auto addFOp =
985 getState<ComponentLoweringState>()
986 .getNewLibraryOpInstance<calyx::AddFOpIEEE754>(
987 rewriter, loc,
988 {one, one, one, one, one, width, width, three, width, five, one});
989 return buildLibraryBinaryPipeOp<calyx::AddFOpIEEE754>(rewriter, addf, addFOp,
990 addFOp.getOut());
991}
992
993LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
994 SubFOp subf) const {
995 Location loc = subf.getLoc();
996 IntegerType one = rewriter.getI1Type(), three = rewriter.getIntegerType(3),
997 five = rewriter.getIntegerType(5),
998 width = rewriter.getIntegerType(
999 subf.getType().getIntOrFloatBitWidth());
1000 auto subFOp =
1001 getState<ComponentLoweringState>()
1002 .getNewLibraryOpInstance<calyx::AddFOpIEEE754>(
1003 rewriter, loc,
1004 {one, one, one, one, one, width, width, three, width, five, one});
1005 return buildLibraryBinaryPipeOp<calyx::AddFOpIEEE754>(rewriter, subf, subFOp,
1006 subFOp.getOut());
1007}
1008
1009LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1010 MulFOp mulf) const {
1011 Location loc = mulf.getLoc();
1012 IntegerType one = rewriter.getI1Type(), three = rewriter.getIntegerType(3),
1013 five = rewriter.getIntegerType(5),
1014 width = rewriter.getIntegerType(
1015 mulf.getType().getIntOrFloatBitWidth());
1016 auto mulFOp =
1017 getState<ComponentLoweringState>()
1018 .getNewLibraryOpInstance<calyx::MulFOpIEEE754>(
1019 rewriter, loc,
1020 {one, one, one, one, width, width, three, width, five, one});
1021 return buildLibraryBinaryPipeOp<calyx::MulFOpIEEE754>(rewriter, mulf, mulFOp,
1022 mulFOp.getOut());
1023}
1024
1025LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1026 CmpFOp cmpf) const {
1027 Location loc = cmpf.getLoc();
1028 IntegerType one = rewriter.getI1Type(), five = rewriter.getIntegerType(5),
1029 width = rewriter.getIntegerType(
1030 cmpf.getLhs().getType().getIntOrFloatBitWidth());
1031 auto calyxCmpFOp = getState<ComponentLoweringState>()
1032 .getNewLibraryOpInstance<calyx::CompareFOpIEEE754>(
1033 rewriter, loc,
1034 {one, one, one, width, width, one, one, one, one,
1035 one, five, one});
1036 hw::ConstantOp c0 = createConstant(loc, rewriter, getComponent(), 1, 0);
1037 hw::ConstantOp c1 = createConstant(loc, rewriter, getComponent(), 1, 1);
1038 rewriter.setInsertionPointToStart(getComponent().getBodyBlock());
1039
1041 using CombLogic = PredicateInfo::CombLogic;
1042 using Port = PredicateInfo::InputPorts::Port;
1043 PredicateInfo info = calyx::getPredicateInfo(cmpf.getPredicate());
1044 if (info.logic == CombLogic::None) {
1045 if (cmpf.getPredicate() == CmpFPredicate::AlwaysTrue) {
1046 cmpf.getResult().replaceAllUsesWith(c1);
1047 return success();
1048 }
1049
1050 if (cmpf.getPredicate() == CmpFPredicate::AlwaysFalse) {
1051 cmpf.getResult().replaceAllUsesWith(c0);
1052 return success();
1053 }
1054 }
1055
1056 // General case
1057 StringRef opName = cmpf.getOperationName().split(".").second;
1058 auto reg =
1059 createRegister(loc, rewriter, getComponent(), 1,
1060 getState<ComponentLoweringState>().getUniqueName(opName));
1061
1062 // Operation pipelines are not combinational, so a GroupOp is required.
1063 auto group = createGroupForOp<calyx::GroupOp>(rewriter, cmpf);
1064 OpBuilder builder(group->getRegion(0));
1065 getState<ComponentLoweringState>().addBlockScheduleable(cmpf->getBlock(),
1066 group);
1067
1068 rewriter.setInsertionPointToEnd(group.getBodyBlock());
1069 calyx::AssignOp::create(rewriter, loc, calyxCmpFOp.getLeft(), cmpf.getLhs());
1070 calyx::AssignOp::create(rewriter, loc, calyxCmpFOp.getRight(), cmpf.getRhs());
1071
1072 bool signalingFlag = false;
1073 switch (cmpf.getPredicate()) {
1074 case CmpFPredicate::UGT:
1075 case CmpFPredicate::UGE:
1076 case CmpFPredicate::ULT:
1077 case CmpFPredicate::ULE:
1078 case CmpFPredicate::OGT:
1079 case CmpFPredicate::OGE:
1080 case CmpFPredicate::OLT:
1081 case CmpFPredicate::OLE:
1082 signalingFlag = true;
1083 break;
1084 case CmpFPredicate::UEQ:
1085 case CmpFPredicate::UNE:
1086 case CmpFPredicate::OEQ:
1087 case CmpFPredicate::ONE:
1088 case CmpFPredicate::UNO:
1089 case CmpFPredicate::ORD:
1090 case CmpFPredicate::AlwaysTrue:
1091 case CmpFPredicate::AlwaysFalse:
1092 signalingFlag = false;
1093 break;
1094 }
1095
1096 // The IEEE Standard mandates that equality comparisons ordinarily are quiet,
1097 // while inequality comparisons ordinarily are signaling.
1098 calyx::AssignOp::create(rewriter, loc, calyxCmpFOp.getSignaling(),
1099 signalingFlag ? c1 : c0);
1100
1101 // Prepare signals and create registers
1102 SmallVector<calyx::RegisterOp> inputRegs;
1103 for (const auto &input : info.inputPorts) {
1104 Value signal;
1105 switch (input.port) {
1106 case Port::Eq: {
1107 signal = calyxCmpFOp.getEq();
1108 break;
1109 }
1110 case Port::Gt: {
1111 signal = calyxCmpFOp.getGt();
1112 break;
1113 }
1114 case Port::Lt: {
1115 signal = calyxCmpFOp.getLt();
1116 break;
1117 }
1118 case Port::Unordered: {
1119 signal = calyxCmpFOp.getUnordered();
1120 break;
1121 }
1122 }
1123 std::string nameSuffix =
1124 (input.port == PredicateInfo::InputPorts::Port::Unordered)
1125 ? "unordered_port"
1126 : "compare_port";
1127 auto signalReg = createSignalRegister(rewriter, signal, input.invert,
1128 nameSuffix, calyxCmpFOp, group);
1129 inputRegs.push_back(signalReg);
1130 }
1131
1132 // Create the output logical operation
1133 Value outputValue, doneValue;
1134 switch (info.logic) {
1135 case CombLogic::None: {
1136 // it's guaranteed to be either ORD or UNO
1137 outputValue = inputRegs[0].getOut();
1138 doneValue = inputRegs[0].getDone();
1139 break;
1140 }
1141 case CombLogic::And: {
1142 auto outputLibOp = getState<ComponentLoweringState>()
1143 .getNewLibraryOpInstance<calyx::AndLibOp>(
1144 rewriter, loc, {one, one, one});
1145 calyx::AssignOp::create(rewriter, loc, outputLibOp.getLeft(),
1146 inputRegs[0].getOut());
1147 calyx::AssignOp::create(rewriter, loc, outputLibOp.getRight(),
1148 inputRegs[1].getOut());
1149
1150 outputValue = outputLibOp.getOut();
1151 break;
1152 }
1153 case CombLogic::Or: {
1154 auto outputLibOp = getState<ComponentLoweringState>()
1155 .getNewLibraryOpInstance<calyx::OrLibOp>(
1156 rewriter, loc, {one, one, one});
1157 calyx::AssignOp::create(rewriter, loc, outputLibOp.getLeft(),
1158 inputRegs[0].getOut());
1159 calyx::AssignOp::create(rewriter, loc, outputLibOp.getRight(),
1160 inputRegs[1].getOut());
1161
1162 outputValue = outputLibOp.getOut();
1163 break;
1164 }
1165 }
1166
1167 if (info.logic != CombLogic::None) {
1168 auto doneLibOp = getState<ComponentLoweringState>()
1169 .getNewLibraryOpInstance<calyx::AndLibOp>(
1170 rewriter, loc, {one, one, one});
1171 calyx::AssignOp::create(rewriter, loc, doneLibOp.getLeft(),
1172 inputRegs[0].getDone());
1173 calyx::AssignOp::create(rewriter, loc, doneLibOp.getRight(),
1174 inputRegs[1].getDone());
1175 doneValue = doneLibOp.getOut();
1176 }
1177
1178 // Write to the output register
1179 calyx::AssignOp::create(rewriter, loc, reg.getIn(), outputValue);
1180 calyx::AssignOp::create(rewriter, loc, reg.getWriteEn(), doneValue);
1181
1182 // Set the go and done signal
1183 calyx::AssignOp::create(
1184 rewriter, loc, calyxCmpFOp.getGo(), c1,
1185 comb::createOrFoldNot(loc, calyxCmpFOp.getDone(), builder));
1186 calyx::GroupDoneOp::create(rewriter, loc, reg.getDone());
1187
1188 cmpf.getResult().replaceAllUsesWith(reg.getOut());
1189
1190 // Register evaluating groups
1191 getState<ComponentLoweringState>().registerEvaluatingGroup(outputValue,
1192 group);
1193 getState<ComponentLoweringState>().registerEvaluatingGroup(doneValue, group);
1194 getState<ComponentLoweringState>().registerEvaluatingGroup(
1195 calyxCmpFOp.getLeft(), group);
1196 getState<ComponentLoweringState>().registerEvaluatingGroup(
1197 calyxCmpFOp.getRight(), group);
1198
1199 return success();
1200}
1201
1202LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1203 FPToSIOp fptosi) const {
1204 return buildFpIntTypeCastOp<calyx::FpToIntOpIEEE754>(
1205 rewriter, fptosi, fptosi.getIn().getType().getIntOrFloatBitWidth(),
1206 fptosi.getOut().getType().getIntOrFloatBitWidth(), "signedOut");
1207}
1208
1209LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1210 SIToFPOp sitofp) const {
1211 return buildFpIntTypeCastOp<calyx::IntToFpOpIEEE754>(
1212 rewriter, sitofp, sitofp.getIn().getType().getIntOrFloatBitWidth(),
1213 sitofp.getOut().getType().getIntOrFloatBitWidth(), "signedIn");
1214}
1215
1216LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1217 DivFOp divf) const {
1218 Location loc = divf.getLoc();
1219 IntegerType one = rewriter.getI1Type(), three = rewriter.getIntegerType(3),
1220 five = rewriter.getIntegerType(5),
1221 width = rewriter.getIntegerType(
1222 divf.getType().getIntOrFloatBitWidth());
1223 auto divFOp = getState<ComponentLoweringState>()
1224 .getNewLibraryOpInstance<calyx::DivSqrtOpIEEE754>(
1225 rewriter, loc,
1226 {/*clk=*/one, /*reset=*/one, /*go=*/one,
1227 /*control=*/one, /*sqrtOp=*/one, /*left=*/width,
1228 /*right=*/width, /*roundingMode=*/three, /*out=*/width,
1229 /*exceptionalFlags=*/five, /*done=*/one});
1230 return buildLibraryBinaryPipeOp<calyx::DivSqrtOpIEEE754>(
1231 rewriter, divf, divFOp, divFOp.getOut());
1232}
1233
1234LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1235 math::SqrtOp sqrt) const {
1236 Location loc = sqrt.getLoc();
1237 IntegerType one = rewriter.getI1Type(), three = rewriter.getIntegerType(3),
1238 five = rewriter.getIntegerType(5),
1239 width = rewriter.getIntegerType(
1240 sqrt.getType().getIntOrFloatBitWidth());
1241 auto sqrtOp = getState<ComponentLoweringState>()
1242 .getNewLibraryOpInstance<calyx::DivSqrtOpIEEE754>(
1243 rewriter, loc,
1244 {/*clk=*/one, /*reset=*/one, /*go=*/one,
1245 /*control=*/one, /*sqrtOp=*/one, /*left=*/width,
1246 /*right=*/width, /*roundingMode=*/three, /*out=*/width,
1247 /*exceptionalFlags=*/five, /*done=*/one});
1248 return buildLibraryBinaryPipeOp<calyx::DivSqrtOpIEEE754>(
1249 rewriter, sqrt, sqrtOp, sqrtOp.getOut());
1250}
1251
1252LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1253 math::AbsFOp absFOp) const {
1254 Location loc = absFOp.getLoc();
1255 auto input = absFOp.getOperand();
1256
1257 unsigned bitwidth = input.getType().getIntOrFloatBitWidth();
1258 Type intTy = rewriter.getIntegerType(bitwidth);
1259
1260 uint64_t signBit = 1ULL << (bitwidth - 1);
1261 uint64_t absMask = ~signBit & ((1ULL << bitwidth) - 1); // clear sign bit
1262
1263 Value maskOp = arith::ConstantIntOp::create(rewriter, loc, intTy, absMask);
1264
1265 auto combGroup = createGroupForOp<calyx::CombGroupOp>(rewriter, absFOp);
1266 rewriter.setInsertionPointToStart(combGroup.getBodyBlock());
1267
1268 auto andLibOp = getState<ComponentLoweringState>()
1269 .getNewLibraryOpInstance<calyx::AndLibOp>(
1270 rewriter, loc, {intTy, intTy, intTy});
1271 calyx::AssignOp::create(rewriter, loc, andLibOp.getLeft(), maskOp);
1272 calyx::AssignOp::create(rewriter, loc, andLibOp.getRight(), input);
1273
1274 getState<ComponentLoweringState>().registerEvaluatingGroup(andLibOp.getOut(),
1275 combGroup);
1276 rewriter.replaceAllUsesWith(absFOp, andLibOp.getOut());
1277
1278 return success();
1279}
1280
1281template <typename TAllocOp>
1282static LogicalResult buildAllocOp(ComponentLoweringState &componentState,
1283 PatternRewriter &rewriter, TAllocOp allocOp) {
1284 rewriter.setInsertionPointToStart(
1285 componentState.getComponentOp().getBodyBlock());
1286 MemRefType memtype = allocOp.getType();
1287 SmallVector<int64_t> addrSizes;
1288 SmallVector<int64_t> sizes;
1289 for (int64_t dim : memtype.getShape()) {
1290 sizes.push_back(dim);
1291 addrSizes.push_back(calyx::handleZeroWidth(dim));
1292 }
1293 // If memref has no size (e.g., memref<i32>) create a 1 dimensional memory of
1294 // size 1.
1295 if (sizes.empty() && addrSizes.empty()) {
1296 sizes.push_back(1);
1297 addrSizes.push_back(1);
1298 }
1299 auto memoryOp = calyx::SeqMemoryOp::create(
1300 rewriter, allocOp.getLoc(), componentState.getUniqueName("mem"),
1301 memtype.getElementType().getIntOrFloatBitWidth(), sizes, addrSizes);
1302
1303 // Externalize memories conditionally (only in the top-level component because
1304 // Calyx compiler requires it as a well-formness check).
1305 memoryOp->setAttr("external",
1306 IntegerAttr::get(rewriter.getI1Type(), llvm::APInt(1, 1)));
1307 componentState.registerMemoryInterface(allocOp.getResult(),
1308 calyx::MemoryInterface(memoryOp));
1309
1310 unsigned elmTyBitWidth = memtype.getElementTypeBitWidth();
1311 assert(elmTyBitWidth <= 64 && "element bitwidth should not exceed 64");
1312 bool isFloat = !memtype.getElementType().isInteger();
1313
1314 auto shape = allocOp.getType().getShape();
1315 int totalSize =
1316 std::reduce(shape.begin(), shape.end(), 1, std::multiplies<int>());
1317 // The `totalSize <= 1` check is a hack to:
1318 // https://github.com/llvm/circt/pull/2661, where a multi-dimensional memory
1319 // whose size in some dimension equals 1, e.g. memref<1x1x1x1xi32>, will be
1320 // collapsed to `memref<1xi32>` with `totalSize == 1`. While the above case is
1321 // a trivial fix, Calyx expects 1-dimensional memories in general:
1322 // https://github.com/calyxir/calyx/issues/907
1323 if (!(shape.size() <= 1 || totalSize <= 1)) {
1324 allocOp.emitError("input memory dimension must be empty or one.");
1325 return failure();
1326 }
1327
1328 std::vector<uint64_t> flattenedVals(totalSize, 0);
1329 if (isa<memref::GetGlobalOp>(allocOp)) {
1330 auto getGlobalOp = cast<memref::GetGlobalOp>(allocOp);
1331 auto *symbolTableOp =
1332 getGlobalOp->template getParentWithTrait<mlir::OpTrait::SymbolTable>();
1333 auto globalOp = dyn_cast_or_null<memref::GlobalOp>(
1334 SymbolTable::lookupSymbolIn(symbolTableOp, getGlobalOp.getNameAttr()));
1335 // Flatten the values in the attribute
1336 auto cstAttr = llvm::dyn_cast_or_null<DenseElementsAttr>(
1337 globalOp.getConstantInitValue());
1338 int sizeCount = 0;
1339 for (auto attr : cstAttr.template getValues<Attribute>()) {
1340 assert((isa<mlir::FloatAttr, mlir::IntegerAttr>(attr)) &&
1341 "memory attributes must be float or int");
1342 if (auto fltAttr = dyn_cast<mlir::FloatAttr>(attr)) {
1343 flattenedVals[sizeCount++] =
1344 bit_cast<uint64_t>(fltAttr.getValueAsDouble());
1345 } else {
1346 auto intAttr = dyn_cast<mlir::IntegerAttr>(attr);
1347 APInt value = intAttr.getValue();
1348 flattenedVals[sizeCount++] = *value.getRawData();
1349 }
1350 }
1351
1352 rewriter.eraseOp(globalOp);
1353 }
1354
1355 llvm::json::Array result;
1356 result.reserve(std::max(static_cast<int>(shape.size()), 1));
1357
1358 Type elemType = memtype.getElementType();
1359 bool isSigned =
1360 !elemType.isSignlessInteger() && !elemType.isUnsignedInteger();
1361 for (uint64_t bitValue : flattenedVals) {
1362 llvm::json::Value value = 0;
1363 if (isFloat) {
1364 // We cast to `double` and let downstream calyx to deal with the actual
1365 // value's precision handling.
1366 value = bit_cast<double>(bitValue);
1367 } else {
1368 APInt apInt(/*numBits=*/elmTyBitWidth, bitValue, isSigned,
1369 /*implicitTrunc=*/true);
1370 // The conditional ternary operation will cause the `value` to interpret
1371 // the underlying data as unsigned regardless `isSigned` or not.
1372 if (isSigned)
1373 value = static_cast<int64_t>(apInt.getSExtValue());
1374 else
1375 value = apInt.getZExtValue();
1376 }
1377 result.push_back(std::move(value));
1378 }
1379
1380 componentState.setDataField(memoryOp.getName(), result);
1381 std::string numType =
1382 memtype.getElementType().isInteger() ? "bitnum" : "ieee754_float";
1383 componentState.setFormat(memoryOp.getName(), numType, isSigned,
1384 elmTyBitWidth);
1385
1386 return success();
1387}
1388
1389LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1390 memref::AllocOp allocOp) const {
1391 return buildAllocOp(getState<ComponentLoweringState>(), rewriter, allocOp);
1392}
1393
1394LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1395 memref::AllocaOp allocOp) const {
1396 return buildAllocOp(getState<ComponentLoweringState>(), rewriter, allocOp);
1397}
1398
1399LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1400 memref::GetGlobalOp getGlobalOp) const {
1401 return buildAllocOp(getState<ComponentLoweringState>(), rewriter,
1402 getGlobalOp);
1403}
1404
1405LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1406 scf::YieldOp yieldOp) const {
1407 if (yieldOp.getOperands().empty()) {
1408 if (auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp())) {
1409 ScfForOp forOpInterface(forOp);
1410
1411 // Get the ForLoop's Induction Register.
1412 auto inductionReg = getState<ComponentLoweringState>().getForLoopIterReg(
1413 forOpInterface, 0);
1414
1415 Type regWidth = inductionReg.getOut().getType();
1416 // Adder should have same width as the inductionReg.
1417 SmallVector<Type> types(3, regWidth);
1418 auto addOp = getState<ComponentLoweringState>()
1419 .getNewLibraryOpInstance<calyx::AddLibOp>(
1420 rewriter, forOp.getLoc(), types);
1421
1422 auto directions = addOp.portDirections();
1423 // For an add operation, we expect two input ports and one output port.
1424 SmallVector<Value, 2> opInputPorts;
1425 Value opOutputPort;
1426 for (auto dir : enumerate(directions)) {
1427 switch (dir.value()) {
1429 opInputPorts.push_back(addOp.getResult(dir.index()));
1430 break;
1431 }
1433 opOutputPort = addOp.getResult(dir.index());
1434 break;
1435 }
1436 }
1437 }
1438
1439 // "Latch Group" increments inductionReg by forLoop's step value.
1440 calyx::ComponentOp componentOp =
1441 getState<ComponentLoweringState>().getComponentOp();
1442 SmallVector<StringRef, 4> groupIdentifier = {
1443 "incr", getState<ComponentLoweringState>().getUniqueName(forOp),
1444 "induction", "var"};
1445 auto groupOp = calyx::createGroup<calyx::GroupOp>(
1446 rewriter, componentOp, forOp.getLoc(),
1447 llvm::join(groupIdentifier, "_"));
1448 rewriter.setInsertionPointToEnd(groupOp.getBodyBlock());
1449
1450 // Assign inductionReg.out to the left port of the adder.
1451 Value leftOp = opInputPorts.front();
1452 calyx::AssignOp::create(rewriter, forOp.getLoc(), leftOp,
1453 inductionReg.getOut());
1454 // Assign forOp.getConstantStep to the right port of the adder.
1455 Value rightOp = opInputPorts.back();
1456 calyx::AssignOp::create(
1457 rewriter, forOp.getLoc(), rightOp,
1458 createConstant(forOp->getLoc(), rewriter, componentOp,
1459 regWidth.getIntOrFloatBitWidth(),
1460 forOp.getConstantStep().value().getSExtValue()));
1461 // Assign adder's output port to inductionReg.
1462 buildAssignmentsForRegisterWrite(rewriter, groupOp, componentOp,
1463 inductionReg, opOutputPort);
1464 // Set group as For Loop's "latch" group.
1465 getState<ComponentLoweringState>().setForLoopLatchGroup(forOpInterface,
1466 groupOp);
1467 getState<ComponentLoweringState>().registerEvaluatingGroup(opOutputPort,
1468 groupOp);
1469 return success();
1470 }
1471 if (auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp()))
1472 // Empty yield inside ifOp, essentially a no-op.
1473 return success();
1474 if (auto executeRegionOp =
1475 dyn_cast<scf::ExecuteRegionOp>(yieldOp->getParentOp()))
1476 // Empty yield inside an `ExecuteRegionOp` acts as the terminator op.
1477 return success();
1478 return yieldOp.getOperation()->emitError()
1479 << "Unsupported empty yieldOp outside ForOp or IfOp.";
1480 }
1481 // If yieldOp for a for loop is not empty, then we do not transform for loop.
1482 if (dyn_cast<scf::ForOp>(yieldOp->getParentOp())) {
1483 return yieldOp.getOperation()->emitError()
1484 << "Currently do not support non-empty yield operations inside for "
1485 "loops. Run --scf-for-to-while before running --scf-to-calyx.";
1486 }
1487
1488 if (auto whileOp = dyn_cast<scf::WhileOp>(yieldOp->getParentOp())) {
1489 ScfWhileOp whileOpInterface(whileOp);
1490
1491 auto assignGroup =
1492 getState<ComponentLoweringState>().buildWhileLoopIterArgAssignments(
1493 rewriter, whileOpInterface,
1494 getState<ComponentLoweringState>().getComponentOp(),
1495 getState<ComponentLoweringState>().getUniqueName(whileOp) +
1496 "_latch",
1497 yieldOp->getOpOperands());
1498 getState<ComponentLoweringState>().setWhileLoopLatchGroup(whileOpInterface,
1499 assignGroup);
1500 return success();
1501 }
1502
1503 if (auto ifOp = dyn_cast<scf::IfOp>(yieldOp->getParentOp())) {
1504 auto resultRegs = getState<ComponentLoweringState>().getResultRegs(ifOp);
1505
1506 if (yieldOp->getParentRegion() == &ifOp.getThenRegion()) {
1507 auto thenGroup = getState<ComponentLoweringState>().getThenGroup(ifOp);
1508 for (auto op : enumerate(yieldOp.getOperands())) {
1509 auto resultReg =
1510 getState<ComponentLoweringState>().getResultRegs(ifOp, op.index());
1511 buildAssignmentsForRegisterWrite(
1512 rewriter, thenGroup,
1513 getState<ComponentLoweringState>().getComponentOp(), resultReg,
1514 op.value());
1515 getState<ComponentLoweringState>().registerEvaluatingGroup(
1516 ifOp.getResult(op.index()), thenGroup);
1517 }
1518 }
1519
1520 if (!ifOp.getElseRegion().empty() &&
1521 (yieldOp->getParentRegion() == &ifOp.getElseRegion())) {
1522 auto elseGroup = getState<ComponentLoweringState>().getElseGroup(ifOp);
1523 for (auto op : enumerate(yieldOp.getOperands())) {
1524 auto resultReg =
1525 getState<ComponentLoweringState>().getResultRegs(ifOp, op.index());
1526 buildAssignmentsForRegisterWrite(
1527 rewriter, elseGroup,
1528 getState<ComponentLoweringState>().getComponentOp(), resultReg,
1529 op.value());
1530 getState<ComponentLoweringState>().registerEvaluatingGroup(
1531 ifOp.getResult(op.index()), elseGroup);
1532 }
1533 }
1534 }
1535 return success();
1536}
1537
1538LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1539 BranchOpInterface brOp) const {
1540 /// Branch argument passing group creation
1541 /// Branch operands are passed through registers. In BuildBasicBlockRegs we
1542 /// created registers for all branch arguments of each block. We now
1543 /// create groups for assigning values to these registers.
1544 Block *srcBlock = brOp->getBlock();
1545 for (auto succBlock : enumerate(brOp->getSuccessors())) {
1546 auto succOperands = brOp.getSuccessorOperands(succBlock.index());
1547 if (succOperands.empty())
1548 continue;
1549 // Create operand passing group
1550 std::string groupName = loweringState().blockName(srcBlock) + "_to_" +
1551 loweringState().blockName(succBlock.value());
1552 auto groupOp = calyx::createGroup<calyx::GroupOp>(rewriter, getComponent(),
1553 brOp.getLoc(), groupName);
1554 // Fetch block argument registers associated with the basic block
1555 auto dstBlockArgRegs =
1556 getState<ComponentLoweringState>().getBlockArgRegs(succBlock.value());
1557 // Create register assignment for each block argument
1558 for (auto arg : enumerate(succOperands.getForwardedOperands())) {
1559 auto reg = dstBlockArgRegs[arg.index()];
1561 rewriter, groupOp,
1562 getState<ComponentLoweringState>().getComponentOp(), reg,
1563 arg.value());
1564 }
1565 /// Register the group as a block argument group, to be executed
1566 /// when entering the successor block from this block (srcBlock).
1567 getState<ComponentLoweringState>().addBlockArgGroup(
1568 srcBlock, succBlock.value(), groupOp);
1569 }
1570 return success();
1571}
1572
1573/// For each return statement, we create a new group for assigning to the
1574/// previously created return value registers.
1575LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1576 ReturnOp retOp) const {
1577 if (retOp.getNumOperands() == 0)
1578 return success();
1579
1580 std::string groupName =
1581 getState<ComponentLoweringState>().getUniqueName("ret_assign");
1582 auto groupOp = calyx::createGroup<calyx::GroupOp>(rewriter, getComponent(),
1583 retOp.getLoc(), groupName);
1584 for (auto op : enumerate(retOp.getOperands())) {
1585 auto reg = getState<ComponentLoweringState>().getReturnReg(op.index());
1587 rewriter, groupOp, getState<ComponentLoweringState>().getComponentOp(),
1588 reg, op.value());
1589 }
1590 /// Schedule group for execution for when executing the return op block.
1591 getState<ComponentLoweringState>().addBlockScheduleable(retOp->getBlock(),
1592 groupOp);
1593 return success();
1594}
1595
1596LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1597 arith::ConstantOp constOp) const {
1598 if (isa<IntegerType>(constOp.getType())) {
1599 /// Move constant operations to the compOp body as hw::ConstantOp's.
1600 APInt value;
1601 calyx::matchConstantOp(constOp, value);
1602 auto hwConstOp =
1603 rewriter.replaceOpWithNewOp<hw::ConstantOp>(constOp, value);
1604 hwConstOp->moveAfter(getComponent().getBodyBlock(),
1605 getComponent().getBodyBlock()->begin());
1606 } else {
1607 std::string name = getState<ComponentLoweringState>().getUniqueName("cst");
1608 auto floatAttr = cast<FloatAttr>(constOp.getValueAttr());
1609 auto intType =
1610 rewriter.getIntegerType(floatAttr.getType().getIntOrFloatBitWidth());
1611 auto calyxConstOp = calyx::ConstantOp::create(rewriter, constOp.getLoc(),
1612 name, floatAttr, intType);
1613 calyxConstOp->moveAfter(getComponent().getBodyBlock(),
1614 getComponent().getBodyBlock()->begin());
1615 rewriter.replaceAllUsesWith(constOp, calyxConstOp.getOut());
1616 }
1617
1618 return success();
1619}
1620
1621LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1622 AddIOp op) const {
1623 return buildLibraryOp<calyx::CombGroupOp, calyx::AddLibOp>(rewriter, op);
1624}
1625LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1626 SubIOp op) const {
1627 return buildLibraryOp<calyx::CombGroupOp, calyx::SubLibOp>(rewriter, op);
1628}
1629LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1630 ShRUIOp op) const {
1631 return buildLibraryOp<calyx::CombGroupOp, calyx::RshLibOp>(rewriter, op);
1632}
1633LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1634 ShRSIOp op) const {
1635 return buildLibraryOp<calyx::CombGroupOp, calyx::SrshLibOp>(rewriter, op);
1636}
1637LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1638 ShLIOp op) const {
1639 return buildLibraryOp<calyx::CombGroupOp, calyx::LshLibOp>(rewriter, op);
1640}
1641LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1642 AndIOp op) const {
1643 return buildLibraryOp<calyx::CombGroupOp, calyx::AndLibOp>(rewriter, op);
1644}
1645LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1646 OrIOp op) const {
1647 return buildLibraryOp<calyx::CombGroupOp, calyx::OrLibOp>(rewriter, op);
1648}
1649LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1650 XOrIOp op) const {
1651 return buildLibraryOp<calyx::CombGroupOp, calyx::XorLibOp>(rewriter, op);
1652}
1653LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1654 SelectOp op) const {
1655 return buildLibraryOp<calyx::CombGroupOp, calyx::MuxLibOp>(rewriter, op);
1656}
1657
1658LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1659 CmpIOp op) const {
1660 switch (op.getPredicate()) {
1661 case CmpIPredicate::eq:
1662 return buildCmpIOpHelper<calyx::EqLibOp>(rewriter, op);
1663 case CmpIPredicate::ne:
1664 return buildCmpIOpHelper<calyx::NeqLibOp>(rewriter, op);
1665 case CmpIPredicate::uge:
1666 return buildCmpIOpHelper<calyx::GeLibOp>(rewriter, op);
1667 case CmpIPredicate::ult:
1668 return buildCmpIOpHelper<calyx::LtLibOp>(rewriter, op);
1669 case CmpIPredicate::ugt:
1670 return buildCmpIOpHelper<calyx::GtLibOp>(rewriter, op);
1671 case CmpIPredicate::ule:
1672 return buildCmpIOpHelper<calyx::LeLibOp>(rewriter, op);
1673 case CmpIPredicate::sge:
1674 return buildCmpIOpHelper<calyx::SgeLibOp>(rewriter, op);
1675 case CmpIPredicate::slt:
1676 return buildCmpIOpHelper<calyx::SltLibOp>(rewriter, op);
1677 case CmpIPredicate::sgt:
1678 return buildCmpIOpHelper<calyx::SgtLibOp>(rewriter, op);
1679 case CmpIPredicate::sle:
1680 return buildCmpIOpHelper<calyx::SleLibOp>(rewriter, op);
1681 }
1682 llvm_unreachable("unsupported comparison predicate");
1683}
1684
1685LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1686 TruncIOp op) const {
1687 return buildLibraryOp<calyx::CombGroupOp, calyx::SliceLibOp>(
1688 rewriter, op, {op.getOperand().getType()}, {op.getType()});
1689}
1690LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1691 ExtUIOp op) const {
1692 return buildLibraryOp<calyx::CombGroupOp, calyx::PadLibOp>(
1693 rewriter, op, {op.getOperand().getType()}, {op.getType()});
1694}
1695
1696LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1697 ExtSIOp op) const {
1698 return buildLibraryOp<calyx::CombGroupOp, calyx::ExtSILibOp>(
1699 rewriter, op, {op.getOperand().getType()}, {op.getType()});
1700}
1701
1702LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1703 IndexCastOp op) const {
1704 Type sourceType = calyx::normalizeType(rewriter, op.getOperand().getType());
1705 Type targetType = calyx::normalizeType(rewriter, op.getResult().getType());
1706 unsigned targetBits = targetType.getIntOrFloatBitWidth();
1707 unsigned sourceBits = sourceType.getIntOrFloatBitWidth();
1708 LogicalResult res = success();
1709
1710 if (targetBits == sourceBits) {
1711 /// Drop the index cast and replace uses of the target value with the source
1712 /// value.
1713 op.getResult().replaceAllUsesWith(op.getOperand());
1714 } else {
1715 /// pad/slice the source operand.
1716 if (sourceBits > targetBits)
1717 res = buildLibraryOp<calyx::CombGroupOp, calyx::SliceLibOp>(
1718 rewriter, op, {sourceType}, {targetType});
1719 else
1720 res = buildLibraryOp<calyx::CombGroupOp, calyx::PadLibOp>(
1721 rewriter, op, {sourceType}, {targetType});
1722 }
1723 rewriter.eraseOp(op);
1724 return res;
1725}
1726
1727// The Calyx language treats values as bit vectors, i.e., there is no type
1728// system, so this is essentially a no-op.
1729LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1730 BitcastOp op) const {
1731 rewriter.replaceAllUsesWith(op.getOut(), op.getIn());
1732 return success();
1733}
1734
1735LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1736 scf::WhileOp whileOp) const {
1737 // Only need to add the whileOp to the BlockSchedulables scheduler interface.
1738 // Everything else was handled in the `BuildWhileGroups` pattern.
1739 ScfWhileOp scfWhileOp(whileOp);
1740 getState<ComponentLoweringState>().addBlockScheduleable(
1741 whileOp.getOperation()->getBlock(), WhileScheduleable{scfWhileOp});
1742 return success();
1743}
1744
1745LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1746 scf::ForOp forOp) const {
1747 // Only need to add the forOp to the BlockSchedulables scheduler interface.
1748 // Everything else was handled in the `BuildForGroups` pattern.
1749 ScfForOp scfForOp(forOp);
1750 // If we cannot compute the trip count of the for loop, then we should
1751 // emit an error saying to use --scf-for-to-while
1752 std::optional<uint64_t> bound = scfForOp.getBound();
1753 if (!bound.has_value()) {
1754 return scfForOp.getOperation()->emitError()
1755 << "Loop bound not statically known. Should "
1756 "transform into while loop using `--scf-for-to-while` before "
1757 "running --lower-scf-to-calyx.";
1758 }
1759 getState<ComponentLoweringState>().addBlockScheduleable(
1760 forOp.getOperation()->getBlock(), ForScheduleable{
1761 scfForOp,
1762 bound.value(),
1763 });
1764 return success();
1765}
1766
1767LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1768 scf::IfOp ifOp) const {
1769 getState<ComponentLoweringState>().addBlockScheduleable(
1770 ifOp.getOperation()->getBlock(), IfScheduleable{ifOp});
1771 return success();
1772}
1773
1774LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1775 scf::ReduceOp reduceOp) const {
1776 // we don't handle reduce operation and simply return success for now since
1777 // BuildParGroups would have already emitted an error and exited early
1778 // if a reduce operation was encountered.
1779 return success();
1780}
1781
1782LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1783 scf::ParallelOp parOp) const {
1784 if (!parOp->hasAttr(unrolledParallelAttr)) {
1785 parOp.emitError(
1786 "AffineParallelUnroll must be run in order to lower scf.parallel");
1787 return failure();
1788 }
1789 getState<ComponentLoweringState>().addBlockScheduleable(
1790 parOp.getOperation()->getBlock(), ParScheduleable{parOp});
1791 return success();
1792}
1793
1794LogicalResult
1795BuildOpGroups::buildOp(PatternRewriter &rewriter,
1796 scf::ExecuteRegionOp executeRegionOp) const {
1797 // Simply return success because the only remaining `scf.execute_region` op
1798 // are generated by the `BuildParGroups` pass - the rest of them are inlined
1799 // by the `InlineExecuteRegionOpPattern`.
1800 return success();
1801}
1802
1803LogicalResult BuildOpGroups::buildOp(PatternRewriter &rewriter,
1804 CallOp callOp) const {
1805 std::string instanceName = calyx::getInstanceName(callOp);
1806 calyx::InstanceOp instanceOp =
1807 getState<ComponentLoweringState>().getInstance(instanceName);
1808 SmallVector<Value, 4> outputPorts;
1809 auto portInfos = instanceOp.getReferencedComponent().getPortInfo();
1810 for (auto [idx, portInfo] : enumerate(portInfos)) {
1811 if (portInfo.direction == calyx::Direction::Output)
1812 outputPorts.push_back(instanceOp.getResult(idx));
1813 }
1814
1815 // Replacing a CallOp results in the out port of the instance.
1816 for (auto [idx, result] : llvm::enumerate(callOp.getResults()))
1817 rewriter.replaceAllUsesWith(result, outputPorts[idx]);
1818
1819 // CallScheduleanle requires an instance, while CallOp can be used to get the
1820 // input ports.
1821 getState<ComponentLoweringState>().addBlockScheduleable(
1822 callOp.getOperation()->getBlock(), CallScheduleable{instanceOp, callOp});
1823 return success();
1824}
1825
1826/// Inlines Calyx ExecuteRegionOp operations within their parent blocks.
1827/// An execution region op (ERO) is inlined by:
1828/// i : add a sink basic block for all yield operations inside the
1829/// ERO to jump to
1830/// ii : Rewrite scf.yield calls inside the ERO to branch to the sink block
1831/// iii: inline the ERO region
1832/// TODO(#1850) evaluate the usefulness of this lowering pattern.
1834 : public OpRewritePattern<scf::ExecuteRegionOp> {
1835 using OpRewritePattern::OpRewritePattern;
1836
1837 LogicalResult matchAndRewrite(scf::ExecuteRegionOp execOp,
1838 PatternRewriter &rewriter) const override {
1839 if (auto parOp = dyn_cast_or_null<scf::ParallelOp>(execOp->getParentOp())) {
1840 if (auto boolAttr = dyn_cast_or_null<mlir::BoolAttr>(
1841 parOp->getAttr(unrolledParallelAttr)))
1842 // If the `ExecuteRegionOp` was inserted when running the
1843 // `AffineParallelUnrollPass` (indicated by having `calyx.unroll`
1844 // attribute), we should skip inline.
1845 return success();
1846 }
1847 /// Determine type of "yield" operations inside the ERO.
1848 TypeRange yieldTypes = execOp.getResultTypes();
1849
1850 /// Create sink basic block and rewrite uses of yield results to sink block
1851 /// arguments.
1852 rewriter.setInsertionPointAfter(execOp);
1853 auto *sinkBlock = rewriter.splitBlock(
1854 execOp->getBlock(),
1855 execOp.getOperation()->getIterator()->getNextNode()->getIterator());
1856 sinkBlock->addArguments(
1857 yieldTypes,
1858 SmallVector<Location, 4>(yieldTypes.size(), rewriter.getUnknownLoc()));
1859 for (auto res : enumerate(execOp.getResults()))
1860 res.value().replaceAllUsesWith(sinkBlock->getArgument(res.index()));
1861
1862 /// Rewrite yield calls as branches.
1863 for (auto yieldOp :
1864 make_early_inc_range(execOp.getRegion().getOps<scf::YieldOp>())) {
1865 rewriter.setInsertionPointAfter(yieldOp);
1866 rewriter.replaceOpWithNewOp<BranchOp>(yieldOp, sinkBlock,
1867 yieldOp.getOperands());
1868 }
1869
1870 /// Inline the regionOp.
1871 auto *preBlock = execOp->getBlock();
1872 auto *execOpEntryBlock = &execOp.getRegion().front();
1873 auto *postBlock = execOp->getBlock()->splitBlock(execOp);
1874 rewriter.inlineRegionBefore(execOp.getRegion(), postBlock);
1875 rewriter.mergeBlocks(postBlock, preBlock);
1876 rewriter.eraseOp(execOp);
1877
1878 /// Finally, erase the unused entry block of the execOp region.
1879 rewriter.mergeBlocks(execOpEntryBlock, preBlock);
1880
1881 return success();
1882 }
1883};
1884
1885/// Creates a new Calyx component for each FuncOp in the program.
1887 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
1888
1889 LogicalResult
1891 PatternRewriter &rewriter) const override {
1892 /// Maintain a mapping between funcOp input arguments and the port index
1893 /// which the argument will eventually map to.
1894 DenseMap<Value, unsigned> funcOpArgRewrites;
1895
1896 /// Maintain a mapping between funcOp output indexes and the component
1897 /// output port index which the return value will eventually map to.
1898 DenseMap<unsigned, unsigned> funcOpResultMapping;
1899
1900 /// Maintain a mapping between an external memory argument (identified by a
1901 /// memref) and eventual component input- and output port indices that will
1902 /// map to the memory ports. The pair denotes the start index of the memory
1903 /// ports in the in- and output ports of the component. Ports are expected
1904 /// to be ordered in the same manner as they are added by
1905 /// calyx::appendPortsForExternalMemref.
1906 DenseMap<Value, std::pair<unsigned, unsigned>> extMemoryCompPortIndices;
1907
1908 /// Create I/O ports. Maintain separate in/out port vectors to determine
1909 /// which port index each function argument will eventually map to.
1910 SmallVector<calyx::PortInfo> inPorts, outPorts;
1911 FunctionType funcType = funcOp.getFunctionType();
1912 for (auto arg : enumerate(funcOp.getArguments())) {
1913 if (!isa<MemRefType>(arg.value().getType())) {
1914 /// Single-port arguments
1915 std::string inName;
1916 if (auto portNameAttr = funcOp.getArgAttrOfType<StringAttr>(
1917 arg.index(), scfToCalyx::sPortNameAttr))
1918 inName = portNameAttr.str();
1919 else
1920 inName = "in" + std::to_string(arg.index());
1921 funcOpArgRewrites[arg.value()] = inPorts.size();
1922 inPorts.push_back(calyx::PortInfo{
1923 rewriter.getStringAttr(inName),
1924 calyx::normalizeType(rewriter, arg.value().getType()),
1926 DictionaryAttr::get(rewriter.getContext(), {})});
1927 }
1928 }
1929 for (auto res : enumerate(funcType.getResults())) {
1930 std::string resName;
1931 if (auto portNameAttr = funcOp.getResultAttrOfType<StringAttr>(
1932 res.index(), scfToCalyx::sPortNameAttr))
1933 resName = portNameAttr.str();
1934 else
1935 resName = "out" + std::to_string(res.index());
1936 funcOpResultMapping[res.index()] = outPorts.size();
1937
1938 outPorts.push_back(calyx::PortInfo{
1939 rewriter.getStringAttr(resName),
1940 calyx::normalizeType(rewriter, res.value()), calyx::Direction::Output,
1941 DictionaryAttr::get(rewriter.getContext(), {})});
1942 }
1943
1944 /// We've now recorded all necessary indices. Merge in- and output ports
1945 /// and add the required mandatory component ports.
1946 auto ports = inPorts;
1947 llvm::append_range(ports, outPorts);
1948 calyx::addMandatoryComponentPorts(rewriter, ports);
1949
1950 /// Create a calyx::ComponentOp corresponding to the to-be-lowered function.
1951 auto compOp = calyx::ComponentOp::create(
1952 rewriter, funcOp.getLoc(), rewriter.getStringAttr(funcOp.getSymName()),
1953 ports);
1954
1955 std::string funcName = "func_" + funcOp.getSymName().str();
1956 rewriter.modifyOpInPlace(funcOp, [&]() { funcOp.setSymName(funcName); });
1957
1958 /// Mark this component as the toplevel if it's the top-level function of
1959 /// the module.
1960 if (compOp.getName() == loweringState().getTopLevelFunction())
1961 compOp->setAttr("toplevel", rewriter.getUnitAttr());
1962
1963 /// Store the function-to-component mapping.
1964 functionMapping[funcOp] = compOp;
1965 auto *compState = loweringState().getState<ComponentLoweringState>(compOp);
1966 compState->setFuncOpResultMapping(funcOpResultMapping);
1967
1968 unsigned extMemCounter = 0;
1969 for (auto arg : enumerate(funcOp.getArguments())) {
1970 if (isa<MemRefType>(arg.value().getType())) {
1971 std::string memName =
1972 llvm::join_items("_", "arg_mem", std::to_string(extMemCounter++));
1973
1974 rewriter.setInsertionPointToStart(compOp.getBodyBlock());
1975 MemRefType memtype = cast<MemRefType>(arg.value().getType());
1976 SmallVector<int64_t> addrSizes;
1977 SmallVector<int64_t> sizes;
1978 for (int64_t dim : memtype.getShape()) {
1979 sizes.push_back(dim);
1980 addrSizes.push_back(calyx::handleZeroWidth(dim));
1981 }
1982 if (sizes.empty() && addrSizes.empty()) {
1983 sizes.push_back(1);
1984 addrSizes.push_back(1);
1985 }
1986 auto memOp = calyx::SeqMemoryOp::create(
1987 rewriter, funcOp.getLoc(), memName,
1988 memtype.getElementType().getIntOrFloatBitWidth(), sizes, addrSizes);
1989 // we don't set the memory to "external", which implies it's a reference
1990
1991 compState->registerMemoryInterface(arg.value(),
1992 calyx::MemoryInterface(memOp));
1993 }
1994 }
1995
1996 /// Rewrite funcOp SSA argument values to the CompOp arguments.
1997 for (auto &mapping : funcOpArgRewrites)
1998 mapping.getFirst().replaceAllUsesWith(
1999 compOp.getArgument(mapping.getSecond()));
2000
2001 return success();
2002 }
2003};
2004
2005/// In BuildWhileGroups, a register is created for each iteration argumenet of
2006/// the while op. These registers are then written to on the while op
2007/// terminating yield operation alongside before executing the whileOp in the
2008/// schedule, to set the initial values of the argument registers.
2010 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2011
2012 LogicalResult
2014 PatternRewriter &rewriter) const override {
2015 LogicalResult res = success();
2016 funcOp.walk([&](Operation *op) {
2017 // Only work on ops that support the ScfWhileOp.
2018 if (!isa<scf::WhileOp>(op))
2019 return WalkResult::advance();
2020
2021 auto scfWhileOp = cast<scf::WhileOp>(op);
2022 ScfWhileOp whileOp(scfWhileOp);
2023
2024 getState<ComponentLoweringState>().setUniqueName(whileOp.getOperation(),
2025 "while");
2026
2027 /// Check for do-while loops.
2028 /// TODO(mortbopet) can we support these? for now, do not support loops
2029 /// where iterargs are changed in the 'before' region. scf.WhileOp also
2030 /// has support for different types of iter_args and return args which we
2031 /// also do not support; iter_args and while return values are placed in
2032 /// the same registers.
2033 for (auto barg :
2034 enumerate(scfWhileOp.getBefore().front().getArguments())) {
2035 auto condOp = scfWhileOp.getConditionOp().getArgs()[barg.index()];
2036 if (barg.value() != condOp) {
2037 res = whileOp.getOperation()->emitError()
2038 << loweringState().irName(barg.value())
2039 << " != " << loweringState().irName(condOp)
2040 << "do-while loops not supported; expected iter-args to "
2041 "remain untransformed in the 'before' region of the "
2042 "scf.while op.";
2043 return WalkResult::interrupt();
2044 }
2045 }
2046
2047 /// Create iteration argument registers.
2048 /// The iteration argument registers will be referenced:
2049 /// - In the "before" part of the while loop, calculating the conditional,
2050 /// - In the "after" part of the while loop,
2051 /// - Outside the while loop, rewriting the while loop return values.
2052 for (auto arg : enumerate(whileOp.getBodyArgs())) {
2053 std::string name = getState<ComponentLoweringState>()
2054 .getUniqueName(whileOp.getOperation())
2055 .str() +
2056 "_arg" + std::to_string(arg.index());
2057 auto reg =
2058 createRegister(arg.value().getLoc(), rewriter, getComponent(),
2059 arg.value().getType().getIntOrFloatBitWidth(), name);
2060 getState<ComponentLoweringState>().addWhileLoopIterReg(whileOp, reg,
2061 arg.index());
2062 arg.value().replaceAllUsesWith(reg.getOut());
2063
2064 /// Also replace uses in the "before" region of the while loop
2065 whileOp.getConditionBlock()
2066 ->getArgument(arg.index())
2067 .replaceAllUsesWith(reg.getOut());
2068 }
2069
2070 /// Create iter args initial value assignment group(s), one per register.
2071 SmallVector<calyx::GroupOp> initGroups;
2072 auto numOperands = whileOp.getOperation()->getNumOperands();
2073 for (size_t i = 0; i < numOperands; ++i) {
2074 auto initGroupOp =
2075 getState<ComponentLoweringState>().buildWhileLoopIterArgAssignments(
2076 rewriter, whileOp,
2077 getState<ComponentLoweringState>().getComponentOp(),
2078 getState<ComponentLoweringState>().getUniqueName(
2079 whileOp.getOperation()) +
2080 "_init_" + std::to_string(i),
2081 whileOp.getOperation()->getOpOperand(i));
2082 initGroups.push_back(initGroupOp);
2083 }
2084
2085 getState<ComponentLoweringState>().setWhileLoopInitGroups(whileOp,
2086 initGroups);
2087
2088 return WalkResult::advance();
2089 });
2090 return res;
2091 }
2092};
2093
2094/// In BuildForGroups, a register is created for the iteration argument of
2095/// the for op. This register is then initialized to the lowerBound of the for
2096/// loop in a group that executes the for loop.
2098 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2099
2100 LogicalResult
2102 PatternRewriter &rewriter) const override {
2103 LogicalResult res = success();
2104 funcOp.walk([&](Operation *op) {
2105 // Only work on ops that support the ScfForOp.
2106 if (!isa<scf::ForOp>(op))
2107 return WalkResult::advance();
2108
2109 auto scfForOp = cast<scf::ForOp>(op);
2110 ScfForOp forOp(scfForOp);
2111
2112 getState<ComponentLoweringState>().setUniqueName(forOp.getOperation(),
2113 "for");
2114
2115 // Create a register for the InductionVar, and set that Register as the
2116 // only IterReg for the For Loop
2117 auto inductionVar = forOp.getOperation().getInductionVar();
2118 SmallVector<std::string, 3> inductionVarIdentifiers = {
2119 getState<ComponentLoweringState>()
2120 .getUniqueName(forOp.getOperation())
2121 .str(),
2122 "induction", "var"};
2123 std::string name = llvm::join(inductionVarIdentifiers, "_");
2124 auto reg =
2125 createRegister(inductionVar.getLoc(), rewriter, getComponent(),
2126 inductionVar.getType().getIntOrFloatBitWidth(), name);
2127 getState<ComponentLoweringState>().addForLoopIterReg(forOp, reg, 0);
2128 inductionVar.replaceAllUsesWith(reg.getOut());
2129
2130 // Create InitGroup that sets the InductionVar to LowerBound
2131 calyx::ComponentOp componentOp =
2132 getState<ComponentLoweringState>().getComponentOp();
2133 SmallVector<calyx::GroupOp> initGroups;
2134 SmallVector<std::string, 4> groupIdentifiers = {
2135 "init",
2136 getState<ComponentLoweringState>()
2137 .getUniqueName(forOp.getOperation())
2138 .str(),
2139 "induction", "var"};
2140 std::string groupName = llvm::join(groupIdentifiers, "_");
2141 auto groupOp = calyx::createGroup<calyx::GroupOp>(
2142 rewriter, componentOp, forOp.getLoc(), groupName);
2143 buildAssignmentsForRegisterWrite(rewriter, groupOp, componentOp, reg,
2144 forOp.getOperation().getLowerBound());
2145 initGroups.push_back(groupOp);
2146 getState<ComponentLoweringState>().setForLoopInitGroups(forOp,
2147 initGroups);
2148
2149 return WalkResult::advance();
2150 });
2151 return res;
2152 }
2153};
2154
2156 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2157
2158 LogicalResult
2160 PatternRewriter &rewriter) const override {
2161 LogicalResult res = success();
2162 funcOp.walk([&](Operation *op) {
2163 if (!isa<scf::IfOp>(op))
2164 return WalkResult::advance();
2165
2166 auto scfIfOp = cast<scf::IfOp>(op);
2167
2168 // There is no need to build `thenGroup` and `elseGroup` if `scfIfOp`
2169 // doesn't yield any result since these groups are created for managing
2170 // the result values.
2171 if (scfIfOp.getResults().empty())
2172 return WalkResult::advance();
2173
2174 calyx::ComponentOp componentOp =
2175 getState<ComponentLoweringState>().getComponentOp();
2176
2177 std::string thenGroupName =
2178 getState<ComponentLoweringState>().getUniqueName("then_br");
2179 auto thenGroupOp = calyx::createGroup<calyx::GroupOp>(
2180 rewriter, componentOp, scfIfOp.getLoc(), thenGroupName);
2181 getState<ComponentLoweringState>().setThenGroup(scfIfOp, thenGroupOp);
2182
2183 if (!scfIfOp.getElseRegion().empty()) {
2184 std::string elseGroupName =
2185 getState<ComponentLoweringState>().getUniqueName("else_br");
2186 auto elseGroupOp = calyx::createGroup<calyx::GroupOp>(
2187 rewriter, componentOp, scfIfOp.getLoc(), elseGroupName);
2188 getState<ComponentLoweringState>().setElseGroup(scfIfOp, elseGroupOp);
2189 }
2190
2191 for (auto ifOpRes : scfIfOp.getResults()) {
2192 auto reg = createRegister(
2193 scfIfOp.getLoc(), rewriter, getComponent(),
2194 ifOpRes.getType().getIntOrFloatBitWidth(),
2195 getState<ComponentLoweringState>().getUniqueName("if_res"));
2196 getState<ComponentLoweringState>().setResultRegs(
2197 scfIfOp, reg, ifOpRes.getResultNumber());
2198 }
2199
2200 return WalkResult::advance();
2201 });
2202 return res;
2203 }
2204};
2205
2206/// Builds a control schedule by traversing the CFG of the function and
2207/// associating this with the previously created groups.
2208/// For simplicity, the generated control flow is expanded for all possible
2209/// paths in the input DAG. This elaborated control flow is later reduced in
2210/// the runControlFlowSimplification passes.
2212 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2213
2214 LogicalResult
2216 PatternRewriter &rewriter) const override {
2217 auto *entryBlock = &funcOp.getBlocks().front();
2218 rewriter.setInsertionPointToStart(
2219 getComponent().getControlOp().getBodyBlock());
2220 auto topLevelSeqOp = calyx::SeqOp::create(rewriter, funcOp.getLoc());
2221 DenseSet<Block *> path;
2222 return buildCFGControl(path, rewriter, topLevelSeqOp.getBodyBlock(),
2223 nullptr, entryBlock);
2224 }
2225
2226private:
2227 /// Sequentially schedules the groups that registered themselves with
2228 /// 'block'.
2229 LogicalResult scheduleBasicBlock(PatternRewriter &rewriter,
2230 const DenseSet<Block *> &path,
2231 mlir::Block *parentCtrlBlock,
2232 mlir::Block *block) const {
2233 auto compBlockScheduleables =
2234 getState<ComponentLoweringState>().getBlockScheduleables(block);
2235 auto loc = block->front().getLoc();
2236
2237 if (compBlockScheduleables.size() > 1 &&
2238 !isa<scf::ParallelOp>(block->getParentOp())) {
2239 auto seqOp = calyx::SeqOp::create(rewriter, loc);
2240 parentCtrlBlock = seqOp.getBodyBlock();
2241 }
2242
2243 for (auto &group : compBlockScheduleables) {
2244 rewriter.setInsertionPointToEnd(parentCtrlBlock);
2245 if (auto groupPtr = std::get_if<calyx::GroupOp>(&group); groupPtr) {
2246 calyx::EnableOp::create(rewriter, groupPtr->getLoc(),
2247 groupPtr->getSymName());
2248 } else if (auto whileSchedPtr = std::get_if<WhileScheduleable>(&group);
2249 whileSchedPtr) {
2250 auto &whileOp = whileSchedPtr->whileOp;
2251
2252 auto whileCtrlOp = buildWhileCtrlOp(
2253 whileOp,
2254 getState<ComponentLoweringState>().getWhileLoopInitGroups(whileOp),
2255 rewriter);
2256 rewriter.setInsertionPointToEnd(whileCtrlOp.getBodyBlock());
2257 auto whileBodyOp =
2258 calyx::SeqOp::create(rewriter, whileOp.getOperation()->getLoc());
2259 auto *whileBodyOpBlock = whileBodyOp.getBodyBlock();
2260
2261 /// Only schedule the 'after' block. The 'before' block is
2262 /// implicitly scheduled when evaluating the while condition.
2263 if (LogicalResult result =
2264 buildCFGControl(path, rewriter, whileBodyOpBlock, block,
2265 whileOp.getBodyBlock());
2266 result.failed())
2267 return result;
2268
2269 // Insert loop-latch at the end of the while group
2270 rewriter.setInsertionPointToEnd(whileBodyOpBlock);
2271 calyx::GroupOp whileLatchGroup =
2272 getState<ComponentLoweringState>().getWhileLoopLatchGroup(whileOp);
2273 calyx::EnableOp::create(rewriter, whileLatchGroup.getLoc(),
2274 whileLatchGroup.getName());
2275 } else if (auto *parSchedPtr = std::get_if<ParScheduleable>(&group)) {
2276 auto parOp = parSchedPtr->parOp;
2277 auto calyxParOp = calyx::ParOp::create(rewriter, parOp.getLoc());
2278
2279 WalkResult walkResult =
2280 parOp.walk([&](scf::ExecuteRegionOp execRegion) {
2281 rewriter.setInsertionPointToEnd(calyxParOp.getBodyBlock());
2282 auto seqOp = calyx::SeqOp::create(rewriter, execRegion.getLoc());
2283 rewriter.setInsertionPointToEnd(seqOp.getBodyBlock());
2284
2285 for (auto &execBlock : execRegion.getRegion().getBlocks()) {
2286 if (LogicalResult res = scheduleBasicBlock(
2287 rewriter, path, seqOp.getBodyBlock(), &execBlock);
2288 res.failed()) {
2289 return WalkResult::interrupt();
2290 }
2291 }
2292 return WalkResult::advance();
2293 });
2294
2295 if (walkResult.wasInterrupted())
2296 return failure();
2297 } else if (auto *forSchedPtr = std::get_if<ForScheduleable>(&group);
2298 forSchedPtr) {
2299 auto forOp = forSchedPtr->forOp;
2300
2301 auto forCtrlOp = buildForCtrlOp(
2302 forOp,
2303 getState<ComponentLoweringState>().getForLoopInitGroups(forOp),
2304 forSchedPtr->bound, rewriter);
2305 rewriter.setInsertionPointToEnd(forCtrlOp.getBodyBlock());
2306 auto forBodyOp =
2307 calyx::SeqOp::create(rewriter, forOp.getOperation()->getLoc());
2308 auto *forBodyOpBlock = forBodyOp.getBodyBlock();
2309
2310 // Schedule the body of the for loop.
2311 if (LogicalResult res = buildCFGControl(path, rewriter, forBodyOpBlock,
2312 block, forOp.getBodyBlock());
2313 res.failed())
2314 return res;
2315
2316 // Insert loop-latch at the end of the while group.
2317 rewriter.setInsertionPointToEnd(forBodyOpBlock);
2318 calyx::GroupOp forLatchGroup =
2319 getState<ComponentLoweringState>().getForLoopLatchGroup(forOp);
2320 calyx::EnableOp::create(rewriter, forLatchGroup.getLoc(),
2321 forLatchGroup.getName());
2322 } else if (auto *ifSchedPtr = std::get_if<IfScheduleable>(&group);
2323 ifSchedPtr) {
2324 auto ifOp = ifSchedPtr->ifOp;
2325
2326 Location loc = ifOp->getLoc();
2327
2328 auto cond = ifOp.getCondition();
2329
2330 FlatSymbolRefAttr symbolAttr = nullptr;
2331 auto condReg = getState<ComponentLoweringState>().getCondReg(ifOp);
2332 if (!condReg) {
2333 auto condGroup = getState<ComponentLoweringState>()
2334 .getEvaluatingGroup<calyx::CombGroupOp>(cond);
2335
2336 symbolAttr = FlatSymbolRefAttr::get(
2337 StringAttr::get(getContext(), condGroup.getSymName()));
2338 }
2339
2340 bool initElse = !ifOp.getElseRegion().empty();
2341 auto ifCtrlOp = calyx::IfOp::create(rewriter, loc, cond, symbolAttr,
2342 /*initializeElseBody=*/initElse);
2343
2344 rewriter.setInsertionPointToEnd(ifCtrlOp.getBodyBlock());
2345
2346 auto thenSeqOp =
2347 calyx::SeqOp::create(rewriter, ifOp.getThenRegion().getLoc());
2348 auto *thenSeqOpBlock = thenSeqOp.getBodyBlock();
2349
2350 auto *thenBlock = &ifOp.getThenRegion().front();
2351 LogicalResult res = buildCFGControl(path, rewriter, thenSeqOpBlock,
2352 /*preBlock=*/block, thenBlock);
2353 if (res.failed())
2354 return res;
2355
2356 // `thenGroup`s won't be created in the first place if there's no
2357 // yielded results for this `ifOp`.
2358 if (!ifOp.getResults().empty()) {
2359 rewriter.setInsertionPointToEnd(thenSeqOpBlock);
2360 calyx::GroupOp thenGroup =
2361 getState<ComponentLoweringState>().getThenGroup(ifOp);
2362 calyx::EnableOp::create(rewriter, thenGroup.getLoc(),
2363 thenGroup.getName());
2364 }
2365
2366 if (!ifOp.getElseRegion().empty()) {
2367 rewriter.setInsertionPointToEnd(ifCtrlOp.getElseBody());
2368
2369 auto elseSeqOp =
2370 calyx::SeqOp::create(rewriter, ifOp.getElseRegion().getLoc());
2371 auto *elseSeqOpBlock = elseSeqOp.getBodyBlock();
2372
2373 auto *elseBlock = &ifOp.getElseRegion().front();
2374 res = buildCFGControl(path, rewriter, elseSeqOpBlock,
2375 /*preBlock=*/block, elseBlock);
2376 if (res.failed())
2377 return res;
2378
2379 if (!ifOp.getResults().empty()) {
2380 rewriter.setInsertionPointToEnd(elseSeqOpBlock);
2381 calyx::GroupOp elseGroup =
2382 getState<ComponentLoweringState>().getElseGroup(ifOp);
2383 calyx::EnableOp::create(rewriter, elseGroup.getLoc(),
2384 elseGroup.getName());
2385 }
2386 }
2387 } else if (auto *callSchedPtr = std::get_if<CallScheduleable>(&group)) {
2388 auto instanceOp = callSchedPtr->instanceOp;
2389 OpBuilder::InsertionGuard g(rewriter);
2390 auto callBody = calyx::SeqOp::create(rewriter, instanceOp.getLoc());
2391 rewriter.setInsertionPointToStart(callBody.getBodyBlock());
2392
2393 auto callee = callSchedPtr->callOp.getCallee();
2394 auto *calleeOp = SymbolTable::lookupNearestSymbolFrom(
2395 callSchedPtr->callOp.getOperation()->getParentOp(),
2396 StringAttr::get(rewriter.getContext(), "func_" + callee.str()));
2397 FuncOp calleeFunc = dyn_cast_or_null<FuncOp>(calleeOp);
2398
2399 auto instanceOpComp =
2400 llvm::cast<calyx::ComponentOp>(instanceOp.getReferencedComponent());
2401 auto *instanceOpLoweringState =
2402 loweringState().getState(instanceOpComp);
2403
2404 SmallVector<Value, 4> instancePorts;
2405 SmallVector<Value, 4> inputPorts;
2406 SmallVector<Attribute, 4> refCells;
2407 for (auto operandEnum : enumerate(callSchedPtr->callOp.getOperands())) {
2408 auto operand = operandEnum.value();
2409 auto index = operandEnum.index();
2410 if (!isa<MemRefType>(operand.getType())) {
2411 inputPorts.push_back(operand);
2412 continue;
2413 }
2414
2415 auto memOpName = getState<ComponentLoweringState>()
2416 .getMemoryInterface(operand)
2417 .memName();
2418 auto memOpNameAttr =
2419 SymbolRefAttr::get(rewriter.getContext(), memOpName);
2420 Value argI = calleeFunc.getArgument(index);
2421 if (isa<MemRefType>(argI.getType())) {
2422 NamedAttrList namedAttrList;
2423 namedAttrList.append(
2424 rewriter.getStringAttr(
2425 instanceOpLoweringState->getMemoryInterface(argI)
2426 .memName()),
2427 memOpNameAttr);
2428 refCells.push_back(
2429 DictionaryAttr::get(rewriter.getContext(), namedAttrList));
2430 }
2431 }
2432 llvm::copy(instanceOp.getResults().take_front(inputPorts.size()),
2433 std::back_inserter(instancePorts));
2434
2435 ArrayAttr refCellsAttr =
2436 ArrayAttr::get(rewriter.getContext(), refCells);
2437
2438 calyx::InvokeOp::create(rewriter, instanceOp.getLoc(),
2439 instanceOp.getSymName(), instancePorts,
2440 inputPorts, refCellsAttr,
2441 ArrayAttr::get(rewriter.getContext(), {}),
2442 ArrayAttr::get(rewriter.getContext(), {}));
2443 } else
2444 llvm_unreachable("Unknown scheduleable");
2445 }
2446 return success();
2447 }
2448
2449 /// Schedules a block by inserting a branch argument assignment block (if any)
2450 /// before recursing into the scheduling of the block innards.
2451 /// Blocks 'from' and 'to' refer to blocks in the source program.
2452 /// parentCtrlBlock refers to the control block wherein control operations are
2453 /// to be inserted.
2454 LogicalResult schedulePath(PatternRewriter &rewriter,
2455 const DenseSet<Block *> &path, Location loc,
2456 Block *from, Block *to,
2457 Block *parentCtrlBlock) const {
2458 /// Schedule any registered block arguments to be executed before the body
2459 /// of the branch.
2460 rewriter.setInsertionPointToEnd(parentCtrlBlock);
2461 auto preSeqOp = calyx::SeqOp::create(rewriter, loc);
2462 rewriter.setInsertionPointToEnd(preSeqOp.getBodyBlock());
2463 for (auto barg :
2464 getState<ComponentLoweringState>().getBlockArgGroups(from, to))
2465 calyx::EnableOp::create(rewriter, barg.getLoc(), barg.getSymName());
2466
2467 return buildCFGControl(path, rewriter, parentCtrlBlock, from, to);
2468 }
2469
2470 LogicalResult buildCFGControl(DenseSet<Block *> path,
2471 PatternRewriter &rewriter,
2472 mlir::Block *parentCtrlBlock,
2473 mlir::Block *preBlock,
2474 mlir::Block *block) const {
2475 if (path.count(block) != 0)
2476 return preBlock->getTerminator()->emitError()
2477 << "CFG backedge detected. Loops must be raised to 'scf.while' or "
2478 "'scf.for' operations.";
2479
2480 rewriter.setInsertionPointToEnd(parentCtrlBlock);
2481 LogicalResult bbSchedResult =
2482 scheduleBasicBlock(rewriter, path, parentCtrlBlock, block);
2483 if (bbSchedResult.failed())
2484 return bbSchedResult;
2485
2486 path.insert(block);
2487 auto successors = block->getSuccessors();
2488 auto nSuccessors = successors.size();
2489 if (nSuccessors > 0) {
2490 auto brOp = dyn_cast<BranchOpInterface>(block->getTerminator());
2491 assert(brOp);
2492 if (nSuccessors > 1) {
2493 /// TODO(mortbopet): we could choose to support ie. std.switch, but it
2494 /// would probably be easier to just require it to be lowered
2495 /// beforehand.
2496 assert(nSuccessors == 2 &&
2497 "only conditional branches supported for now...");
2498 /// Wrap each branch inside an if/else.
2499 auto cond = brOp->getOperand(0);
2500 auto condGroup = getState<ComponentLoweringState>()
2501 .getEvaluatingGroup<calyx::CombGroupOp>(cond);
2502 auto symbolAttr = FlatSymbolRefAttr::get(
2503 StringAttr::get(getContext(), condGroup.getSymName()));
2504
2505 auto ifOp =
2506 calyx::IfOp::create(rewriter, brOp->getLoc(), cond, symbolAttr,
2507 /*initializeElseBody=*/true);
2508 rewriter.setInsertionPointToStart(ifOp.getThenBody());
2509 auto thenSeqOp = calyx::SeqOp::create(rewriter, brOp.getLoc());
2510 rewriter.setInsertionPointToStart(ifOp.getElseBody());
2511 auto elseSeqOp = calyx::SeqOp::create(rewriter, brOp.getLoc());
2512
2513 bool trueBrSchedSuccess =
2514 schedulePath(rewriter, path, brOp.getLoc(), block, successors[0],
2515 thenSeqOp.getBodyBlock())
2516 .succeeded();
2517 bool falseBrSchedSuccess = true;
2518 if (trueBrSchedSuccess) {
2519 falseBrSchedSuccess =
2520 schedulePath(rewriter, path, brOp.getLoc(), block, successors[1],
2521 elseSeqOp.getBodyBlock())
2522 .succeeded();
2523 }
2524
2525 return success(trueBrSchedSuccess && falseBrSchedSuccess);
2526 } else {
2527 /// Schedule sequentially within the current parent control block.
2528 return schedulePath(rewriter, path, brOp.getLoc(), block,
2529 successors.front(), parentCtrlBlock);
2530 }
2531 }
2532 return success();
2533 }
2534
2535 // Insert a Par of initGroups at Location loc. Used as helper for
2536 // `buildWhileCtrlOp` and `buildForCtrlOp`.
2537 void
2538 insertParInitGroups(PatternRewriter &rewriter, Location loc,
2539 const SmallVector<calyx::GroupOp> &initGroups) const {
2540 PatternRewriter::InsertionGuard g(rewriter);
2541 auto parOp = calyx::ParOp::create(rewriter, loc);
2542 rewriter.setInsertionPointToStart(parOp.getBodyBlock());
2543 for (calyx::GroupOp group : initGroups)
2544 calyx::EnableOp::create(rewriter, group.getLoc(), group.getName());
2545 }
2546
2547 calyx::WhileOp buildWhileCtrlOp(ScfWhileOp whileOp,
2548 SmallVector<calyx::GroupOp> initGroups,
2549 PatternRewriter &rewriter) const {
2550 Location loc = whileOp.getLoc();
2551 /// Insert while iter arg initialization group(s). Emit a
2552 /// parallel group to assign one or more registers all at once.
2553 insertParInitGroups(rewriter, loc, initGroups);
2554
2555 /// Insert the while op itself.
2556 auto cond = whileOp.getConditionValue();
2557 auto condGroup = getState<ComponentLoweringState>()
2558 .getEvaluatingGroup<calyx::CombGroupOp>(cond);
2559 auto symbolAttr = FlatSymbolRefAttr::get(
2560 StringAttr::get(getContext(), condGroup.getSymName()));
2561 return calyx::WhileOp::create(rewriter, loc, cond, symbolAttr);
2562 }
2563
2564 calyx::RepeatOp buildForCtrlOp(ScfForOp forOp,
2565 SmallVector<calyx::GroupOp> const &initGroups,
2566 uint64_t bound,
2567 PatternRewriter &rewriter) const {
2568 Location loc = forOp.getLoc();
2569 // Insert for iter arg initialization group(s). Emit a
2570 // parallel group to assign one or more registers all at once.
2571 insertParInitGroups(rewriter, loc, initGroups);
2572
2573 // Insert the repeatOp that corresponds to the For loop.
2574 return calyx::RepeatOp::create(rewriter, loc, bound);
2575 }
2576};
2577
2578/// LateSSAReplacement contains various functions for replacing SSA values that
2579/// were not replaced during op construction.
2581 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2582
2583 LogicalResult partiallyLowerFuncToComp(FuncOp funcOp,
2584 PatternRewriter &) const override {
2585 funcOp.walk([&](scf::IfOp op) {
2586 for (auto res : getState<ComponentLoweringState>().getResultRegs(op))
2587 op.getOperation()->getResults()[res.first].replaceAllUsesWith(
2588 res.second.getOut());
2589 });
2590
2591 funcOp.walk([&](scf::WhileOp op) {
2592 /// The yielded values returned from the while op will be present in the
2593 /// iterargs registers post execution of the loop.
2594 /// This is done now, as opposed to during BuildWhileGroups since if the
2595 /// results of the whileOp were replaced before
2596 /// BuildOpGroups/BuildControl, the whileOp would get dead-code
2597 /// eliminated.
2598 ScfWhileOp whileOp(op);
2599 for (auto res :
2600 getState<ComponentLoweringState>().getWhileLoopIterRegs(whileOp))
2601 whileOp.getOperation()->getResults()[res.first].replaceAllUsesWith(
2602 res.second.getOut());
2603 });
2604
2605 funcOp.walk([&](memref::LoadOp loadOp) {
2606 if (calyx::singleLoadFromMemory(loadOp)) {
2607 /// In buildOpGroups we did not replace loadOp's results, to ensure a
2608 /// link between evaluating groups (which fix the input addresses of a
2609 /// memory op) and a readData result. Now, we may replace these SSA
2610 /// values with their memoryOp readData output.
2611 loadOp.getResult().replaceAllUsesWith(
2612 getState<ComponentLoweringState>()
2613 .getMemoryInterface(loadOp.getMemref())
2614 .readData());
2615 }
2616 });
2617
2618 return success();
2619 }
2620};
2621
2622/// Erases FuncOp operations.
2624 using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern;
2625
2626 LogicalResult matchAndRewrite(FuncOp funcOp,
2627 PatternRewriter &rewriter) const override {
2628 rewriter.eraseOp(funcOp);
2629 return success();
2630 }
2631
2632 LogicalResult
2634 PatternRewriter &rewriter) const override {
2635 return success();
2636 }
2637};
2638
2639} // namespace scftocalyx
2640
2641namespace {
2642
2643using namespace circt::scftocalyx;
2644
2645//===----------------------------------------------------------------------===//
2646// Pass driver
2647//===----------------------------------------------------------------------===//
2648class SCFToCalyxPass : public circt::impl::SCFToCalyxBase<SCFToCalyxPass> {
2649public:
2650 SCFToCalyxPass(std::string topLevelFunction)
2651 : SCFToCalyxBase<SCFToCalyxPass>(), partialPatternRes(success()) {
2652 this->topLevelFunctionOpt = topLevelFunction;
2653 }
2654 void runOnOperation() override;
2655
2656 LogicalResult setTopLevelFunction(mlir::ModuleOp moduleOp,
2657 std::string &topLevelFunction) {
2658 if (!topLevelFunctionOpt.empty()) {
2659 if (SymbolTable::lookupSymbolIn(moduleOp, topLevelFunctionOpt) ==
2660 nullptr) {
2661 moduleOp.emitError() << "Top level function '" << topLevelFunctionOpt
2662 << "' not found in module.";
2663 return failure();
2664 }
2665 topLevelFunction = topLevelFunctionOpt;
2666 } else {
2667 /// No top level function set; infer top level if the module only contains
2668 /// a single function, else, throw error.
2669 auto funcOps = moduleOp.getOps<FuncOp>();
2670 if (std::distance(funcOps.begin(), funcOps.end()) == 1)
2671 topLevelFunction = (*funcOps.begin()).getSymName().str();
2672 else {
2673 moduleOp.emitError()
2674 << "Module contains multiple functions, but no top level "
2675 "function was set. Please see --top-level-function";
2676 return failure();
2677 }
2678 }
2679
2680 return createOptNewTopLevelFn(moduleOp, topLevelFunction);
2681 }
2682
2683 struct LoweringPattern {
2684 enum class Strategy { Once, Greedy };
2685 RewritePatternSet pattern;
2686 Strategy strategy;
2687 };
2688
2689 //// Labels the entry point of a Calyx program.
2690 /// Furthermore, this function performs validation on the input function,
2691 /// to ensure that we've implemented the capabilities necessary to convert
2692 /// it.
2693 LogicalResult labelEntryPoint(StringRef topLevelFunction) {
2694 // Program legalization - the partial conversion driver will not run
2695 // unless some pattern is provided - provide a dummy pattern.
2696 struct DummyPattern : public OpRewritePattern<mlir::ModuleOp> {
2697 using OpRewritePattern::OpRewritePattern;
2698 LogicalResult matchAndRewrite(mlir::ModuleOp,
2699 PatternRewriter &) const override {
2700 return failure();
2701 }
2702 };
2703
2704 ConversionTarget target(getContext());
2705 target.addLegalDialect<calyx::CalyxDialect>();
2706 target.addLegalDialect<scf::SCFDialect>();
2707 target.addIllegalDialect<hw::HWDialect>();
2708 target.addIllegalDialect<comb::CombDialect>();
2709
2710 // Only accept std operations which we've added lowerings for
2711 target.addIllegalDialect<FuncDialect>();
2712 target.addIllegalDialect<ArithDialect>();
2713 target.addLegalOp<
2714 AddIOp, SelectOp, SubIOp, CmpIOp, ShLIOp, ShRUIOp, ShRSIOp, AndIOp,
2715 XOrIOp, OrIOp, ExtUIOp, TruncIOp, CondBranchOp, BranchOp, MulIOp,
2716 DivUIOp, DivSIOp, RemUIOp, RemSIOp, ReturnOp, arith::ConstantOp,
2717 IndexCastOp, BitcastOp, FuncOp, ExtSIOp, CallOp, AddFOp, SubFOp, MulFOp,
2718 CmpFOp, FPToSIOp, SIToFPOp, DivFOp, math::SqrtOp>();
2719
2720 RewritePatternSet legalizePatterns(&getContext());
2721 legalizePatterns.add<DummyPattern>(&getContext());
2722 DenseSet<Operation *> legalizedOps;
2723 if (applyPartialConversion(getOperation(), target,
2724 std::move(legalizePatterns))
2725 .failed())
2726 return failure();
2727
2728 // Program conversion
2729 return calyx::applyModuleOpConversion(getOperation(), topLevelFunction);
2730 }
2731
2732 /// 'Once' patterns are expected to take an additional LogicalResult&
2733 /// argument, to forward their result state (greedyPatternRewriteDriver
2734 /// results are skipped for Once patterns).
2735 template <typename TPattern, typename... PatternArgs>
2736 void addOncePattern(SmallVectorImpl<LoweringPattern> &patterns,
2737 PatternArgs &&...args) {
2738 RewritePatternSet ps(&getContext());
2739 ps.add<TPattern>(&getContext(), partialPatternRes, args...);
2740 patterns.push_back(
2741 LoweringPattern{std::move(ps), LoweringPattern::Strategy::Once});
2742 }
2743
2744 template <typename TPattern, typename... PatternArgs>
2745 void addGreedyPattern(SmallVectorImpl<LoweringPattern> &patterns,
2746 PatternArgs &&...args) {
2747 RewritePatternSet ps(&getContext());
2748 ps.add<TPattern>(&getContext(), args...);
2749 patterns.push_back(
2750 LoweringPattern{std::move(ps), LoweringPattern::Strategy::Greedy});
2751 }
2752
2753 LogicalResult runPartialPattern(RewritePatternSet &pattern, bool runOnce) {
2754 assert(pattern.getNativePatterns().size() == 1 &&
2755 "Should only apply 1 partial lowering pattern at once");
2756
2757 // During component creation, the function body is inlined into the
2758 // component body for further processing. However, proper control flow
2759 // will only be established later in the conversion process, so ensure
2760 // that rewriter optimizations (especially DCE) are disabled.
2761 GreedyRewriteConfig config;
2762 config.setRegionSimplificationLevel(
2763 mlir::GreedySimplifyRegionLevel::Disabled);
2764 if (runOnce)
2765 config.setMaxIterations(1);
2766
2767 /// Can't return applyPatternsGreedily. Root isn't
2768 /// necessarily erased so it will always return failed(). Instead,
2769 /// forward the 'succeeded' value from PartialLoweringPatternBase.
2770 (void)applyPatternsGreedily(getOperation(), std::move(pattern), config);
2771 return partialPatternRes;
2772 }
2773
2774private:
2775 LogicalResult partialPatternRes;
2776 std::shared_ptr<calyx::CalyxLoweringState> loweringState = nullptr;
2777
2778 /// Creates a new new top-level function based on `baseName`.
2779 FuncOp createNewTopLevelFn(ModuleOp moduleOp, std::string &baseName) {
2780 std::string newName = "main";
2781
2782 if (auto *existingMainOp = SymbolTable::lookupSymbolIn(moduleOp, newName)) {
2783 auto existingMainFunc = dyn_cast<FuncOp>(existingMainOp);
2784 if (existingMainFunc == nullptr) {
2785 moduleOp.emitError() << "Symbol 'main' exists but is not a function";
2786 return nullptr;
2787 }
2788 unsigned counter = 0;
2789 std::string newOldName = baseName;
2790 while (SymbolTable::lookupSymbolIn(moduleOp, newOldName))
2791 newOldName = llvm::join_items("_", baseName, std::to_string(++counter));
2792 existingMainFunc.setName(newOldName);
2793 if (baseName == "main")
2794 baseName = newOldName;
2795 }
2796
2797 // Create the new "main" function
2798 OpBuilder builder(moduleOp.getContext());
2799 builder.setInsertionPointToStart(moduleOp.getBody());
2800
2801 FunctionType funcType = builder.getFunctionType({}, {});
2802
2803 if (auto newFunc =
2804 FuncOp::create(builder, moduleOp.getLoc(), newName, funcType))
2805 return newFunc;
2806
2807 return nullptr;
2808 }
2809
2810 /// Insert a call from the newly created top-level function/`caller` to the
2811 /// old top-level function/`callee`; and create `memref.alloc`s inside the new
2812 /// top-level function for arguments with `memref` types and for the
2813 /// `memref.alloc`s inside `callee`.
2814 void insertCallFromNewTopLevel(OpBuilder &builder, FuncOp caller,
2815 FuncOp callee) {
2816 if (caller.getBody().empty()) {
2817 caller.addEntryBlock();
2818 }
2819
2820 Block *callerEntryBlock = &caller.getBody().front();
2821 builder.setInsertionPointToStart(callerEntryBlock);
2822
2823 // For those non-memref arguments passing to the original top-level
2824 // function, we need to copy them to the new top-level function.
2825 SmallVector<Type, 4> nonMemRefCalleeArgTypes;
2826 for (auto arg : callee.getArguments()) {
2827 if (!isa<MemRefType>(arg.getType())) {
2828 nonMemRefCalleeArgTypes.push_back(arg.getType());
2829 }
2830 }
2831
2832 for (Type type : nonMemRefCalleeArgTypes) {
2833 callerEntryBlock->addArgument(type, caller.getLoc());
2834 }
2835
2836 FunctionType callerFnType = caller.getFunctionType();
2837 SmallVector<Type, 4> updatedCallerArgTypes(
2838 caller.getFunctionType().getInputs());
2839 updatedCallerArgTypes.append(nonMemRefCalleeArgTypes.begin(),
2840 nonMemRefCalleeArgTypes.end());
2841 caller.setType(FunctionType::get(caller.getContext(), updatedCallerArgTypes,
2842 callerFnType.getResults()));
2843
2844 Block *calleeFnBody = &callee.getBody().front();
2845 unsigned originalCalleeArgNum = callee.getArguments().size();
2846
2847 SmallVector<Value, 4> extraMemRefArgs;
2848 SmallVector<Type, 4> extraMemRefArgTypes;
2849 SmallVector<Value, 4> extraMemRefOperands;
2850 SmallVector<Operation *, 4> opsToModify;
2851 for (auto &op : callee.getBody().getOps()) {
2852 if (isa<memref::AllocaOp, memref::AllocOp, memref::GetGlobalOp>(op))
2853 opsToModify.push_back(&op);
2854 }
2855
2856 // Replace `alloc`/`getGlobal` in the original top-level with new
2857 // corresponding operations in the new top-level.
2858 builder.setInsertionPointToEnd(callerEntryBlock);
2859 for (auto *op : opsToModify) {
2860 // TODO (https://github.com/llvm/circt/issues/7764)
2861 Value newOpRes;
2862 TypeSwitch<Operation *>(op)
2863 .Case<memref::AllocaOp>([&](memref::AllocaOp allocaOp) {
2864 newOpRes = memref::AllocaOp::create(builder, callee.getLoc(),
2865 allocaOp.getType());
2866 })
2867 .Case<memref::AllocOp>([&](memref::AllocOp allocOp) {
2868 newOpRes = memref::AllocOp::create(builder, callee.getLoc(),
2869 allocOp.getType());
2870 })
2871 .Case<memref::GetGlobalOp>([&](memref::GetGlobalOp getGlobalOp) {
2872 newOpRes = memref::GetGlobalOp::create(builder, caller.getLoc(),
2873 getGlobalOp.getType(),
2874 getGlobalOp.getName());
2875 })
2876 .Default([&](Operation *defaultOp) {
2877 llvm::report_fatal_error("Unsupported operation in TypeSwitch");
2878 });
2879 extraMemRefOperands.push_back(newOpRes);
2880
2881 calleeFnBody->addArgument(newOpRes.getType(), callee.getLoc());
2882 BlockArgument newBodyArg = calleeFnBody->getArguments().back();
2883 op->getResult(0).replaceAllUsesWith(newBodyArg);
2884 op->erase();
2885 extraMemRefArgs.push_back(newBodyArg);
2886 extraMemRefArgTypes.push_back(newBodyArg.getType());
2887 }
2888
2889 SmallVector<Type, 4> updatedCalleeArgTypes(
2890 callee.getFunctionType().getInputs());
2891 updatedCalleeArgTypes.append(extraMemRefArgTypes.begin(),
2892 extraMemRefArgTypes.end());
2893 callee.setType(FunctionType::get(callee.getContext(), updatedCalleeArgTypes,
2894 callee.getFunctionType().getResults()));
2895
2896 unsigned otherArgsCount = 0;
2897 SmallVector<Value, 4> calleeArgFnOperands;
2898 builder.setInsertionPointToStart(callerEntryBlock);
2899 for (auto arg : callee.getArguments().take_front(originalCalleeArgNum)) {
2900 if (isa<MemRefType>(arg.getType())) {
2901 auto memrefType = cast<MemRefType>(arg.getType());
2902 auto allocOp =
2903 memref::AllocOp::create(builder, callee.getLoc(), memrefType);
2904 calleeArgFnOperands.push_back(allocOp);
2905 } else {
2906 auto callerArg = callerEntryBlock->getArgument(otherArgsCount++);
2907 calleeArgFnOperands.push_back(callerArg);
2908 }
2909 }
2910
2911 SmallVector<Value, 4> fnOperands;
2912 fnOperands.append(calleeArgFnOperands.begin(), calleeArgFnOperands.end());
2913 fnOperands.append(extraMemRefOperands.begin(), extraMemRefOperands.end());
2914 auto calleeName =
2915 SymbolRefAttr::get(builder.getContext(), callee.getSymName());
2916 auto resultTypes = callee.getResultTypes();
2917
2918 builder.setInsertionPointToEnd(callerEntryBlock);
2919 CallOp::create(builder, caller.getLoc(), calleeName, resultTypes,
2920 fnOperands);
2921 ReturnOp::create(builder, caller.getLoc());
2922 }
2923
2924 /// Conditionally creates an optional new top-level function; and inserts a
2925 /// call from the new top-level function to the old top-level function if we
2926 /// did create one
2927 LogicalResult createOptNewTopLevelFn(ModuleOp moduleOp,
2928 std::string &topLevelFunction) {
2929 auto hasMemrefArguments = [](FuncOp func) {
2930 return std::any_of(
2931 func.getArguments().begin(), func.getArguments().end(),
2932 [](BlockArgument arg) { return isa<MemRefType>(arg.getType()); });
2933 };
2934
2935 /// We only create a new top-level function and call the original top-level
2936 /// function from the new one if the original top-level has `memref` in its
2937 /// argument
2938 auto funcOps = moduleOp.getOps<FuncOp>();
2939 bool hasMemrefArgsInTopLevel =
2940 std::any_of(funcOps.begin(), funcOps.end(), [&](auto funcOp) {
2941 return funcOp.getName() == topLevelFunction &&
2942 hasMemrefArguments(funcOp);
2943 });
2944
2945 if (hasMemrefArgsInTopLevel) {
2946 auto newTopLevelFunc = createNewTopLevelFn(moduleOp, topLevelFunction);
2947 if (!newTopLevelFunc)
2948 return failure();
2949
2950 OpBuilder builder(moduleOp.getContext());
2951 Operation *oldTopLevelFuncOp =
2952 SymbolTable::lookupSymbolIn(moduleOp, topLevelFunction);
2953 if (auto oldTopLevelFunc = dyn_cast<FuncOp>(oldTopLevelFuncOp))
2954 insertCallFromNewTopLevel(builder, newTopLevelFunc, oldTopLevelFunc);
2955 else {
2956 moduleOp.emitOpError("Original top-level function not found!");
2957 return failure();
2958 }
2959 topLevelFunction = "main";
2960 }
2961
2962 return success();
2963 }
2964};
2965
2966void SCFToCalyxPass::runOnOperation() {
2967 // Clear internal state. See https://github.com/llvm/circt/issues/3235
2968 loweringState.reset();
2969 partialPatternRes = LogicalResult::failure();
2970
2971 std::string topLevelFunction;
2972 if (failed(setTopLevelFunction(getOperation(), topLevelFunction))) {
2973 signalPassFailure();
2974 return;
2975 }
2976
2977 /// Start conversion
2978 if (failed(labelEntryPoint(topLevelFunction))) {
2979 signalPassFailure();
2980 return;
2981 }
2982 loweringState = std::make_shared<calyx::CalyxLoweringState>(getOperation(),
2983 topLevelFunction);
2984
2985 /// --------------------------------------------------------------------------
2986 /// If you are a developer, it may be helpful to add a
2987 /// 'getOperation()->dump()' call after the execution of each stage to
2988 /// view the transformations that's going on.
2989 /// --------------------------------------------------------------------------
2990
2991 /// A mapping is maintained between a function operation and its corresponding
2992 /// Calyx component.
2993 DenseMap<FuncOp, calyx::ComponentOp> funcMap;
2994 SmallVector<LoweringPattern, 8> loweringPatterns;
2995 calyx::PatternApplicationState patternState;
2996
2997 /// Creates a new Calyx component for each FuncOp in the inpurt module.
2998 addOncePattern<FuncOpConversion>(loweringPatterns, patternState, funcMap,
2999 *loweringState);
3000
3001 /// This pass inlines scf.ExecuteRegionOp's by adding control-flow.
3002 addGreedyPattern<InlineExecuteRegionOpPattern>(loweringPatterns);
3003
3004 /// This pattern converts all index typed values to an i32 integer.
3005 addOncePattern<calyx::ConvertIndexTypes>(loweringPatterns, patternState,
3006 funcMap, *loweringState);
3007
3008 /// This pattern creates registers for all basic-block arguments.
3009 addOncePattern<calyx::BuildBasicBlockRegs>(loweringPatterns, patternState,
3010 funcMap, *loweringState);
3011
3012 addOncePattern<calyx::BuildCallInstance>(loweringPatterns, patternState,
3013 funcMap, *loweringState);
3014
3015 /// This pattern creates registers for the function return values.
3016 addOncePattern<calyx::BuildReturnRegs>(loweringPatterns, patternState,
3017 funcMap, *loweringState);
3018
3019 /// This pattern creates registers for iteration arguments of scf.while
3020 /// operations. Additionally, creates a group for assigning the initial
3021 /// value of the iteration argument registers.
3022 addOncePattern<BuildWhileGroups>(loweringPatterns, patternState, funcMap,
3023 *loweringState);
3024
3025 /// This pattern creates registers for iteration arguments of scf.for
3026 /// operations. Additionally, creates a group for assigning the initial
3027 /// value of the iteration argument registers.
3028 addOncePattern<BuildForGroups>(loweringPatterns, patternState, funcMap,
3029 *loweringState);
3030
3031 addOncePattern<BuildIfGroups>(loweringPatterns, patternState, funcMap,
3032 *loweringState);
3033
3034 /// This pattern converts operations within basic blocks to Calyx library
3035 /// operators. Combinational operations are assigned inside a
3036 /// calyx::CombGroupOp, and sequential inside calyx::GroupOps.
3037 /// Sequential groups are registered with the Block* of which the operation
3038 /// originated from. This is used during control schedule generation. By
3039 /// having a distinct group for each operation, groups are analogous to SSA
3040 /// values in the source program.
3041 addOncePattern<BuildOpGroups>(loweringPatterns, patternState, funcMap,
3042 *loweringState, writeJsonOpt);
3043
3044 /// This pattern traverses the CFG of the program and generates a control
3045 /// schedule based on the calyx::GroupOp's which were registered for each
3046 /// basic block in the source function.
3047 addOncePattern<BuildControl>(loweringPatterns, patternState, funcMap,
3048 *loweringState);
3049
3050 /// This pass recursively inlines use-def chains of combinational logic (from
3051 /// non-stateful groups) into groups referenced in the control schedule.
3052 addOncePattern<calyx::InlineCombGroups>(loweringPatterns, patternState,
3053 *loweringState);
3054
3055 /// This pattern performs various SSA replacements that must be done
3056 /// after control generation.
3057 addOncePattern<LateSSAReplacement>(loweringPatterns, patternState, funcMap,
3058 *loweringState);
3059
3060 /// Eliminate any unused combinational groups. This is done before
3061 /// calyx::RewriteMemoryAccesses to avoid inferring slice components for
3062 /// groups that will be removed.
3063 addGreedyPattern<calyx::EliminateUnusedCombGroups>(loweringPatterns);
3064
3065 /// This pattern rewrites accesses to memories which are too wide due to
3066 /// index types being converted to a fixed-width integer type.
3067 addOncePattern<calyx::RewriteMemoryAccesses>(loweringPatterns, patternState,
3068 *loweringState);
3069
3070 /// This pattern removes the source FuncOp which has now been converted into
3071 /// a Calyx component.
3072 addOncePattern<CleanupFuncOps>(loweringPatterns, patternState, funcMap,
3073 *loweringState);
3074
3075 /// Sequentially apply each lowering pattern.
3076 for (auto &pat : loweringPatterns) {
3077 LogicalResult partialPatternRes = runPartialPattern(
3078 pat.pattern,
3079 /*runOnce=*/pat.strategy == LoweringPattern::Strategy::Once);
3080 if (succeeded(partialPatternRes))
3081 continue;
3082 signalPassFailure();
3083 return;
3084 }
3085
3086 //===--------------------------------------------------------------------===//
3087 // Cleanup patterns
3088 //===--------------------------------------------------------------------===//
3089 RewritePatternSet cleanupPatterns(&getContext());
3090 cleanupPatterns.add<calyx::MultipleGroupDonePattern,
3092 if (failed(
3093 applyPatternsGreedily(getOperation(), std::move(cleanupPatterns)))) {
3094 signalPassFailure();
3095 return;
3096 }
3097
3098 if (ciderSourceLocationMetadata) {
3099 // Debugging information for the Cider debugger.
3100 // Reference: https://docs.calyxir.org/debug/cider.html
3101 SmallVector<Attribute, 16> sourceLocations;
3102 getOperation()->walk([&](calyx::ComponentOp component) {
3103 return getCiderSourceLocationMetadata(component, sourceLocations);
3104 });
3105
3106 MLIRContext *context = getOperation()->getContext();
3107 getOperation()->setAttr("calyx.metadata",
3108 ArrayAttr::get(context, sourceLocations));
3109 }
3110}
3111} // namespace
3112
3113//===----------------------------------------------------------------------===//
3114// Pass initialization
3115//===----------------------------------------------------------------------===//
3116
3117std::unique_ptr<OperationPass<ModuleOp>>
3118createSCFToCalyxPass(std::string topLevelFunction) {
3119 return std::make_unique<SCFToCalyxPass>(topLevelFunction);
3120}
3121
3122} // namespace circt
assert(baseType &&"element must be base type")
static Block * getBodyBlock(FModuleLike mod)
RewritePatternSet pattern
Strategy strategy
std::shared_ptr< calyx::CalyxLoweringState > loweringState
LogicalResult partialPatternRes
An interface for conversion passes that lower Calyx programs.
std::string irName(ValueOrBlock &v)
Returns a meaningful name for a value within the program scope.
std::string blockName(Block *b)
Returns a meaningful name for a block within the program scope (removes the ^ prefix from block names...
T * getState(calyx::ComponentOp op)
Returns the component lowering state associated with op.
void setFuncOpResultMapping(const DenseMap< unsigned, unsigned > &mapping)
Assign a mapping between the source funcOp result indices and the corresponding output port indices o...
std::string getUniqueName(StringRef prefix)
Returns a unique name within compOp with the provided prefix.
calyx::ComponentOp component
The component which this lowering state is associated to.
void registerMemoryInterface(Value memref, const calyx::MemoryInterface &memoryInterface)
Registers a memory interface as being associated with a memory identified by 'memref'.
calyx::ComponentOp getComponentOp()
Returns the calyx::ComponentOp associated with this lowering state.
void setDataField(StringRef name, llvm::json::Array data)
ComponentLoweringStateInterface(calyx::ComponentOp component)
void setFormat(StringRef name, std::string numType, bool isSigned, unsigned width)
FuncOpPartialLoweringPatterns are patterns which intend to match on FuncOps and then perform their ow...
calyx::ComponentOp getComponent() const
Returns the component operation associated with the currently executing partial lowering.
DenseMap< mlir::func::FuncOp, calyx::ComponentOp > & functionMapping
CalyxLoweringState & loweringState() const
Return the calyx lowering state for this pattern.
FuncOpPartialLoweringPattern(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, DenseMap< mlir::func::FuncOp, calyx::ComponentOp > &map, calyx::CalyxLoweringState &state)
calyx::GroupOp getLoopLatchGroup(ScfWhileOp op)
Retrieve the loop latch group registered for op.
void setLoopLatchGroup(ScfWhileOp op, calyx::GroupOp group)
Registers grp to be the loop latch group of op.
calyx::RegisterOp getLoopIterReg(ScfForOp op, unsigned idx)
Return a mapping of block argument indices to block argument.
void addLoopIterReg(ScfWhileOp op, calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th iter_args register for 'op'.
void setLoopInitGroups(ScfWhileOp op, SmallVector< calyx::GroupOp > groups)
Registers groups to be the loop init groups of op.
SmallVector< calyx::GroupOp > getLoopInitGroups(ScfWhileOp op)
Retrieve the loop init groups registered for op.
calyx::GroupOp buildLoopIterArgAssignments(OpBuilder &builder, ScfWhileOp op, calyx::ComponentOp componentOp, Twine uniqueSuffix, MutableArrayRef< OpOperand > ops)
Creates a new group that assigns the 'ops' values to the iter arg registers of the loop operation.
const DenseMap< unsigned, calyx::RegisterOp > & getLoopIterRegs(ScfWhileOp op)
Return a mapping of block argument indices to block argument.
Holds common utilities used for scheduling when lowering to Calyx.
Builds a control schedule by traversing the CFG of the function and associating this with the previou...
calyx::RepeatOp buildForCtrlOp(ScfForOp forOp, SmallVector< calyx::GroupOp > const &initGroups, uint64_t bound, PatternRewriter &rewriter) const
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
LogicalResult schedulePath(PatternRewriter &rewriter, const DenseSet< Block * > &path, Location loc, Block *from, Block *to, Block *parentCtrlBlock) const
Schedules a block by inserting a branch argument assignment block (if any) before recursing into the ...
calyx::WhileOp buildWhileCtrlOp(ScfWhileOp whileOp, SmallVector< calyx::GroupOp > initGroups, PatternRewriter &rewriter) const
LogicalResult scheduleBasicBlock(PatternRewriter &rewriter, const DenseSet< Block * > &path, mlir::Block *parentCtrlBlock, mlir::Block *block) const
Sequentially schedules the groups that registered themselves with 'block'.
LogicalResult buildCFGControl(DenseSet< Block * > path, PatternRewriter &rewriter, mlir::Block *parentCtrlBlock, mlir::Block *preBlock, mlir::Block *block) const
void insertParInitGroups(PatternRewriter &rewriter, Location loc, const SmallVector< calyx::GroupOp > &initGroups) const
In BuildForGroups, a register is created for the iteration argument of the for op.
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
Iterate through the operations of a source function and instantiate components or primitives based on...
BuildOpGroups(MLIRContext *context, LogicalResult &resRef, calyx::PatternApplicationState &patternState, DenseMap< mlir::func::FuncOp, calyx::ComponentOp > &map, calyx::CalyxLoweringState &state, mlir::Pass::Option< std::string > &writeJsonOpt)
LogicalResult buildCmpIOpHelper(PatternRewriter &rewriter, CmpIOp op) const
void setupCmpIOp(PatternRewriter &rewriter, CmpIOp cmpIOp, Operation *group, calyx::RegisterOp &condReg, calyx::RegisterOp &resReg, TCalyxLibOp calyxOp) const
LogicalResult buildFpIntTypeCastOp(PatternRewriter &rewriter, TSrcOp op, unsigned inputWidth, unsigned outputWidth, StringRef signedPort) const
TGroupOp createGroupForOp(PatternRewriter &rewriter, Operation *op) const
Creates a group named by the basic block which the input op resides in.
LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op) const
buildLibraryOp which provides in- and output types based on the operands and results of the op argume...
LogicalResult buildOp(PatternRewriter &rewriter, scf::YieldOp yieldOp) const
Op builder specializations.
calyx::RegisterOp createSignalRegister(PatternRewriter &rewriter, Value signal, bool invert, StringRef nameSuffix, calyx::CompareFOpIEEE754 calyxCmpFOp, calyx::GroupOp group) const
void assignAddressPorts(PatternRewriter &rewriter, Location loc, calyx::GroupInterface group, calyx::MemoryInterface memoryInterface, Operation::operand_range addressValues) const
Creates assignments within the provided group to the address ports of the memoryOp based on the provi...
LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op, TypeRange srcTypes, TypeRange dstTypes) const
buildLibraryOp will build a TCalyxLibOp inside a TGroupOp based on the source operation TSrcOp.
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
LogicalResult buildLibraryBinaryPipeOp(PatternRewriter &rewriter, TSrcOp op, TOpType opPipe, Value out) const
buildLibraryBinaryPipeOp will build a TCalyxLibBinaryPipeOp, to deal with MulIOp, DivUIOp and RemUIOp...
mlir::Pass::Option< std::string > & writeJson
In BuildWhileGroups, a register is created for each iteration argumenet of the while op.
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
Erases FuncOp operations.
LogicalResult matchAndRewrite(FuncOp funcOp, PatternRewriter &rewriter) const override
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
Handles the current state of lowering of a Calyx component.
ComponentLoweringState(calyx::ComponentOp component)
void setForLoopInitGroups(ScfForOp op, SmallVector< calyx::GroupOp > groups)
calyx::GroupOp buildForLoopIterArgAssignments(OpBuilder &builder, ScfForOp op, calyx::ComponentOp componentOp, Twine uniqueSuffix, MutableArrayRef< OpOperand > ops)
void setForLoopLatchGroup(ScfForOp op, calyx::GroupOp group)
SmallVector< calyx::GroupOp > getForLoopInitGroups(ScfForOp op)
void addForLoopIterReg(ScfForOp op, calyx::RegisterOp reg, unsigned idx)
calyx::GroupOp getForLoopLatchGroup(ScfForOp op)
calyx::RegisterOp getForLoopIterReg(ScfForOp op, unsigned idx)
const DenseMap< unsigned, calyx::RegisterOp > & getForLoopIterRegs(ScfForOp op)
DenseMap< Operation *, calyx::GroupOp > elseGroup
DenseMap< Operation *, calyx::GroupOp > thenGroup
void setCondReg(scf::IfOp op, calyx::RegisterOp regOp)
const DenseMap< unsigned, calyx::RegisterOp > & getResultRegs(scf::IfOp op)
void setElseGroup(scf::IfOp op, calyx::GroupOp group)
void setResultRegs(scf::IfOp op, calyx::RegisterOp reg, unsigned idx)
void setThenGroup(scf::IfOp op, calyx::GroupOp group)
DenseMap< Operation *, DenseMap< unsigned, calyx::RegisterOp > > resultRegs
calyx::RegisterOp getResultRegs(scf::IfOp op, unsigned idx)
calyx::RegisterOp getCondReg(scf::IfOp op)
calyx::GroupOp getThenGroup(scf::IfOp op)
DenseMap< Operation *, calyx::RegisterOp > condReg
calyx::GroupOp getElseGroup(scf::IfOp op)
Inlines Calyx ExecuteRegionOp operations within their parent blocks.
LogicalResult matchAndRewrite(scf::ExecuteRegionOp execOp, PatternRewriter &rewriter) const override
LateSSAReplacement contains various functions for replacing SSA values that were not replaced during ...
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &) const override
std::optional< int64_t > getBound() override
Block::BlockArgListType getBodyArgs() override
Block * getBodyBlock() override
Block * getBodyBlock() override
Block::BlockArgListType getBodyArgs() override
Value getConditionValue() override
std::optional< int64_t > getBound() override
Block * getConditionBlock() override
Stores the state information for condition checks involving sequential computation.
void setSeqResReg(Operation *op, calyx::RegisterOp reg)
calyx::RegisterOp getSeqResReg(Operation *op)
DenseMap< Operation *, calyx::RegisterOp > resultRegs
calyx::GroupOp buildWhileLoopIterArgAssignments(OpBuilder &builder, ScfWhileOp op, calyx::ComponentOp componentOp, Twine uniqueSuffix, MutableArrayRef< OpOperand > ops)
void setWhileLoopInitGroups(ScfWhileOp op, SmallVector< calyx::GroupOp > groups)
SmallVector< calyx::GroupOp > getWhileLoopInitGroups(ScfWhileOp op)
void addWhileLoopIterReg(ScfWhileOp op, calyx::RegisterOp reg, unsigned idx)
void setWhileLoopLatchGroup(ScfWhileOp op, calyx::GroupOp group)
const DenseMap< unsigned, calyx::RegisterOp > & getWhileLoopIterRegs(ScfWhileOp op)
calyx::GroupOp getWhileLoopLatchGroup(ScfWhileOp op)
bool parentIsSeqCell(Value value)
void addMandatoryComponentPorts(PatternRewriter &rewriter, SmallVectorImpl< calyx::PortInfo > &ports)
void buildAssignmentsForRegisterWrite(OpBuilder &builder, calyx::GroupOp groupOp, calyx::ComponentOp componentOp, calyx::RegisterOp &reg, Value inputValue)
Creates register assignment operations within the provided groupOp.
DenseMap< const mlir::RewritePattern *, SmallPtrSet< Operation *, 16 > > PatternApplicationState
Extra state that is passed to all PartialLoweringPatterns so they can record when they have run on an...
PredicateInfo getPredicateInfo(mlir::arith::CmpFPredicate pred)
Type normalizeType(OpBuilder &builder, Type type)
LogicalResult applyModuleOpConversion(mlir::ModuleOp, StringRef topLevelFunction)
Helper to update the top-level ModuleOp to set the entrypoing function.
WalkResult getCiderSourceLocationMetadata(calyx::ComponentOp component, SmallVectorImpl< Attribute > &sourceLocations)
bool matchConstantOp(Operation *op, APInt &value)
unsigned handleZeroWidth(int64_t dim)
hw::ConstantOp createConstant(Location loc, OpBuilder &builder, ComponentOp component, size_t width, size_t value)
A helper function to create constants in the HW dialect.
bool noStoresToMemory(Value memoryReference)
bool singleLoadFromMemory(Value memoryReference)
Type toBitVector(T type)
Performs a bit cast from a non-signless integer type value, such as a floating point value,...
std::string getInstanceName(mlir::func::CallOp callOp)
A helper function to get the instance name.
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition CombOps.cpp:65
static constexpr std::string_view sPortNameAttr
Definition SCFToCalyx.h:29
static constexpr std::string_view unrolledParallelAttr
static LogicalResult buildAllocOp(ComponentLoweringState &componentState, PatternRewriter &rewriter, TAllocOp allocOp)
std::variant< calyx::GroupOp, WhileScheduleable, ForScheduleable, IfScheduleable, CallScheduleable, ParScheduleable > Scheduleable
A variant of types representing scheduleable operations.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< OperationPass< ModuleOp > > createSCFToCalyxPass(std::string topLevelFunction="")
Create an SCF to Calyx conversion pass.
When building groups which contain accesses to multiple sequential components, a group_done op is cre...
GroupDoneOp's are terminator operations and should therefore be the last operator in a group.
This holds information about the port for either a Component or Cell.
Definition CalyxOps.h:89
Predicate information for the floating point comparisons.
calyx::InstanceOp instanceOp
Instance for invoking.
ScfForOp forOp
For operation to schedule.
Creates a new Calyx component for each FuncOp in the program.
LogicalResult partiallyLowerFuncToComp(FuncOp funcOp, PatternRewriter &rewriter) const override
scf::ParallelOp parOp
Parallel operation to schedule.
ScfWhileOp whileOp
While operation to schedule.