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