CIRCT  18.0.0git
CalyxLoweringUtils.cpp
Go to the documentation of this file.
1 //===- CalyxLoweringUtils.cpp - Calyx lowering utility methods --*- 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 // Various lowering utility methods converting to and from Calyx programs.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "circt/Support/LLVM.h"
17 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
18 #include "mlir/Dialect/MemRef/IR/MemRef.h"
19 #include "mlir/Dialect/SCF/IR/SCF.h"
20 #include "mlir/IR/Matchers.h"
21 
22 #include <variant>
23 
24 using namespace llvm;
25 using namespace mlir;
26 using namespace mlir::arith;
27 
28 namespace circt {
29 namespace calyx {
30 
31 void appendPortsForExternalMemref(PatternRewriter &rewriter, StringRef memName,
32  Value memref, unsigned memoryID,
33  SmallVectorImpl<calyx::PortInfo> &inPorts,
34  SmallVectorImpl<calyx::PortInfo> &outPorts) {
35  MemRefType memrefType = memref.getType().cast<MemRefType>();
36 
37  // Ports constituting a memory interface are added a set of attributes under
38  // a "mem : {...}" dictionary. These attributes allows for deducing which
39  // top-level I/O signals constitutes a unique memory interface.
40  auto getMemoryInterfaceAttr = [&](StringRef tag,
41  std::optional<unsigned> addrIdx = {}) {
42  auto attrs = SmallVector<NamedAttribute>{
43  // "id" denotes a unique memory interface.
44  rewriter.getNamedAttr("id", rewriter.getI32IntegerAttr(memoryID)),
45  // "tag" denotes the function of this signal.
46  rewriter.getNamedAttr("tag", rewriter.getStringAttr(tag))};
47  if (addrIdx.has_value())
48  // "addr_idx" denotes the address index of this signal, for
49  // multi-dimensional memory interfaces.
50  attrs.push_back(rewriter.getNamedAttr(
51  "addr_idx", rewriter.getI32IntegerAttr(*addrIdx)));
52 
53  return rewriter.getNamedAttr("mem", rewriter.getDictionaryAttr(attrs));
54  };
55 
56  // Read data
57  inPorts.push_back(calyx::PortInfo{
58  rewriter.getStringAttr(memName + "_read_data"),
59  memrefType.getElementType(), calyx::Direction::Input,
60  DictionaryAttr::get(rewriter.getContext(),
61  {getMemoryInterfaceAttr("read_data")})});
62 
63  // Done
64  inPorts.push_back(
65  calyx::PortInfo{rewriter.getStringAttr(memName + "_done"),
66  rewriter.getI1Type(), calyx::Direction::Input,
67  DictionaryAttr::get(rewriter.getContext(),
68  {getMemoryInterfaceAttr("done")})});
69 
70  // Write data
71  outPorts.push_back(calyx::PortInfo{
72  rewriter.getStringAttr(memName + "_write_data"),
73  memrefType.getElementType(), calyx::Direction::Output,
74  DictionaryAttr::get(rewriter.getContext(),
75  {getMemoryInterfaceAttr("write_data")})});
76 
77  // Memory address outputs
78  for (auto dim : enumerate(memrefType.getShape())) {
79  outPorts.push_back(calyx::PortInfo{
80  rewriter.getStringAttr(memName + "_addr" + std::to_string(dim.index())),
81  rewriter.getIntegerType(calyx::handleZeroWidth(dim.value())),
83  DictionaryAttr::get(rewriter.getContext(),
84  {getMemoryInterfaceAttr("addr", dim.index())})});
85  }
86 
87  // Write enable
88  outPorts.push_back(calyx::PortInfo{
89  rewriter.getStringAttr(memName + "_write_en"), rewriter.getI1Type(),
91  DictionaryAttr::get(rewriter.getContext(),
92  {getMemoryInterfaceAttr("write_en")})});
93 }
94 
95 WalkResult
96 getCiderSourceLocationMetadata(calyx::ComponentOp component,
97  SmallVectorImpl<Attribute> &sourceLocations) {
98  Builder builder(component->getContext());
99  return component.getControlOp().walk([&](Operation *op) {
100  if (!calyx::isControlLeafNode(op))
101  return WalkResult::advance();
102 
103  std::string sourceLocation;
104  llvm::raw_string_ostream os(sourceLocation);
105  op->getLoc()->print(os);
106  int64_t position = sourceLocations.size();
107  sourceLocations.push_back(
108  StringAttr::get(op->getContext(), sourceLocation));
109 
110  op->setAttr("pos", builder.getI64IntegerAttr(position));
111  return WalkResult::advance();
112  });
113 }
114 
115 bool matchConstantOp(Operation *op, APInt &value) {
116  return mlir::detail::constant_int_value_binder(&value).match(op);
117 }
118 
119 bool singleLoadFromMemory(Value memoryReference) {
120  return llvm::count_if(memoryReference.getUses(), [](OpOperand &user) {
121  return isa<mlir::memref::LoadOp>(user.getOwner());
122  }) <= 1;
123 }
124 
125 bool noStoresToMemory(Value memoryReference) {
126  return llvm::none_of(memoryReference.getUses(), [](OpOperand &user) {
127  return isa<mlir::memref::StoreOp>(user.getOwner());
128  });
129 }
130 
131 Value getComponentOutput(calyx::ComponentOp compOp, unsigned outPortIdx) {
132  size_t index = compOp.getInputPortInfo().size() + outPortIdx;
133  assert(index < compOp.getNumArguments() &&
134  "Exceeded number of arguments in the Component");
135  return compOp.getArgument(index);
136 }
137 
138 Type convIndexType(OpBuilder &builder, Type type) {
139  if (type.isIndex())
140  return builder.getI32Type();
141  return type;
142 }
143 
145  calyx::GroupOp groupOp,
146  calyx::ComponentOp componentOp,
147  calyx::RegisterOp &reg,
148  Value inputValue) {
149  mlir::IRRewriter::InsertionGuard guard(builder);
150  auto loc = inputValue.getLoc();
151  builder.setInsertionPointToEnd(groupOp.getBodyBlock());
152  builder.create<calyx::AssignOp>(loc, reg.getIn(), inputValue);
153  builder.create<calyx::AssignOp>(
154  loc, reg.getWriteEn(), createConstant(loc, builder, componentOp, 1, 1));
155  builder.create<calyx::GroupDoneOp>(loc, reg.getDone());
156 }
157 
158 //===----------------------------------------------------------------------===//
159 // MemoryInterface
160 //===----------------------------------------------------------------------===//
161 
162 MemoryInterface::MemoryInterface() = default;
163 MemoryInterface::MemoryInterface(const MemoryPortsImpl &ports) : impl(ports) {}
164 MemoryInterface::MemoryInterface(calyx::MemoryOp memOp) : impl(memOp) {}
165 MemoryInterface::MemoryInterface(calyx::SeqMemoryOp memOp) : impl(memOp) {}
166 
168  auto readData = readDataOpt();
169  assert(readData.has_value() && "Memory does not have readData");
170  return readData.value();
171 }
172 
174  auto readEn = readEnOpt();
175  assert(readEn.has_value() && "Memory does not have readEn");
176  return readEn.value();
177 }
178 
180  auto readDone = readDoneOpt();
181  assert(readDone.has_value() && "Memory does not have readDone");
182  return readDone.value();
183 }
184 
186  auto writeData = writeDataOpt();
187  assert(writeData.has_value() && "Memory does not have writeData");
188  return writeData.value();
189 }
190 
192  auto writeEn = writeEnOpt();
193  assert(writeEn.has_value() && "Memory does not have writeEn");
194  return writeEn.value();
195 }
196 
198  auto writeDone = writeDoneOpt();
199  assert(writeDone.has_value() && "Memory doe snot have writeDone");
200  return writeDone.value();
201 }
202 
203 std::optional<Value> MemoryInterface::readDataOpt() {
204  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
205  return memOp->readData();
206  }
207 
208  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
209  return memOp->readData();
210  }
211  return std::get<MemoryPortsImpl>(impl).readData;
212 }
213 
214 std::optional<Value> MemoryInterface::readEnOpt() {
215  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
216  return std::nullopt;
217  }
218 
219  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
220  return memOp->readEn();
221  }
222  return std::get<MemoryPortsImpl>(impl).readEn;
223 }
224 
225 std::optional<Value> MemoryInterface::readDoneOpt() {
226  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
227  return std::nullopt;
228  }
229 
230  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
231  return memOp->readDone();
232  }
233  return std::get<MemoryPortsImpl>(impl).readDone;
234 }
235 std::optional<Value> MemoryInterface::writeDataOpt() {
236  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
237  return memOp->writeData();
238  }
239 
240  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
241  return memOp->writeData();
242  }
243  return std::get<MemoryPortsImpl>(impl).writeData;
244 }
245 
246 std::optional<Value> MemoryInterface::writeEnOpt() {
247  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
248  return memOp->writeEn();
249  }
250 
251  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
252  return memOp->writeEn();
253  }
254  return std::get<MemoryPortsImpl>(impl).writeEn;
255 }
256 
257 std::optional<Value> MemoryInterface::writeDoneOpt() {
258  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
259  return memOp->done();
260  }
261 
262  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
263  return memOp->writeDone();
264  }
265  return std::get<MemoryPortsImpl>(impl).writeDone;
266 }
267 
269  if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
270  return memOp->addrPorts();
271  }
272 
273  if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
274  return memOp->addrPorts();
275  }
276  return std::get<MemoryPortsImpl>(impl).addrPorts;
277 }
278 
279 //===----------------------------------------------------------------------===//
280 // BasicLoopInterface
281 //===----------------------------------------------------------------------===//
282 
284 
285 //===----------------------------------------------------------------------===//
286 // ComponentLoweringStateInterface
287 //===----------------------------------------------------------------------===//
288 
290  calyx::ComponentOp component)
291  : component(component) {}
292 
294 
296  return component;
297 }
298 
300  calyx::RegisterOp reg,
301  unsigned idx) {
302  assert(blockArgRegs[block].count(idx) == 0);
303  assert(idx < block->getArguments().size());
304  blockArgRegs[block][idx] = reg;
305 }
306 
307 const DenseMap<unsigned, calyx::RegisterOp> &
309  return blockArgRegs[block];
310 }
311 
313  calyx::GroupOp grp) {
314  blockArgGroups[from][to].push_back(grp);
315 }
316 
317 ArrayRef<calyx::GroupOp>
319  return blockArgGroups[from][to];
320 }
321 
322 std::string ComponentLoweringStateInterface::getUniqueName(StringRef prefix) {
323  std::string prefixStr = prefix.str();
324  unsigned idx = prefixIdMap[prefixStr];
325  ++prefixIdMap[prefixStr];
326  return (prefix + "_" + std::to_string(idx)).str();
327 }
328 
330  auto it = opNames.find(op);
331  assert(it != opNames.end() && "A unique name should have been set for op");
332  return it->second;
333 }
334 
336  StringRef prefix) {
337  assert(opNames.find(op) == opNames.end() &&
338  "A unique name was already set for op");
339  opNames[op] = getUniqueName(prefix);
340 }
341 
343  Value v, calyx::GroupInterface group) {
344  valueGroupAssigns[v] = group;
345 }
346 
348  unsigned idx) {
349  assert(returnRegs.count(idx) == 0 &&
350  "A register was already registered for this index");
351  returnRegs[idx] = reg;
352 }
353 
354 calyx::RegisterOp ComponentLoweringStateInterface::getReturnReg(unsigned idx) {
355  assert(returnRegs.count(idx) && "No register registered for index!");
356  return returnRegs[idx];
357 }
358 
360  Value memref, const calyx::MemoryInterface &memoryInterface) {
361  assert(memref.getType().isa<MemRefType>());
362  assert(memories.find(memref) == memories.end() &&
363  "Memory already registered for memref");
364  memories[memref] = memoryInterface;
365 }
366 
369  assert(memref.getType().isa<MemRefType>());
370  auto it = memories.find(memref);
371  assert(it != memories.end() && "No memory registered for memref");
372  return it->second;
373 }
374 
375 std::optional<calyx::MemoryInterface>
377  for (auto &memIf : memories) {
378  auto &mem = memIf.getSecond();
379  if (mem.writeEn() == v || mem.writeData() == v ||
380  llvm::any_of(mem.addrPorts(), [=](Value port) { return port == v; }))
381  return {mem};
382  }
383  return {};
384 }
385 
387  const DenseMap<unsigned, unsigned> &mapping) {
388  funcOpResultMapping = mapping;
389 }
390 
392  unsigned funcReturnIdx) {
393  auto it = funcOpResultMapping.find(funcReturnIdx);
394  assert(it != funcOpResultMapping.end() &&
395  "No component return port index recorded for the requested function "
396  "return index");
397  return it->second;
398 }
399 
400 InstanceOp ComponentLoweringStateInterface::getInstance(StringRef calleeName) {
401  return instanceMap[calleeName];
402 }
403 
405  InstanceOp instanceOp) {
406  instanceMap[calleeName] = instanceOp;
407 }
408 
409 //===----------------------------------------------------------------------===//
410 // CalyxLoweringState
411 //===----------------------------------------------------------------------===//
412 
414  StringRef topLevelFunction)
415  : topLevelFunction(topLevelFunction), module(module) {}
416 
418  assert(module.getOperation() != nullptr);
419  return module;
420 }
421 
423  return topLevelFunction;
424 }
425 
426 std::string CalyxLoweringState::blockName(Block *b) {
427  std::string blockName = irName(*b);
428  blockName.erase(std::remove(blockName.begin(), blockName.end(), '^'),
429  blockName.end());
430  return blockName;
431 }
432 
433 //===----------------------------------------------------------------------===//
434 // ModuleOpConversion
435 //===----------------------------------------------------------------------===//
436 
437 /// Helper to update the top-level ModuleOp to set the entrypoing function.
438 LogicalResult applyModuleOpConversion(mlir::ModuleOp moduleOp,
439  StringRef topLevelFunction) {
440 
441  if (moduleOp->hasAttr("calyx.entrypoint"))
442  return failure();
443 
444  moduleOp->setAttr("calyx.entrypoint",
445  StringAttr::get(moduleOp.getContext(), topLevelFunction));
446  return success();
447 }
448 
449 //===----------------------------------------------------------------------===//
450 // Partial lowering patterns
451 //===----------------------------------------------------------------------===//
452 
454  MLIRContext *context, LogicalResult &resRef,
455  PatternApplicationState &patternState,
456  DenseMap<mlir::func::FuncOp, calyx::ComponentOp> &map,
458  : PartialLoweringPattern(context, resRef, patternState),
459  functionMapping(map), calyxLoweringState(state) {}
460 
461 LogicalResult
463  PatternRewriter &rewriter) const {
464  // Initialize the component op references if a calyx::ComponentOp has been
465  // created for the matched funcOp.
466  if (auto it = functionMapping.find(funcOp); it != functionMapping.end()) {
467  componentOp = it->second;
470  componentOp);
471  }
472 
473  return partiallyLowerFuncToComp(funcOp, rewriter);
474 }
475 
476 calyx::ComponentOp FuncOpPartialLoweringPattern::getComponent() const {
478  "Component operation should be set during pattern construction");
479  return componentOp;
480 }
481 
483  return calyxLoweringState;
484 }
485 
486 //===----------------------------------------------------------------------===//
487 // ConvertIndexTypes
488 //===----------------------------------------------------------------------===//
489 
490 LogicalResult
492  PatternRewriter &rewriter) const {
493  funcOp.walk([&](Block *block) {
494  for (Value arg : block->getArguments())
495  arg.setType(calyx::convIndexType(rewriter, arg.getType()));
496  });
497 
498  funcOp.walk([&](Operation *op) {
499  for (Value result : op->getResults()) {
500  Type resType = result.getType();
501  if (!resType.isIndex())
502  continue;
503 
504  result.setType(calyx::convIndexType(rewriter, resType));
505  auto constant = dyn_cast<mlir::arith::ConstantOp>(op);
506  if (!constant)
507  continue;
508 
509  APInt value;
510  calyx::matchConstantOp(constant, value);
511  rewriter.setInsertionPoint(constant);
512  rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>(
513  constant, rewriter.getI32IntegerAttr(value.getSExtValue()));
514  }
515  });
516  return success();
517 }
518 
519 //===----------------------------------------------------------------------===//
520 // NonTerminatingGroupDonePattern
521 //===----------------------------------------------------------------------===//
522 
523 LogicalResult
524 NonTerminatingGroupDonePattern::matchAndRewrite(calyx::GroupDoneOp groupDoneOp,
525  PatternRewriter &) const {
526  Block *block = groupDoneOp->getBlock();
527  if (&block->back() == groupDoneOp)
528  return failure();
529 
530  groupDoneOp->moveBefore(groupDoneOp->getBlock(),
531  groupDoneOp->getBlock()->end());
532  return success();
533 }
534 
535 //===----------------------------------------------------------------------===//
536 // MultipleGroupDonePattern
537 //===----------------------------------------------------------------------===//
538 
539 LogicalResult
541  PatternRewriter &rewriter) const {
542  auto groupDoneOps = SmallVector<calyx::GroupDoneOp>(
543  groupOp.getBodyBlock()->getOps<calyx::GroupDoneOp>());
544 
545  if (groupDoneOps.size() <= 1)
546  return failure();
547 
548  /// 'and' all of the calyx::GroupDoneOp's.
549  rewriter.setInsertionPointToEnd(groupDoneOps[0]->getBlock());
550  SmallVector<Value> doneOpSrcs;
551  llvm::transform(groupDoneOps, std::back_inserter(doneOpSrcs),
552  [](calyx::GroupDoneOp op) { return op.getSrc(); });
553  Value allDone = rewriter.create<comb::AndOp>(groupDoneOps.front().getLoc(),
554  doneOpSrcs, false);
555 
556  /// Create a group done op with the complex expression as a guard.
557  rewriter.create<calyx::GroupDoneOp>(
558  groupOp.getLoc(),
559  rewriter.create<hw::ConstantOp>(groupOp.getLoc(), APInt(1, 1)), allDone);
560  for (auto groupDoneOp : groupDoneOps)
561  rewriter.eraseOp(groupDoneOp);
562 
563  return success();
564 }
565 
566 //===----------------------------------------------------------------------===//
567 // EliminateUnusedCombGroups
568 //===----------------------------------------------------------------------===//
569 
570 LogicalResult
571 EliminateUnusedCombGroups::matchAndRewrite(calyx::CombGroupOp combGroupOp,
572  PatternRewriter &rewriter) const {
573  auto control =
574  combGroupOp->getParentOfType<calyx::ComponentOp>().getControlOp();
575  if (!SymbolTable::symbolKnownUseEmpty(combGroupOp.getSymNameAttr(), control))
576  return failure();
577 
578  rewriter.eraseOp(combGroupOp);
579  return success();
580 }
581 
582 //===----------------------------------------------------------------------===//
583 // InlineCombGroups
584 //===----------------------------------------------------------------------===//
585 
586 InlineCombGroups::InlineCombGroups(MLIRContext *context, LogicalResult &resRef,
587  PatternApplicationState &patternState,
589  : PartialLoweringPattern(context, resRef, patternState), cls(cls) {}
590 
591 LogicalResult
592 InlineCombGroups::partiallyLower(calyx::GroupInterface originGroup,
593  PatternRewriter &rewriter) const {
594  auto component = originGroup->getParentOfType<calyx::ComponentOp>();
595  ComponentLoweringStateInterface *state = cls.getState(component);
596 
597  // Filter groups which are not part of the control schedule.
598  if (SymbolTable::symbolKnownUseEmpty(originGroup.symName(),
599  component.getControlOp()))
600  return success();
601 
602  // Maintain a set of the groups which we've inlined so far. The group
603  // itself is implicitly inlined.
604  llvm::SmallSetVector<Operation *, 8> inlinedGroups;
605  inlinedGroups.insert(originGroup);
606 
607  // Starting from the matched originGroup, we traverse use-def chains of
608  // combinational logic, and inline assignments from the defining
609  // combinational groups.
610  recurseInlineCombGroups(rewriter, *state, inlinedGroups, originGroup,
611  originGroup,
612  /*doInline=*/false);
613  return success();
614 }
615 
617  PatternRewriter &rewriter, ComponentLoweringStateInterface &state,
618  llvm::SmallSetVector<Operation *, 8> &inlinedGroups,
619  calyx::GroupInterface originGroup, calyx::GroupInterface recGroup,
620  bool doInline) const {
621  inlinedGroups.insert(recGroup);
622  for (auto assignOp : recGroup.getBody()->getOps<calyx::AssignOp>()) {
623  if (doInline) {
624  /// Inline the assignment into the originGroup.
625  auto *clonedAssignOp = rewriter.clone(*assignOp.getOperation());
626  clonedAssignOp->moveBefore(originGroup.getBody(),
627  originGroup.getBody()->end());
628  }
629  Value src = assignOp.getSrc();
630 
631  // Things which stop recursive inlining (or in other words, what
632  // breaks combinational paths).
633  // - Component inputs
634  // - Register and memory reads
635  // - Constant ops (constant ops are not evaluated by any group)
636  // - Multiplication pipelines are sequential.
637  // - 'While' return values (these are registers, however, 'while'
638  // return values have at the current point of conversion not yet
639  // been rewritten to their register outputs, see comment in
640  // LateSSAReplacement)
641  if (src.isa<BlockArgument>() ||
642  isa<calyx::RegisterOp, calyx::MemoryOp, calyx::SeqMemoryOp,
643  hw::ConstantOp, mlir::arith::ConstantOp, calyx::MultPipeLibOp,
644  calyx::DivUPipeLibOp, calyx::DivSPipeLibOp, calyx::RemSPipeLibOp,
645  calyx::RemUPipeLibOp, mlir::scf::WhileOp, calyx::InstanceOp>(
646  src.getDefiningOp()))
647  continue;
648 
649  auto srcCombGroup = dyn_cast<calyx::CombGroupOp>(
650  state.getEvaluatingGroup(src).getOperation());
651  if (!srcCombGroup)
652  continue;
653  if (inlinedGroups.count(srcCombGroup))
654  continue;
655 
656  recurseInlineCombGroups(rewriter, state, inlinedGroups, originGroup,
657  srcCombGroup, /*doInline=*/true);
658  }
659 }
660 
661 //===----------------------------------------------------------------------===//
662 // RewriteMemoryAccesses
663 //===----------------------------------------------------------------------===//
664 
665 LogicalResult
666 RewriteMemoryAccesses::partiallyLower(calyx::AssignOp assignOp,
667  PatternRewriter &rewriter) const {
668  auto *state = cls.getState(assignOp->getParentOfType<calyx::ComponentOp>());
669 
670  Value dest = assignOp.getDest();
671  if (!state->isInputPortOfMemory(dest))
672  return success();
673 
674  Value src = assignOp.getSrc();
675  unsigned srcBits = src.getType().getIntOrFloatBitWidth();
676  unsigned dstBits = dest.getType().getIntOrFloatBitWidth();
677  if (srcBits == dstBits)
678  return success();
679 
680  SmallVector<Type> types = {
681  rewriter.getIntegerType(srcBits),
682  rewriter.getIntegerType(dstBits),
683  };
684  mlir::Location loc = assignOp.getLoc();
685  Operation *newOp;
686  if (srcBits > dstBits)
687  newOp =
688  state->getNewLibraryOpInstance<calyx::SliceLibOp>(rewriter, loc, types);
689  else
690  newOp =
691  state->getNewLibraryOpInstance<calyx::PadLibOp>(rewriter, loc, types);
692 
693  rewriter.setInsertionPoint(assignOp->getBlock(),
694  assignOp->getBlock()->begin());
695  rewriter.create<calyx::AssignOp>(assignOp->getLoc(), newOp->getResult(0),
696  src);
697  assignOp.setOperand(1, newOp->getResult(1));
698 
699  return success();
700 }
701 
702 //===----------------------------------------------------------------------===//
703 // BuildBasicBlockRegs
704 //===----------------------------------------------------------------------===//
705 
706 LogicalResult
708  PatternRewriter &rewriter) const {
709  funcOp.walk([&](Block *block) {
710  /// Do not register component input values.
711  if (block == &block->getParent()->front())
712  return;
713 
714  for (auto arg : enumerate(block->getArguments())) {
715  Type argType = arg.value().getType();
716  assert(argType.isa<IntegerType>() && "unsupported block argument type");
717  unsigned width = argType.getIntOrFloatBitWidth();
718  std::string index = std::to_string(arg.index());
719  std::string name = loweringState().blockName(block) + "_arg" + index;
720  auto reg = createRegister(arg.value().getLoc(), rewriter, getComponent(),
721  width, name);
722  getState().addBlockArgReg(block, reg, arg.index());
723  arg.value().replaceAllUsesWith(reg.getOut());
724  }
725  });
726  return success();
727 }
728 
729 //===----------------------------------------------------------------------===//
730 // BuildReturnRegs
731 //===----------------------------------------------------------------------===//
732 
733 LogicalResult
735  PatternRewriter &rewriter) const {
736 
737  for (auto argType : enumerate(funcOp.getResultTypes())) {
738  auto convArgType = calyx::convIndexType(rewriter, argType.value());
739  assert(convArgType.isa<IntegerType>() && "unsupported return type");
740  unsigned width = convArgType.getIntOrFloatBitWidth();
741  std::string name = "ret_arg" + std::to_string(argType.index());
742  auto reg =
743  createRegister(funcOp.getLoc(), rewriter, getComponent(), width, name);
744  getState().addReturnReg(reg, argType.index());
745 
746  rewriter.setInsertionPointToStart(
747  getComponent().getWiresOp().getBodyBlock());
748  rewriter.create<calyx::AssignOp>(
749  funcOp->getLoc(),
751  getComponent(), getState().getFuncOpResultMapping(argType.index())),
752  reg.getOut());
753  }
754  return success();
755 }
756 
757 //===----------------------------------------------------------------------===//
758 // BuildCallInstance
759 //===----------------------------------------------------------------------===//
760 
761 LogicalResult
763  PatternRewriter &rewriter) const {
764  funcOp.walk([&](mlir::func::CallOp callOp) {
765  ComponentOp componentOp = getCallComponent(callOp);
766  SmallVector<Type, 8> resultTypes;
767  for (auto type : componentOp.getArgumentTypes())
768  resultTypes.push_back(type);
769  for (auto type : componentOp.getResultTypes())
770  resultTypes.push_back(type);
771  std::string instanceName = getInstanceName(callOp);
772 
773  // Determines if an instance needs to be created. If the same function was
774  // called by CallOp before, it doesn't need to be created, if not, the
775  // instance is created.
776  if (!getState().getInstance(instanceName)) {
777  InstanceOp instanceOp =
778  createInstance(callOp.getLoc(), rewriter, getComponent(), resultTypes,
779  instanceName, componentOp.getName());
780  getState().addInstance(instanceName, instanceOp);
781  hw::ConstantOp constantOp =
782  createConstant(callOp.getLoc(), rewriter, getComponent(), 1, 1);
783  OpBuilder::InsertionGuard g(rewriter);
784  rewriter.setInsertionPointToStart(
785  getComponent().getWiresOp().getBodyBlock());
786 
787  // Creates the group that initializes the instance.
788  calyx::GroupOp groupOp = rewriter.create<calyx::GroupOp>(
789  callOp.getLoc(), "init_" + instanceName);
790  rewriter.setInsertionPointToStart(groupOp.getBodyBlock());
791  auto portInfos = instanceOp.getReferencedComponent().getPortInfo();
792  auto results = instanceOp.getResults();
793  for (const auto &[portInfo, result] : llvm::zip(portInfos, results)) {
794  if (portInfo.hasAttribute("go") || portInfo.hasAttribute("reset"))
795  rewriter.create<calyx::AssignOp>(callOp.getLoc(), result, constantOp);
796  else if (portInfo.hasAttribute("done"))
797  rewriter.create<calyx::GroupDoneOp>(callOp.getLoc(), result);
798  }
799  }
800  WalkResult::advance();
801  });
802  return success();
803 }
804 
805 ComponentOp
806 BuildCallInstance::getCallComponent(mlir::func::CallOp callOp) const {
807  std::string callee = "func_" + callOp.getCallee().str();
808  for (auto [funcOp, componentOp] : functionMapping) {
809  if (funcOp.getSymName() == callee)
810  return componentOp;
811  }
812  return nullptr;
813 }
814 
815 } // namespace calyx
816 } // namespace circt
lowerAnnotationsNoRefTypePorts FirtoolPreserveValuesMode value
Definition: Firtool.cpp:95
assert(baseType &&"element must be base type")
int32_t width
Definition: FIRRTL.cpp:27
@ Input
Definition: HW.h:32
@ Output
Definition: HW.h:32
Builder builder
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
ComponentOp getCallComponent(mlir::func::CallOp callOp) const
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
An interface for conversion passes that lower Calyx programs.
std::string irName(ValueOrBlock &v)
Returns a meaningful name for a value within the program scope.
mlir::ModuleOp getModule()
Returns the current program.
CalyxLoweringState(mlir::ModuleOp module, StringRef topLevelFunction)
T * getState(calyx::ComponentOp op)
Returns the component lowering state associated with op.
std::string blockName(Block *b)
Returns a meaningful name for a block within the program scope (removes the ^ prefix from block names...
StringRef getTopLevelFunction() const
Returns the name of the top-level function in the source program.
StringRef topLevelFunction
The name of this top-level function.
mlir::ModuleOp module
The program associated with this state.
const DenseMap< unsigned, calyx::RegisterOp > & getBlockArgRegs(Block *block)
Return a mapping of block argument indices to block argument registers.
void setFuncOpResultMapping(const DenseMap< unsigned, unsigned > &mapping)
Assign a mapping between the source funcOp result indices and the corresponding output port indices o...
DenseMap< Value, calyx::MemoryInterface > memories
A mapping from memref's to their corresponding Calyx memory interface.
TGroupOp getEvaluatingGroup(Value v)
Return the group which evaluates the value v.
void addReturnReg(calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th return value register.
calyx::MemoryInterface getMemoryInterface(Value memref)
Returns the memory interface registered for the given memref.
llvm::StringMap< calyx::InstanceOp > instanceMap
A mapping between the callee and the instance.
DenseMap< Block *, DenseMap< Block *, SmallVector< calyx::GroupOp > > > blockArgGroups
Block arg groups is a list of groups that should be sequentially executed when passing control from t...
void setUniqueName(Operation *op, StringRef prefix)
Registers a unique name for a given operation using a provided prefix.
calyx::RegisterOp getReturnReg(unsigned idx)
Returns the idx'th return value register.
std::string getUniqueName(StringRef prefix)
Returns a unique name within compOp with the provided prefix.
calyx::ComponentOp component
The component which this lowering state is associated to.
void registerMemoryInterface(Value memref, const calyx::MemoryInterface &memoryInterface)
Registers a memory interface as being associated with a memory identified by 'memref'.
calyx::ComponentOp getComponentOp()
Returns the calyx::ComponentOp associated with this lowering state.
void addBlockArgReg(Block *block, calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th argument register for block.
InstanceOp getInstance(StringRef calleeName)
The instance is obtained from the name of the callee.
unsigned getFuncOpResultMapping(unsigned funcReturnIdx)
Get the output port index of this component for which the funcReturnIdx of the original function maps...
DenseMap< unsigned, unsigned > funcOpResultMapping
A mapping between the source funcOp result indices and the corresponding output port indices of this ...
void addInstance(StringRef calleeName, InstanceOp instanceOp)
Put the name of the callee and the instance of the call into map.
DenseMap< Value, calyx::GroupInterface > valueGroupAssigns
A mapping between SSA values and the groups which assign them.
std::map< std::string, unsigned > prefixIdMap
A mapping of string prefixes and the current uniqueness counter for that prefix.
ArrayRef< calyx::GroupOp > getBlockArgGroups(Block *from, Block *to)
Returns a list of groups to be evaluated to perform the block argument register assignments when tran...
ComponentLoweringStateInterface(calyx::ComponentOp component)
void registerEvaluatingGroup(Value v, calyx::GroupInterface group)
Register value v as being evaluated when scheduling group.
std::optional< calyx::MemoryInterface > isInputPortOfMemory(Value v)
If v is an input to any memory registered within this component, returns the memory.
void addBlockArgGroup(Block *from, Block *to, calyx::GroupOp grp)
Register 'grp' as a group which performs block argument register transfer when transitioning from bas...
DenseMap< Block *, DenseMap< unsigned, calyx::RegisterOp > > blockArgRegs
A mapping from blocks to block argument registers.
DenseMap< unsigned, calyx::RegisterOp > returnRegs
A mapping from return value indexes to return value registers.
std::map< Operation *, std::string > opNames
A mapping from Operations and previously assigned unique name of the op.
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
calyx::ComponentOp getComponent() const
Returns the component operation associated with the currently executing partial lowering.
ComponentLoweringStateInterface * componentLoweringState
DenseMap< mlir::func::FuncOp, calyx::ComponentOp > & functionMapping
virtual LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const =0
CalyxLoweringState & loweringState() const
Return the calyx lowering state for this pattern.
FuncOpPartialLoweringPattern(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, DenseMap< mlir::func::FuncOp, calyx::ComponentOp > &map, calyx::CalyxLoweringState &state)
LogicalResult partiallyLower(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override final
Entry point to initialize the state of this class and conduct the partial lowering.
LogicalResult partiallyLower(calyx::GroupInterface originGroup, PatternRewriter &rewriter) const override
InlineCombGroups(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, calyx::CalyxLoweringState &pls)
void recurseInlineCombGroups(PatternRewriter &rewriter, ComponentLoweringStateInterface &state, llvm::SmallSetVector< Operation *, 8 > &inlinedGroups, calyx::GroupInterface originGroup, calyx::GroupInterface recGroup, bool doInline) const
calyx::CalyxLoweringState & cls
Base class for partial lowering passes.
calyx::CalyxLoweringState & cls
LogicalResult partiallyLower(calyx::AssignOp assignOp, PatternRewriter &rewriter) const override
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
void appendPortsForExternalMemref(PatternRewriter &rewriter, StringRef memName, Value memref, unsigned memoryID, SmallVectorImpl< calyx::PortInfo > &inPorts, SmallVectorImpl< calyx::PortInfo > &outPorts)
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...
bool isControlLeafNode(Operation *op)
Type convIndexType(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)
Value getComponentOutput(calyx::ComponentOp compOp, unsigned outPortIdx)
bool singleLoadFromMemory(Value memoryReference)
calyx::InstanceOp createInstance(Location loc, OpBuilder &builder, ComponentOp component, SmallVectorImpl< Type > &resultTypes, StringRef instanceName, StringRef componentName)
A helper function to create calyx.instance operation.
std::string getInstanceName(mlir::func::CallOp callOp)
A helper function to get the instance name.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:20
LogicalResult matchAndRewrite(calyx::CombGroupOp combGroupOp, PatternRewriter &rewriter) const override
std::variant< calyx::MemoryOp, calyx::SeqMemoryOp, MemoryPortsImpl > impl
std::optional< Value > writeDataOpt()
std::optional< Value > readEnOpt()
std::optional< Value > readDataOpt()
std::optional< Value > readDoneOpt()
std::optional< Value > writeDoneOpt()
std::optional< Value > writeEnOpt()
LogicalResult matchAndRewrite(calyx::GroupOp groupOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(calyx::GroupDoneOp groupDoneOp, PatternRewriter &) const override
This holds information about the port for either a Component or Cell.
Definition: CalyxOps.h:78