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