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