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