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