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