CIRCT 24.0.0git
Loading...
Searching...
No Matches
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
24using namespace mlir;
25using namespace mlir::arith;
26
27namespace circt {
28namespace calyx {
29
30template <typename OpTy>
31static LogicalResult deduplicateParallelOperation(OpTy parOp,
32 PatternRewriter &rewriter) {
33 auto *body = parOp.getBodyBlock();
34 if (body->getOperations().size() < 2)
35 return failure();
36
37 LogicalResult result = LogicalResult::failure();
38 SetVector<StringRef> members;
39 for (auto &op : make_early_inc_range(*body)) {
40 auto enableOp = dyn_cast<EnableOp>(&op);
41 if (enableOp == nullptr)
42 continue;
43 bool inserted = members.insert(enableOp.getGroupName());
44 if (!inserted) {
45 rewriter.eraseOp(enableOp);
46 result = LogicalResult::success();
47 }
48 }
49 return result;
50}
51
52void appendPortsForExternalMemref(PatternRewriter &rewriter, StringRef memName,
53 Value memref, unsigned memoryID,
54 SmallVectorImpl<calyx::PortInfo> &inPorts,
55 SmallVectorImpl<calyx::PortInfo> &outPorts) {
56 MemRefType memrefType = cast<MemRefType>(memref.getType());
57
58 // Ports constituting a memory interface are added a set of attributes under
59 // a "mem : {...}" dictionary. These attributes allows for deducing which
60 // top-level I/O signals constitutes a unique memory interface.
61 auto getMemoryInterfaceAttr = [&](StringRef tag,
62 std::optional<unsigned> addrIdx = {}) {
63 auto attrs = SmallVector<NamedAttribute>{
64 // "id" denotes a unique memory interface.
65 rewriter.getNamedAttr("id", rewriter.getI32IntegerAttr(memoryID)),
66 // "tag" denotes the function of this signal.
67 rewriter.getNamedAttr("tag", rewriter.getStringAttr(tag))};
68 if (addrIdx.has_value())
69 // "addr_idx" denotes the address index of this signal, for
70 // multi-dimensional memory interfaces.
71 attrs.push_back(rewriter.getNamedAttr(
72 "addr_idx", rewriter.getI32IntegerAttr(*addrIdx)));
73
74 return rewriter.getNamedAttr("mem", rewriter.getDictionaryAttr(attrs));
75 };
76
77 // Read data
78 inPorts.push_back(calyx::PortInfo{
79 rewriter.getStringAttr(memName + "_read_data"),
80 memrefType.getElementType(), calyx::Direction::Input,
81 DictionaryAttr::get(rewriter.getContext(),
82 {getMemoryInterfaceAttr("read_data")})});
83
84 // Done
85 inPorts.push_back(
86 calyx::PortInfo{rewriter.getStringAttr(memName + "_done"),
87 rewriter.getI1Type(), calyx::Direction::Input,
88 DictionaryAttr::get(rewriter.getContext(),
89 {getMemoryInterfaceAttr("done")})});
90
91 // Write data
92 outPorts.push_back(calyx::PortInfo{
93 rewriter.getStringAttr(memName + "_write_data"),
94 memrefType.getElementType(), calyx::Direction::Output,
95 DictionaryAttr::get(rewriter.getContext(),
96 {getMemoryInterfaceAttr("write_data")})});
97
98 // Memory address outputs
99 for (auto dim : enumerate(memrefType.getShape())) {
100 outPorts.push_back(calyx::PortInfo{
101 rewriter.getStringAttr(memName + "_addr" + std::to_string(dim.index())),
102 rewriter.getIntegerType(calyx::handleZeroWidth(dim.value())),
104 DictionaryAttr::get(rewriter.getContext(),
105 {getMemoryInterfaceAttr("addr", dim.index())})});
106 }
107
108 // Write enable
109 outPorts.push_back(calyx::PortInfo{
110 rewriter.getStringAttr(memName + "_write_en"), rewriter.getI1Type(),
112 DictionaryAttr::get(rewriter.getContext(),
113 {getMemoryInterfaceAttr("write_en")})});
114}
115
116WalkResult
117getCiderSourceLocationMetadata(calyx::ComponentOp component,
118 SmallVectorImpl<Attribute> &sourceLocations) {
119 Builder builder(component->getContext());
120 return component.getControlOp().walk([&](Operation *op) {
122 return WalkResult::advance();
123
124 std::string sourceLocation;
125 llvm::raw_string_ostream os(sourceLocation);
126 op->getLoc()->print(os);
127 int64_t position = sourceLocations.size();
128 sourceLocations.push_back(
129 StringAttr::get(op->getContext(), sourceLocation));
130
131 op->setAttr("pos", builder.getI64IntegerAttr(position));
132 return WalkResult::advance();
133 });
134}
135
136bool matchConstantOp(Operation *op, APInt &value) {
137 return mlir::detail::constant_int_value_binder(&value).match(op);
138}
139
140bool singleLoadFromMemory(Value memoryReference) {
141 return llvm::count_if(memoryReference.getUses(), [](OpOperand &user) {
142 return isa<mlir::memref::LoadOp>(user.getOwner());
143 }) <= 1;
144}
145
146bool noStoresToMemory(Value memoryReference) {
147 return llvm::none_of(memoryReference.getUses(), [](OpOperand &user) {
148 return isa<mlir::memref::StoreOp>(user.getOwner());
149 });
150}
151
152Value getComponentOutput(calyx::ComponentOp compOp, unsigned outPortIdx) {
153 size_t index = compOp.getInputPortInfo().size() + outPortIdx;
154 assert(index < compOp.getNumArguments() &&
155 "Exceeded number of arguments in the Component");
156 return compOp.getArgument(index);
157}
158
159Type normalizeType(OpBuilder &builder, Type type) {
160 if (type.isIndex())
161 return builder.getI32Type();
162 if (type.isIntOrFloat())
163 return toBitVector(type);
164 return type;
165}
166
167void buildAssignmentsForRegisterWrite(OpBuilder &builder,
168 calyx::GroupOp groupOp,
169 calyx::ComponentOp componentOp,
170 calyx::RegisterOp &reg,
171 Value inputValue) {
172 mlir::IRRewriter::InsertionGuard guard(builder);
173 auto loc = inputValue.getLoc();
174 builder.setInsertionPointToEnd(groupOp.getBodyBlock());
175 calyx::AssignOp::create(builder, loc, reg.getIn(), inputValue);
176 calyx::AssignOp::create(builder, loc, reg.getWriteEn(),
177 createConstant(loc, builder, componentOp, 1, 1));
178 calyx::GroupDoneOp::create(builder, loc, reg.getDone());
179}
180
181//===----------------------------------------------------------------------===//
182// MemoryInterface
183//===----------------------------------------------------------------------===//
184
187 if (ports.writeEn.has_value() && ports.readOrContentEn.has_value()) {
188 assert(ports.isContentEn.value());
189 }
190}
191MemoryInterface::MemoryInterface(calyx::MemoryOp memOp) : impl(memOp) {}
192MemoryInterface::MemoryInterface(calyx::SeqMemoryOp memOp) : impl(memOp) {}
193
195 auto readData = readDataOpt();
196 assert(readData.has_value() && "Memory does not have readData");
197 return readData.value();
198}
199
201 auto readEn = readEnOpt();
202 assert(readEn.has_value() && "Memory does not have readEn");
203 return readEn.value();
204}
205
207 auto contentEn = contentEnOpt();
208 assert(contentEn.has_value() && "Memory does not have readEn");
209 return contentEn.value();
210}
211
213 auto writeData = writeDataOpt();
214 assert(writeData.has_value() && "Memory does not have writeData");
215 return writeData.value();
216}
217
219 auto writeEn = writeEnOpt();
220 assert(writeEn.has_value() && "Memory does not have writeEn");
221 return writeEn.value();
222}
223
225 auto done = doneOpt();
226 assert(done.has_value() && "Memory does not have done");
227 return done.value();
228}
229
231 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
232 return memOp->instanceName().str();
233 }
234
235 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
236 return memOp->instanceName().str();
237 }
238 return std::get<MemoryPortsImpl>(impl).memName;
239}
240
241std::optional<Value> MemoryInterface::readDataOpt() {
242 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
243 return memOp->readData();
244 }
245
246 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
247 return memOp->readData();
248 }
249 return std::get<MemoryPortsImpl>(impl).readData;
250}
251
252std::optional<Value> MemoryInterface::readEnOpt() {
253 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
254 return std::nullopt;
255 }
256
257 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
258 return std::nullopt;
259 }
260
261 if (std::get<MemoryPortsImpl>(impl).readOrContentEn.has_value()) {
262 assert(std::get<MemoryPortsImpl>(impl).isContentEn.has_value());
263 assert(!std::get<MemoryPortsImpl>(impl).isContentEn.value());
264 }
265 return std::get<MemoryPortsImpl>(impl).readOrContentEn;
266}
267
268std::optional<Value> MemoryInterface::contentEnOpt() {
269 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
270 return std::nullopt;
271 }
272
273 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
274 return memOp->contentEn();
275 }
276
277 if (std::get<MemoryPortsImpl>(impl).readOrContentEn.has_value()) {
278 assert(std::get<MemoryPortsImpl>(impl).writeEn.has_value());
279 assert(std::get<MemoryPortsImpl>(impl).isContentEn.has_value());
280 assert(std::get<MemoryPortsImpl>(impl).isContentEn.value());
281 }
282 return std::get<MemoryPortsImpl>(impl).readOrContentEn;
283}
284
285std::optional<Value> MemoryInterface::writeDataOpt() {
286 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
287 return memOp->writeData();
288 }
289
290 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
291 return memOp->writeData();
292 }
293 return std::get<MemoryPortsImpl>(impl).writeData;
294}
295
296std::optional<Value> MemoryInterface::writeEnOpt() {
297 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
298 return memOp->writeEn();
299 }
300
301 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
302 return memOp->writeEn();
303 }
304 return std::get<MemoryPortsImpl>(impl).writeEn;
305}
306
307std::optional<Value> MemoryInterface::doneOpt() {
308 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
309 return memOp->done();
310 }
311
312 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
313 return memOp->done();
314 }
315 return std::get<MemoryPortsImpl>(impl).done;
316}
317
319 if (auto *memOp = std::get_if<calyx::MemoryOp>(&impl); memOp) {
320 return memOp->addrPorts();
321 }
322
323 if (auto *memOp = std::get_if<calyx::SeqMemoryOp>(&impl); memOp) {
324 return memOp->addrPorts();
325 }
326 return std::get<MemoryPortsImpl>(impl).addrPorts;
327}
328
329//===----------------------------------------------------------------------===//
330// BasicLoopInterface
331//===----------------------------------------------------------------------===//
332
334
335//===----------------------------------------------------------------------===//
336// ComponentLoweringStateInterface
337//===----------------------------------------------------------------------===//
338
342
344
346 return component;
347}
348
350 calyx::RegisterOp reg,
351 unsigned idx) {
352 assert(blockArgRegs[block].count(idx) == 0);
353 assert(idx < block->getArguments().size());
354 blockArgRegs[block][idx] = reg;
355}
356
357const DenseMap<unsigned, calyx::RegisterOp> &
361
363 calyx::GroupOp grp) {
364 blockArgGroups[from][to].push_back(grp);
365}
366
367ArrayRef<calyx::GroupOp>
369 return blockArgGroups[from][to];
370}
371
373 std::string prefixStr = prefix.str();
374 unsigned idx = prefixIdMap[prefixStr];
375 ++prefixIdMap[prefixStr];
376 return (prefix + "_" + std::to_string(idx)).str();
377}
378
380 auto it = opNames.find(op);
381 assert(it != opNames.end() && "A unique name should have been set for op");
382 return it->second;
383}
384
386 StringRef prefix) {
387 assert(opNames.find(op) == opNames.end() &&
388 "A unique name was already set for op");
389 opNames[op] = getUniqueName(prefix);
390}
391
393 Value v, calyx::GroupInterface group) {
394 valueGroupAssigns[v] = group;
395}
396
398 unsigned idx) {
399 assert(returnRegs.count(idx) == 0 &&
400 "A register was already registered for this index");
401 returnRegs[idx] = reg;
402}
403
404calyx::RegisterOp ComponentLoweringStateInterface::getReturnReg(unsigned idx) {
405 assert(returnRegs.count(idx) && "No register registered for index!");
406 return returnRegs[idx];
407}
408
410 Value memref, const calyx::MemoryInterface &memoryInterface) {
411 assert(isa<MemRefType>(memref.getType()));
412 assert(memories.find(memref) == memories.end() &&
413 "Memory already registered for memref");
414 memories[memref] = memoryInterface;
415}
416
419 assert(isa<MemRefType>(memref.getType()));
420 auto it = memories.find(memref);
421 assert(it != memories.end() && "No memory registered for memref");
422 return it->second;
423}
424
425std::optional<calyx::MemoryInterface>
427 for (auto &memIf : memories) {
428 auto &mem = memIf.getSecond();
429 if (mem.writeEn() == v || mem.writeData() == v ||
430 llvm::any_of(mem.addrPorts(), [=](Value port) { return port == v; }))
431 return {mem};
432 }
433 return {};
434}
435
437 const DenseMap<unsigned, unsigned> &mapping) {
438 funcOpResultMapping = mapping;
439}
440
442 unsigned funcReturnIdx) {
443 auto it = funcOpResultMapping.find(funcReturnIdx);
444 assert(it != funcOpResultMapping.end() &&
445 "No component return port index recorded for the requested function "
446 "return index");
447 return it->second;
448}
449
450InstanceOp ComponentLoweringStateInterface::getInstance(StringRef calleeName) {
451 return instanceMap[calleeName];
452}
453
455 InstanceOp instanceOp) {
456 instanceMap[calleeName] = instanceOp;
457}
458
460 return seqGuardCmpLibOps.contains(op);
461}
462
466
467//===----------------------------------------------------------------------===//
468// CalyxLoweringState
469//===----------------------------------------------------------------------===//
470
474
476 assert(module.getOperation() != nullptr);
477 return module;
478}
479
483
484std::string CalyxLoweringState::blockName(Block *b) {
485 std::string blockName = irName(*b);
486 blockName.erase(std::remove(blockName.begin(), blockName.end(), '^'),
487 blockName.end());
488 return blockName;
489}
490
491//===----------------------------------------------------------------------===//
492// ModuleOpConversion
493//===----------------------------------------------------------------------===//
494
495/// Helper to update the top-level ModuleOp to set the entrypoing function.
496LogicalResult applyModuleOpConversion(mlir::ModuleOp moduleOp,
497 StringRef topLevelFunction) {
498
499 if (moduleOp->hasAttr("calyx.entrypoint"))
500 return failure();
501
502 moduleOp->setAttr("calyx.entrypoint",
503 StringAttr::get(moduleOp.getContext(), topLevelFunction));
504 return success();
505}
506
507//===----------------------------------------------------------------------===//
508// Partial lowering patterns
509//===----------------------------------------------------------------------===//
510
512 MLIRContext *context, LogicalResult &resRef,
514 DenseMap<mlir::func::FuncOp, calyx::ComponentOp> &map,
517 functionMapping(map), calyxLoweringState(state) {}
518
519LogicalResult
521 PatternRewriter &rewriter) const {
522 // Initialize the component op references if a calyx::ComponentOp has been
523 // created for the matched funcOp.
524 if (auto it = functionMapping.find(funcOp); it != functionMapping.end()) {
525 componentOp = it->second;
529 }
530
531 return partiallyLowerFuncToComp(funcOp, rewriter);
532}
533
536 "Component operation should be set during pattern construction");
537 return componentOp;
538}
539
543
544//===----------------------------------------------------------------------===//
545// ConvertIndexTypes
546//===----------------------------------------------------------------------===//
547
548LogicalResult
550 PatternRewriter &rewriter) const {
551 funcOp.walk([&](Block *block) {
552 for (Value arg : block->getArguments())
553 arg.setType(calyx::normalizeType(rewriter, arg.getType()));
554 });
555
556 funcOp.walk([&](Operation *op) {
557 for (Value result : op->getResults()) {
558 Type resType = result.getType();
559 if (!resType.isIndex())
560 continue;
561
562 result.setType(calyx::normalizeType(rewriter, resType));
563 auto constant = dyn_cast<mlir::arith::ConstantOp>(op);
564 if (!constant)
565 continue;
566
567 APInt value;
568 calyx::matchConstantOp(constant, value);
569 rewriter.setInsertionPoint(constant);
570 rewriter.replaceOpWithNewOp<mlir::arith::ConstantOp>(
571 constant, rewriter.getI32IntegerAttr(value.getSExtValue()));
572 }
573 });
574 return success();
575}
576
577//===----------------------------------------------------------------------===//
578// NonTerminatingGroupDonePattern
579//===----------------------------------------------------------------------===//
580
581LogicalResult
583 PatternRewriter &) const {
584 Block *block = groupDoneOp->getBlock();
585 if (&block->back() == groupDoneOp)
586 return failure();
587
588 groupDoneOp->moveBefore(groupDoneOp->getBlock(),
589 groupDoneOp->getBlock()->end());
590 return success();
591}
592
593//===----------------------------------------------------------------------===//
594// MultipleGroupDonePattern
595//===----------------------------------------------------------------------===//
596
597LogicalResult
599 PatternRewriter &rewriter) const {
600 auto groupDoneOps = SmallVector<calyx::GroupDoneOp>(
601 groupOp.getBodyBlock()->getOps<calyx::GroupDoneOp>());
602
603 if (groupDoneOps.size() <= 1)
604 return failure();
605
606 /// 'and' all of the calyx::GroupDoneOp's.
607 rewriter.setInsertionPointToEnd(groupDoneOps[0]->getBlock());
608 SmallVector<Value> doneOpSrcs;
609 llvm::transform(groupDoneOps, std::back_inserter(doneOpSrcs),
610 [](calyx::GroupDoneOp op) { return op.getSrc(); });
611 Value allDone = comb::AndOp::create(rewriter, groupDoneOps.front().getLoc(),
612 doneOpSrcs, false);
613
614 /// Create a group done op with the complex expression as a guard.
615 calyx::GroupDoneOp::create(
616 rewriter, groupOp.getLoc(),
617 hw::ConstantOp::create(rewriter, groupOp.getLoc(), APInt(1, 1)), allDone);
618 for (auto groupDoneOp : groupDoneOps)
619 rewriter.eraseOp(groupDoneOp);
620
621 return success();
622}
623
624//===----------------------------------------------------------------------===//
625// EliminateUnusedCombGroups
626//===----------------------------------------------------------------------===//
627
628LogicalResult
629EliminateUnusedCombGroups::matchAndRewrite(calyx::CombGroupOp combGroupOp,
630 PatternRewriter &rewriter) const {
631 auto control =
632 combGroupOp->getParentOfType<calyx::ComponentOp>().getControlOp();
633 if (!SymbolTable::symbolKnownUseEmpty(combGroupOp.getSymNameAttr(), control))
634 return failure();
635
636 rewriter.eraseOp(combGroupOp);
637 return success();
638}
639
640//===----------------------------------------------------------------------===//
641// DeduplicateParallelOperations
642//===----------------------------------------------------------------------===//
643
644LogicalResult
646 PatternRewriter &rewriter) const {
647 return deduplicateParallelOperation<calyx::ParOp>(parOp, rewriter);
648}
649
650LogicalResult
652 PatternRewriter &rewriter) const {
654}
655
656//===----------------------------------------------------------------------===//
657// InlineCombGroups
658//===----------------------------------------------------------------------===//
659
664
665LogicalResult
666InlineCombGroups::partiallyLower(calyx::GroupInterface originGroup,
667 PatternRewriter &rewriter) const {
668 auto component = originGroup->getParentOfType<calyx::ComponentOp>();
669 ComponentLoweringStateInterface *state = cls.getState(component);
670
671 // Filter groups which are not part of the control schedule.
672 if (SymbolTable::symbolKnownUseEmpty(originGroup.symName(),
673 component.getControlOp()))
674 return success();
675
676 // Maintain a set of the groups which we've inlined so far. The group
677 // itself is implicitly inlined.
679 inlinedGroups.insert(originGroup);
680
681 // Starting from the matched originGroup, we traverse use-def chains of
682 // combinational logic, and inline assignments from the defining
683 // combinational groups.
684 recurseInlineCombGroups(rewriter, *state, inlinedGroups, originGroup,
685 originGroup,
686 /*doInline=*/false);
687 return success();
688}
689
691 PatternRewriter &rewriter, ComponentLoweringStateInterface &state,
693 calyx::GroupInterface originGroup, calyx::GroupInterface recGroup,
694 bool doInline) const {
695 inlinedGroups.insert(recGroup);
696 for (auto assignOp : recGroup.getBody()->getOps<calyx::AssignOp>()) {
697 if (doInline) {
698 /// Inline the assignment into the originGroup.
699 auto *clonedAssignOp = rewriter.clone(*assignOp.getOperation());
700 clonedAssignOp->moveBefore(originGroup.getBody(),
701 originGroup.getBody()->end());
702 }
703 Value src = assignOp.getSrc();
704
705 // Things which stop recursive inlining (or in other words, what
706 // breaks combinational paths).
707 // - Component inputs
708 // - Register and memory reads
709 // - Constant ops (constant ops are not evaluated by any group)
710 // - Multiplication pipelines are sequential.
711 // - 'While' return values (these are registers, however, 'while'
712 // return values have at the current point of conversion not yet
713 // been rewritten to their register outputs, see comment in
714 // LateSSAReplacement)
715 if (isa<BlockArgument>(src) ||
716 isa<calyx::RegisterOp, calyx::MemoryOp, calyx::SeqMemoryOp,
717 hw::ConstantOp, mlir::arith::ConstantOp, calyx::MultPipeLibOp,
718 calyx::DivUPipeLibOp, calyx::DivSPipeLibOp, calyx::RemSPipeLibOp,
719 calyx::RemUPipeLibOp, mlir::scf::WhileOp, calyx::InstanceOp,
720 calyx::ConstantOp, calyx::AddFOpIEEE754, calyx::MulFOpIEEE754,
721 calyx::CompareFOpIEEE754, calyx::FpToIntOpIEEE754,
722 calyx::IntToFpOpIEEE754, calyx::DivSqrtOpIEEE754>(
723 src.getDefiningOp()))
724 continue;
725
726 if (state.isSeqGuardCmpLibOp(src.getDefiningOp())) {
727 continue;
728 }
729
730 auto srcCombGroup = dyn_cast<calyx::CombGroupOp>(
731 state.getEvaluatingGroup(src).getOperation());
732 if (!srcCombGroup)
733 continue;
734 if (inlinedGroups.count(srcCombGroup))
735 continue;
736
737 recurseInlineCombGroups(rewriter, state, inlinedGroups, originGroup,
738 srcCombGroup, /*doInline=*/true);
739 }
740}
741
742//===----------------------------------------------------------------------===//
743// RewriteMemoryAccesses
744//===----------------------------------------------------------------------===//
745
746LogicalResult
748 PatternRewriter &rewriter) const {
749 auto *state = cls.getState(assignOp->getParentOfType<calyx::ComponentOp>());
750
751 Value dest = assignOp.getDest();
752 if (!state->isInputPortOfMemory(dest))
753 return success();
754
755 Value src = assignOp.getSrc();
756 unsigned srcBits = src.getType().getIntOrFloatBitWidth();
757 unsigned dstBits = dest.getType().getIntOrFloatBitWidth();
758 if (srcBits == dstBits)
759 return success();
760
761 SmallVector<Type> types = {
762 rewriter.getIntegerType(srcBits),
763 rewriter.getIntegerType(dstBits),
764 };
765 mlir::Location loc = assignOp.getLoc();
766 Operation *newOp;
767 if (srcBits > dstBits)
768 newOp =
769 state->getNewLibraryOpInstance<calyx::SliceLibOp>(rewriter, loc, types);
770 else
771 newOp =
772 state->getNewLibraryOpInstance<calyx::PadLibOp>(rewriter, loc, types);
773
774 rewriter.setInsertionPoint(assignOp->getBlock(),
775 assignOp->getBlock()->begin());
776 calyx::AssignOp::create(rewriter, assignOp->getLoc(), newOp->getResult(0),
777 src);
778 assignOp.setOperand(1, newOp->getResult(1));
779
780 return success();
781}
782
783//===----------------------------------------------------------------------===//
784// BuildBasicBlockRegs
785//===----------------------------------------------------------------------===//
786
787LogicalResult
789 PatternRewriter &rewriter) const {
790 funcOp.walk([&](Block *block) {
791 /// Do not register component input values.
792 if (block == &block->getParent()->front())
793 return;
794
795 for (auto arg : enumerate(block->getArguments())) {
796 Type argType = arg.value().getType();
797 assert(isa<IntegerType>(argType) && "unsupported block argument type");
798 unsigned width = argType.getIntOrFloatBitWidth();
799 std::string index = std::to_string(arg.index());
800 std::string name = loweringState().blockName(block) + "_arg" + index;
801 auto reg = createRegister(arg.value().getLoc(), rewriter, getComponent(),
802 width, name);
803 getState().addBlockArgReg(block, reg, arg.index());
804 rewriter.replaceAllUsesWith(arg.value(), reg.getOut());
805 }
806 });
807 return success();
808}
809
810//===----------------------------------------------------------------------===//
811// BuildReturnRegs
812//===----------------------------------------------------------------------===//
813
814LogicalResult
816 PatternRewriter &rewriter) const {
817
818 for (auto argType : enumerate(funcOp.getResultTypes())) {
819 auto convArgType = calyx::normalizeType(rewriter, argType.value());
820 assert((isa<IntegerType>(convArgType) || isa<FloatType>(convArgType)) &&
821 "unsupported return type");
822 std::string name = "ret_arg" + std::to_string(argType.index());
823 auto reg = createRegister(funcOp.getLoc(), rewriter, getComponent(),
824 convArgType.getIntOrFloatBitWidth(), name);
825 getState().addReturnReg(reg, argType.index());
826
827 rewriter.setInsertionPointToStart(
828 getComponent().getWiresOp().getBodyBlock());
829 calyx::AssignOp::create(
830 rewriter, funcOp->getLoc(),
832 getComponent(), getState().getFuncOpResultMapping(argType.index())),
833 reg.getOut());
834 }
835 return success();
836}
837
838//===----------------------------------------------------------------------===//
839// BuildCallInstance
840//===----------------------------------------------------------------------===//
841
842LogicalResult
844 PatternRewriter &rewriter) const {
845 funcOp.walk([&](mlir::func::CallOp callOp) {
846 ComponentOp componentOp = getCallComponent(callOp);
847 SmallVector<Type, 8> resultTypes;
848 for (auto type : componentOp.getArgumentTypes())
849 resultTypes.push_back(type);
850 for (auto type : componentOp.getResultTypes())
851 resultTypes.push_back(type);
852 std::string instanceName = getInstanceName(callOp);
853
854 // Determines if an instance needs to be created. If the same function was
855 // called by CallOp before, it doesn't need to be created, if not, the
856 // instance is created.
857 if (!getState().getInstance(instanceName)) {
858 InstanceOp instanceOp =
859 createInstance(callOp.getLoc(), rewriter, getComponent(), resultTypes,
860 instanceName, componentOp.getName());
861 getState().addInstance(instanceName, instanceOp);
862 }
863 WalkResult::advance();
864 });
865 return success();
866}
867
868ComponentOp
869BuildCallInstance::getCallComponent(mlir::func::CallOp callOp) const {
870 std::string callee = "func_" + callOp.getCallee().str();
871 for (auto [funcOp, componentOp] : functionMapping) {
872 if (funcOp.getSymName() == callee)
873 return componentOp;
874 }
875 return nullptr;
876}
877
878PredicateInfo getPredicateInfo(CmpFPredicate pred) {
879 using CombLogic = PredicateInfo::CombLogic;
881 PredicateInfo info;
882 switch (pred) {
883 case CmpFPredicate::OEQ: {
884 info.logic = CombLogic::And;
885 info.inputPorts = {{Port::Eq, /*inverted=*/false},
886 {Port::Unordered, /*inverted=*/true}};
887 break;
888 }
889 case CmpFPredicate::OGT: {
891 info.inputPorts = {{Port::Gt, /*inverted=*/false},
892 {Port::Unordered, /*inverted=*/true}};
893 break;
894 }
895 case CmpFPredicate::OGE: {
897 info.inputPorts = {{Port::Lt, /*inverted=*/true},
898 {Port::Unordered, /*inverted=*/true}};
899 break;
900 }
901 case CmpFPredicate::OLT: {
903 info.inputPorts = {{Port::Lt, /*inverted=*/false},
904 {Port::Unordered, /*inverted=*/true}};
905 break;
906 }
907 case CmpFPredicate::OLE: {
909 info.inputPorts = {{Port::Gt, /*inverted=*/true},
910 {Port::Unordered, /*inverted=*/true}};
911 break;
912 }
913 case CmpFPredicate::ONE: {
915 info.inputPorts = {{Port::Eq, /*inverted=*/true},
916 {Port::Unordered, /*inverted=*/true}};
917 break;
918 }
919 case CmpFPredicate::ORD: {
921 info.inputPorts = {{Port::Unordered, /*inverted=*/true}};
922 break;
923 }
924 case CmpFPredicate::UEQ: {
925 info.logic = PredicateInfo::CombLogic::Or;
926 info.inputPorts = {{Port::Eq, /*inverted=*/false},
927 {Port::Unordered, /*inverted=*/false}};
928 break;
929 }
930 case CmpFPredicate::UGT: {
931 info.logic = PredicateInfo::CombLogic::Or;
932 info.inputPorts = {{Port::Gt, /*inverted=*/false},
933 {Port::Unordered, /*inverted=*/false}};
934 break;
935 }
936 case CmpFPredicate::UGE: {
937 info.logic = PredicateInfo::CombLogic::Or;
938 info.inputPorts = {{Port::Lt, /*inverted=*/true},
939 {Port::Unordered, /*inverted=*/false}};
940 break;
941 }
942 case CmpFPredicate::ULT: {
943 info.logic = PredicateInfo::CombLogic::Or;
944 info.inputPorts = {{Port::Lt, /*inverted=*/false},
945 {Port::Unordered, /*inverted=*/false}};
946 break;
947 }
948 case CmpFPredicate::ULE: {
949 info.logic = PredicateInfo::CombLogic::Or;
950 info.inputPorts = {{Port::Gt, /*inverted=*/true},
951 {Port::Unordered, /*inverted=*/false}};
952 break;
953 }
954 case CmpFPredicate::UNE: {
955 info.logic = PredicateInfo::CombLogic::Or;
956 info.inputPorts = {{Port::Eq, /*inverted=*/true},
957 {Port::Unordered, /*inverted=*/false}};
958 break;
959 }
960 case CmpFPredicate::UNO: {
962 info.inputPorts = {{Port::Unordered, /*inverted=*/false}};
963 break;
964 }
965 case CmpFPredicate::AlwaysTrue:
966 case CmpFPredicate::AlwaysFalse:
968 break;
969 }
970
971 return info;
972}
973
974bool parentIsSeqCell(const Value value) {
975 if (Operation *defOp = value.getDefiningOp()) {
976 auto cellOp = dyn_cast_or_null<calyx::CellInterface>(defOp);
977 return cellOp && !cellOp.isCombinational();
978 }
979 return false;
980}
981
982} // namespace calyx
983} // namespace circt
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
NamedAttrList attrs
static Block * getBodyBlock(FModuleLike mod)
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)
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.
void addSeqGuardCmpLibOp(Operation *op)
Add op if it's a compare operator that requires a register to hold the value of its sequential guard ...
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.
bool isSeqGuardCmpLibOp(Operation *op)
Returns if op is a compare operator that requires a register to hold the value of its sequential guar...
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.
DenseSet< Operation * > seqGuardCmpLibOps
A set of compare operators that require registers to hold their sequential guard computation.
std::optional< calyx::MemoryInterface > isInputPortOfMemory(Value v)
If v is an input to any memory registered within this component, returns the memory.
llvm::json::Value extMemData
A json file to store external global memory data.
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
PartialLoweringPattern(MLIRContext *ctx, LogicalResult &resRef, PatternApplicationState &patternState)
LogicalResult partiallyLower(calyx::AssignOp assignOp, PatternRewriter &rewriter) const override
create(data_type, value)
Definition hw.py:433
bool parentIsSeqCell(Value value)
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...
PredicateInfo getPredicateInfo(mlir::arith::CmpFPredicate pred)
bool isControlLeafNode(Operation *op)
Type normalizeType(OpBuilder &builder, Type type)
LogicalResult applyModuleOpConversion(mlir::ModuleOp, StringRef topLevelFunction)
Helper to update the top-level ModuleOp to set the entrypoing function.
static LogicalResult deduplicateParallelOperation(OpTy parOp, PatternRewriter &rewriter)
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.
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
LogicalResult matchAndRewrite(calyx::ParOp parOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(calyx::StaticParOp parOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(calyx::CombGroupOp combGroupOp, PatternRewriter &rewriter) const override
std::optional< Value > contentEnOpt()
std::variant< calyx::MemoryOp, calyx::SeqMemoryOp, MemoryPortsImpl > impl
std::optional< Value > writeDataOpt()
std::optional< Value > readEnOpt()
std::optional< Value > readDataOpt()
std::optional< Value > doneOpt()
std::optional< Value > writeEnOpt()
std::optional< Value > readOrContentEn
std::optional< Value > writeEn
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:89
Predicate information for the floating point comparisons.