20#include "mlir/IR/BuiltinOps.h"
21#include "mlir/IR/BuiltinTypes.h"
22#include "mlir/Tools/mlir-translate/Translation.h"
23#include "llvm/ADT/SmallSet.h"
24#include "llvm/ADT/TypeSwitch.h"
25#include "llvm/Support/Casting.h"
26#include "llvm/Support/FormatVariadic.h"
36static constexpr std::string_view LSquare() {
return "["; }
37static constexpr std::string_view RSquare() {
return "]"; }
38static constexpr std::string_view LAngleBracket() {
return "<"; }
39static constexpr std::string_view RAngleBracket() {
return ">"; }
40static constexpr std::string_view LParen() {
return "("; }
41static constexpr std::string_view RParen() {
return ")"; }
42static constexpr std::string_view colon() {
return ": "; }
43static constexpr std::string_view
space() {
return " "; }
44static constexpr std::string_view period() {
return "."; }
45static constexpr std::string_view questionMark() {
return " ? "; }
46static constexpr std::string_view exclamationMark() {
return "!"; }
47static constexpr std::string_view equals() {
return "="; }
48static constexpr std::string_view comma() {
return ", "; }
49static constexpr std::string_view arrow() {
return " -> "; }
50static constexpr std::string_view quote() {
return "\""; }
51static constexpr std::string_view apostrophe() {
return "'"; }
52static constexpr std::string_view LBraceEndL() {
return "{\n"; }
53static constexpr std::string_view RBraceEndL() {
return "}\n"; }
54static constexpr std::string_view semicolonEndL() {
return ";\n"; }
55static constexpr std::string_view addressSymbol() {
return "@"; }
56static constexpr std::string_view endl() {
return "\n"; }
57static constexpr std::string_view metadataLBrace() {
return "#{\n"; }
58static constexpr std::string_view metadataRBrace() {
return "}#\n"; }
61constexpr std::array<StringRef, 7> integerAttributes{
62 "external",
"static",
"share",
"bound",
63 "write_together",
"read_together",
"pos",
67constexpr std::array<StringRef, 12> booleanAttributes{
68 "clk",
"reset",
"go",
"done",
"generated",
"precious",
69 "toplevel",
"stable",
"nointerface",
"inline",
"state_share",
"data",
72static std::optional<StringRef> getCalyxAttrIdentifier(NamedAttribute attr) {
73 StringRef identifier = attr.getName().strref();
74 if (identifier.contains(
".")) {
75 Dialect *dialect = attr.getNameDialect();
76 if (dialect !=
nullptr && isa<CalyxDialect>(*dialect)) {
77 return std::get<1>(identifier.split(
"."));
86static bool isValidCalyxAttribute(StringRef identifier) {
88 return llvm::find(integerAttributes, identifier) != integerAttributes.end() ||
89 llvm::find(booleanAttributes, identifier) != booleanAttributes.end();
98 FailureOr<llvm::SmallSet<StringRef, 4>> getLibraryNames(ModuleOp module) {
99 auto walkRes =
module.walk([&](ComponentOp component) {
100 for (auto &op : *component.getBodyBlock()) {
101 if (!isa<CellInterface>(op) || isa<InstanceOp, PrimitiveOp>(op))
104 auto libraryName = getLibraryFor(&op);
105 if (failed(libraryName))
106 return WalkResult::interrupt();
107 usedLibraries.insert(*libraryName);
109 return WalkResult::advance();
111 if (walkRes.wasInterrupted())
113 return usedLibraries;
118 FailureOr<StringRef> getLibraryFor(Operation *op) {
119 return TypeSwitch<Operation *, FailureOr<StringRef>>(op)
120 .Case<AddLibOp, RegisterOp, UndefLibOp, WireLibOp>(
121 [&](
auto op) -> FailureOr<StringRef> {
122 static constexpr std::string_view sCompile =
"compile";
125 .Case<NotLibOp, AndLibOp, OrLibOp, XorLibOp, SubLibOp, GtLibOp, LtLibOp,
126 EqLibOp, NeqLibOp, GeLibOp, LeLibOp, LshLibOp, RshLibOp,
127 SliceLibOp, PadLibOp, MuxLibOp>(
128 [&](
auto op) -> FailureOr<StringRef> {
129 static constexpr std::string_view sCore =
"core";
132 .Case<SgtLibOp, SltLibOp, SeqLibOp, SneqLibOp, SgeLibOp, SleLibOp,
133 SrshLibOp, MultPipeLibOp, RemUPipeLibOp, RemSPipeLibOp,
134 DivUPipeLibOp, DivSPipeLibOp, ExtSILibOp>(
135 [&](
auto op) -> FailureOr<StringRef> {
136 static constexpr std::string_view sBinaryOperators =
138 return {sBinaryOperators};
140 .Case<MemoryOp>([&](
auto op) -> FailureOr<StringRef> {
141 static constexpr std::string_view sMemories =
"memories/comb";
144 .Case<SeqMemoryOp>([&](
auto op) -> FailureOr<StringRef> {
145 static constexpr std::string_view sMemories =
"memories/seq";
148 .Case<ConstantOp>([&](
auto op) -> FailureOr<StringRef> {
149 static constexpr std::string_view sFloat =
"float";
152 .Case<AddFOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
153 static constexpr std::string_view sFloatingPoint =
"float/addFN";
154 return {sFloatingPoint};
156 .Case<MulFOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
157 static constexpr std::string_view sFloatingPoint =
"float/mulFN";
158 return {sFloatingPoint};
160 .Case<CompareFOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
161 static constexpr std::string_view sFloatingPoint =
"float/compareFN";
162 return {sFloatingPoint};
164 .Case<FpToIntOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
165 static constexpr std::string_view sFloatingPoint =
"float/fpToInt";
166 return {sFloatingPoint};
168 .Case<IntToFpOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
169 static constexpr std::string_view sFloatingPoint =
"float/intToFp";
170 return {sFloatingPoint};
172 .Case<DivSqrtOpIEEE754>([&](
auto op) -> FailureOr<StringRef> {
173 static constexpr std::string_view sFloatingPoint =
"float/divSqrtFN";
174 return {sFloatingPoint};
176 .Default([&](
auto op) {
177 auto diag = op->emitOpError() <<
"not supported for emission";
192 Emitter(llvm::raw_ostream &os) : os(os) {}
193 LogicalResult finalize();
196 raw_ostream &indent() {
return os.indent(currentIndent); }
197 void addIndent() { currentIndent += 2; }
198 void reduceIndent() {
199 assert(currentIndent >= 2 &&
"Unintended indentation wrap");
204 void emitModule(ModuleOp op);
207 void emitCiderMetadata(mlir::ModuleOp op) {
208 auto metadata = op->getAttrOfType<ArrayAttr>(
"calyx.metadata");
212 constexpr std::string_view metadataIdentifier =
"metadata";
213 os << endl() << metadataIdentifier <<
space() << metadataLBrace();
215 for (
auto sourceLoc :
llvm::enumerate(metadata)) {
217 os << std::to_string(sourceLoc.index()) << colon();
218 os << cast<StringAttr>(sourceLoc.value()).getValue() << endl();
221 os << metadataRBrace();
225 LogicalResult emitImports(ModuleOp op) {
226 auto emitImport = [&](StringRef library) {
229 os <<
"import " << quote() <<
"primitives/" << library << period()
230 <<
"futil" << quote() << semicolonEndL();
233 auto libraryNames = importTracker.getLibraryNames(op);
234 if (failed(libraryNames))
237 for (StringRef library : *libraryNames)
244 void emitComponent(ComponentInterface op);
245 void emitComponentPorts(ComponentInterface op);
252 void emitInstance(InstanceOp op);
255 void emitPrimitive(PrimitiveOp op);
258 void emitWires(WiresOp op);
261 void emitGroup(GroupInterface group);
264 void emitControl(ControlOp control);
267 void emitAssignment(AssignOp op);
270 void emitEnable(EnableOp enable);
273 void emitRegister(RegisterOp reg);
276 void emitUndef(UndefLibOp op);
279 void emitMemory(MemoryOp memory);
282 void emitSeqMemory(SeqMemoryOp memory);
285 void emitInvoke(InvokeOp invoke);
288 void emitConstant(ConstantOp constant);
296 void emitLibraryPrimTypedByAllPorts(Operation *op);
304 void emitLibraryPrimTypedByFirstInputPort(Operation *op);
312 void emitLibraryPrimTypedByFirstOutputPort(
313 Operation *op, std::optional<StringRef> calyxLibName = {});
316 void emitLibraryFloatingPoint(Operation *op);
320 ImportTracker importTracker;
323 InFlightDiagnostic emitError(Operation *op,
const Twine &message) {
324 encounteredError =
true;
325 return op->emitError(message);
329 InFlightDiagnostic emitOpError(Operation *op,
const Twine &message) {
330 encounteredError =
true;
331 return op->emitOpError(message);
344 std::string getAttribute(Operation *op, NamedAttribute attr,
bool isPort,
347 std::optional<StringRef> identifierOpt = getCalyxAttrIdentifier(attr);
349 if (!identifierOpt.has_value())
352 StringRef identifier = *identifierOpt;
354 if (!isValidCalyxAttribute(identifier))
358 llvm::raw_string_ostream buffer(output);
359 buffer.reserveExtraSpace(32);
361 bool isBooleanAttribute =
362 llvm::find(booleanAttributes, identifier) != booleanAttributes.end();
364 if (isa<UnitAttr>(attr.getValue())) {
365 assert(isBooleanAttribute &&
366 "Non-boolean attributes must provide an integer value.");
368 buffer << quote() << identifier << quote() << equals() <<
"1";
370 buffer << addressSymbol() << identifier;
372 }
else if (
auto intAttr = dyn_cast<IntegerAttr>(attr.getValue())) {
373 APInt value = intAttr.getValue();
375 buffer << quote() << identifier << quote() << equals() << value;
377 buffer << addressSymbol() << identifier;
380 if (!isBooleanAttribute || intAttr.getValue() != 1) {
382 SmallVector<char, 4> s;
383 value.toStringUnsigned(s, 10);
384 buffer << LParen() << s << RParen();
393 std::string getAttributes(Operation *op,
bool atFormat,
394 DictionaryAttr attributes =
nullptr) {
395 bool isPort = attributes !=
nullptr;
396 bool atLeastOne =
false;
399 attributes = op->getAttrDictionary();
401 std::string calyxAttributes;
402 llvm::raw_string_ostream buf(calyxAttributes);
405 buf << LAngleBracket();
407 for (
auto &attr : attributes) {
409 if (
auto out = getAttribute(op, attr,
isPort, atFormat); !out.empty()) {
412 buf << (atFormat ?
space() : comma());
417 auto out = buf.str();
420 out.append(atFormat ?
space() : RAngleBracket());
432 template <
typename Func>
433 void emitCalyxBody(Func emitBody) {
434 os <<
space() << LBraceEndL();
438 indent() << RBraceEndL();
442 template <
typename Func>
443 void emitCalyxSection(StringRef sectionName, Func emitBody,
444 StringRef symbolName =
"") {
445 indent() << sectionName;
446 if (!symbolName.empty())
447 os <<
space() << symbolName;
448 emitCalyxBody(emitBody);
452 template <
typename CombinationalOp>
453 void emitCombinationalValue(CombinationalOp op, StringRef logicalSymbol) {
454 auto inputs = op.getInputs();
456 for (
size_t i = 0, e = inputs.size(); i != e; ++i) {
457 emitValue(inputs[i],
false);
465 void emitCycleValue(CycleOp op) {
467 if (op.getEnd().has_value()) {
469 os << op.getStart() <<
":" << op.getEnd();
477 void emitValue(Value value,
bool isIndented) {
478 if (
auto blockArg = dyn_cast<BlockArgument>(value)) {
481 (isIndented ? indent() : os) << portName.getValue();
485 auto *definingOp = value.getDefiningOp();
486 assert(definingOp &&
"Value does not have a defining operation.");
488 TypeSwitch<Operation *>(definingOp)
489 .Case<CellInterface>([&](
auto cell) {
491 (isIndented ? indent() : os)
492 << cell.instanceName() << period() << cell.portName(value);
494 .Case<hw::ConstantOp>([&](
auto op) {
497 APInt value = op.getValue();
499 (isIndented ? indent() : os)
500 << std::to_string(value.
getBitWidth()) << apostrophe() <<
"d";
502 value.print(os,
false);
504 .Case<comb::AndOp>([&](
auto op) { emitCombinationalValue(op,
"&"); })
505 .Case<comb::OrOp>([&](
auto op) { emitCombinationalValue(op,
"|"); })
506 .Case<comb::XorOp>([&](
auto op) {
509 if (!op.isBinaryNot()) {
510 emitOpError(op,
"Only supporting Binary Not for XOR.");
515 os << exclamationMark();
516 emitValue(op.getInputs()[0],
false);
518 .Case<CycleOp>([&](
auto op) { emitCycleValue(op); })
520 [&](
auto op) { emitOpError(op,
"not supported for emission"); });
524 template <
typename OpTy>
525 void emitGroupPort(GroupInterface group, OpTy op, StringRef portHole) {
526 assert((isa<GroupGoOp>(op) || isa<GroupDoneOp>(op)) &&
527 "Required to be a group port.");
528 indent() << group.symName().getValue() << LSquare() << portHole << RSquare()
531 emitValue(op.getGuard(),
false);
532 os << questionMark();
534 emitValue(op.getSrc(),
false);
535 os << semicolonEndL();
539 void emitCalyxControl(Block *body) {
540 Operation *parent = body->getParentOp();
542 "This should only be used to emit Calyx Control structures.");
546 if (
auto enable = dyn_cast<EnableOp>(parent)) {
552 auto prependAttributes = [&](Operation *op, StringRef sym) {
553 return (getAttributes(op,
true) + sym).str();
556 for (
auto &&op : *body) {
558 TypeSwitch<Operation *>(&op)
559 .Case<SeqOp>([&](
auto op) {
560 emitCalyxSection(prependAttributes(op,
"seq"),
561 [&]() { emitCalyxControl(op.getBodyBlock()); });
563 .Case<StaticSeqOp>([&](
auto op) {
564 emitCalyxSection(prependAttributes(op,
"static seq"),
565 [&]() { emitCalyxControl(op.getBodyBlock()); });
567 .Case<ParOp>([&](
auto op) {
568 emitCalyxSection(prependAttributes(op,
"par"),
569 [&]() { emitCalyxControl(op.getBodyBlock()); });
571 .Case<WhileOp>([&](
auto op) {
572 indent() << prependAttributes(op,
"while ");
573 emitValue(op.getCond(),
false);
575 if (
auto groupName = op.getGroupName())
576 os <<
" with " << *groupName;
578 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
580 .Case<IfOp>([&](
auto op) {
581 indent() << prependAttributes(op,
"if ");
582 emitValue(op.getCond(),
false);
584 if (
auto groupName = op.getGroupName())
585 os <<
" with " << *groupName;
587 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
588 if (op.elseBodyExists())
589 emitCalyxSection(
"else",
590 [&]() { emitCalyxControl(op.getElseBody()); });
592 .Case<StaticIfOp>([&](
auto op) {
593 indent() << prependAttributes(op,
"static if ");
594 emitValue(op.getCond(),
false);
596 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
597 if (op.elseBodyExists())
598 emitCalyxSection(
"else",
599 [&]() { emitCalyxControl(op.getElseBody()); });
601 .Case<RepeatOp>([&](
auto op) {
602 indent() << prependAttributes(op,
"repeat ");
605 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
607 .Case<StaticRepeatOp>([&](
auto op) {
608 indent() << prependAttributes(op,
"static repeat ");
611 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
613 .Case<StaticParOp>([&](
auto op) {
614 emitCalyxSection(prependAttributes(op,
"static par"),
615 [&]() { emitCalyxControl(op.getBodyBlock()); });
617 .Case<EnableOp>([&](
auto op) { emitEnable(op); })
618 .Case<InvokeOp>([&](
auto op) { emitInvoke(op); })
619 .Default([&](
auto op) {
620 emitOpError(op,
"not supported for emission inside control.");
626 llvm::raw_ostream &os;
629 bool encounteredError =
false;
633 unsigned currentIndent = 0;
638LogicalResult Emitter::finalize() {
return failure(encounteredError); }
641void Emitter::emitModule(ModuleOp op) {
642 for (
auto &bodyOp : *op.getBody()) {
643 if (
auto componentOp = dyn_cast<ComponentInterface>(bodyOp))
644 emitComponent(componentOp);
645 else if (
auto hwModuleExternOp = dyn_cast<hw::HWModuleExternOp>(bodyOp))
646 emitPrimitiveExtern(hwModuleExternOp);
648 emitOpError(&bodyOp,
"Unexpected op");
653void Emitter::emitComponent(ComponentInterface op) {
654 std::string combinationalPrefix = op.isComb() ?
"comb " :
"";
656 indent() << combinationalPrefix <<
"component " << op.getName()
657 << getAttributes(op,
false,
nullptr);
659 emitComponentPorts(op);
660 os <<
space() << LBraceEndL();
666 emitCalyxSection(
"cells", [&]() {
668 TypeSwitch<Operation *>(&bodyOp)
669 .Case<UndefLibOp>([&](
auto op) { emitUndef(op); })
670 .Case<WiresOp>([&](
auto op) { wires = op; })
671 .Case<ControlOp>([&](
auto op) { control = op; })
672 .Case<InstanceOp>([&](
auto op) { emitInstance(op); })
673 .Case<PrimitiveOp>([&](
auto op) { emitPrimitive(op); })
674 .Case<RegisterOp>([&](
auto op) { emitRegister(op); })
675 .Case<MemoryOp>([&](
auto op) { emitMemory(op); })
676 .Case<SeqMemoryOp>([&](
auto op) { emitSeqMemory(op); })
677 .Case<hw::ConstantOp>([&](
auto op) { })
678 .Case<calyx::ConstantOp>([&](
auto op) { emitConstant(op); })
679 .Case<SliceLibOp, PadLibOp, ExtSILibOp>(
680 [&](
auto op) { emitLibraryPrimTypedByAllPorts(op); })
681 .Case<LtLibOp, GtLibOp, EqLibOp, NeqLibOp, GeLibOp, LeLibOp, SltLibOp,
682 SgtLibOp, SeqLibOp, SneqLibOp, SgeLibOp, SleLibOp, AddLibOp,
683 SubLibOp, ShruLibOp, RshLibOp, SrshLibOp, LshLibOp, AndLibOp,
684 NotLibOp, OrLibOp, XorLibOp, WireLibOp>(
685 [&](
auto op) { emitLibraryPrimTypedByFirstInputPort(op); })
687 [&](
auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
688 .Case<MultPipeLibOp>(
689 [&](
auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
690 .Case<RemUPipeLibOp, DivUPipeLibOp>([&](
auto op) {
691 emitLibraryPrimTypedByFirstOutputPort(
692 op, {
"std_div_pipe"});
694 .Case<RemSPipeLibOp, DivSPipeLibOp>([&](
auto op) {
695 emitLibraryPrimTypedByFirstOutputPort(
696 op, {
"std_sdiv_pipe"});
698 .Case<AddFOpIEEE754, MulFOpIEEE754, CompareFOpIEEE754,
699 FpToIntOpIEEE754, IntToFpOpIEEE754, DivSqrtOpIEEE754>(
700 [&](
auto op) { emitLibraryFloatingPoint(op); })
701 .Default([&](
auto op) {
702 emitOpError(op,
"not supported for emission inside component");
708 emitControl(control);
714void Emitter::emitComponentPorts(ComponentInterface op) {
715 auto emitPorts = [&](
auto ports) {
717 for (
size_t i = 0, e = ports.size(); i < e; ++i) {
721 unsigned int bitWidth = port.
type.getIntOrFloatBitWidth();
722 os << getAttributes(op,
true, port.
attributes)
723 << port.
name.getValue() << colon() << bitWidth;
730 emitPorts(op.getInputPortInfo());
732 emitPorts(op.getOutputPortInfo());
737 Attribute filename = op->getAttrDictionary().get(
"filename");
738 indent() <<
"extern " << filename <<
space() << LBraceEndL();
740 indent() <<
"primitive " << op.getName();
742 if (!op.getParameters().empty()) {
744 llvm::interleaveComma(op.getParameters(), os, [&](Attribute param) {
745 auto paramAttr = cast<hw::ParamDeclAttr>(param);
746 os << paramAttr.getName().str();
750 os << getAttributes(op,
false);
752 emitPrimitivePorts(op);
753 os << semicolonEndL();
760 auto emitPorts = [&](
auto ports,
bool isInput) {
761 auto e =
static_cast<size_t>(std::distance(ports.begin(), ports.end()));
763 auto type = op.getHWModuleType();
764 for (
auto [i, port] :
llvm::enumerate(ports)) {
765 DictionaryAttr portAttr = cast_or_null<DictionaryAttr>(
766 op.getPortAttrs(isInput ? type.getPortIdForInputId(i)
767 : type.getPortIdForOutputId(i)));
769 os << getAttributes(op,
true, portAttr)
770 << port.
name.getValue() << colon();
773 if (hw::isParametricType(port.
type)) {
774 hw::ParamDeclRefAttr bitWidth = dyn_cast<hw::ParamDeclRefAttr>(
775 cast<hw::IntType>(port.
type).getWidth());
776 os << bitWidth.getName().str();
778 unsigned int bitWidth = port.
type.getIntOrFloatBitWidth();
788 emitPorts(ports.getInputs(),
true);
790 emitPorts(ports.getOutputs(),
false);
793void Emitter::emitInstance(InstanceOp op) {
794 indent() << getAttributes(op,
true) << op.instanceName()
795 <<
space() << equals() <<
space() << op.getComponentName()
796 << LParen() << RParen() << semicolonEndL();
799void Emitter::emitPrimitive(PrimitiveOp op) {
800 indent() << getAttributes(op,
true) << op.instanceName()
801 <<
space() << equals() <<
space() << op.getPrimitiveName()
804 if (op.getParameters().has_value()) {
805 llvm::interleaveComma(*op.getParameters(), os, [&](Attribute param) {
806 auto paramAttr = cast<hw::ParamDeclAttr>(param);
807 auto value = paramAttr.getValue();
808 if (auto intAttr = dyn_cast<IntegerAttr>(value)) {
809 os << intAttr.getInt();
810 }
else if (
auto fpAttr = dyn_cast<FloatAttr>(value)) {
811 os << fpAttr.getValue().convertToFloat();
813 llvm_unreachable(
"Primitive parameter type not supported");
818 os << RParen() << semicolonEndL();
821void Emitter::emitRegister(RegisterOp reg) {
822 size_t bitWidth =
reg.getIn().getType().getIntOrFloatBitWidth();
823 indent() << getAttributes(reg,
true) <<
reg.instanceName()
824 <<
space() << equals() <<
space() <<
"std_reg" << LParen()
825 << std::to_string(bitWidth) << RParen() << semicolonEndL();
828void Emitter::emitUndef(UndefLibOp op) {
829 size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
830 indent() << getAttributes(op,
true) << op.instanceName()
831 <<
space() << equals() <<
space() <<
"undef" << LParen()
832 << std::to_string(bitwidth) << RParen() << semicolonEndL();
835void Emitter::emitMemory(MemoryOp memory) {
836 size_t dimension = memory.getSizes().size();
837 if (dimension < 1 || dimension > 4) {
838 emitOpError(memory,
"Only memories with dimensionality in range [1, 4] are "
839 "supported by the native Calyx compiler.");
842 indent() << getAttributes(memory,
true) << memory.instanceName()
843 <<
space() << equals() <<
space() <<
"std_mem_d"
844 << std::to_string(dimension) << LParen() << memory.getWidth()
846 for (Attribute size : memory.getSizes()) {
847 APInt memSize = cast<IntegerAttr>(size).getValue();
848 memSize.print(os,
false);
852 ArrayAttr addrSizes = memory.getAddrSizes();
853 for (
size_t i = 0, e = addrSizes.size(); i != e; ++i) {
854 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
855 addrSize.print(os,
false);
860 os << RParen() << semicolonEndL();
863void Emitter::emitSeqMemory(SeqMemoryOp memory) {
864 size_t dimension = memory.getSizes().size();
865 if (dimension < 1 || dimension > 4) {
866 emitOpError(memory,
"Only memories with dimensionality in range [1, 4] are "
867 "supported by the native Calyx compiler.");
870 bool isRef = !memory->hasAttr(
"external");
874 os << getAttributes(memory,
true) << memory.instanceName()
875 <<
space() << equals() <<
space() <<
"seq_mem_d"
876 << std::to_string(dimension) << LParen() << memory.getWidth() << comma();
877 for (Attribute size : memory.getSizes()) {
878 APInt memSize = cast<IntegerAttr>(size).getValue();
879 memSize.print(os,
false);
883 ArrayAttr addrSizes = memory.getAddrSizes();
884 for (
size_t i = 0, e = addrSizes.size(); i != e; ++i) {
885 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
886 addrSize.print(os,
false);
891 os << RParen() << semicolonEndL();
894void Emitter::emitInvoke(InvokeOp invoke) {
895 StringRef callee = invoke.getCallee();
896 indent() <<
"invoke " << callee;
897 auto refCellsMap = invoke.getRefCellsMap();
898 if (!refCellsMap.empty()) {
900 llvm::interleaveComma(refCellsMap, os, [&](Attribute attr) {
901 auto dictAttr = cast<DictionaryAttr>(attr);
902 llvm::interleaveComma(dictAttr, os, [&](NamedAttribute namedAttr) {
903 auto refCellName = namedAttr.getName().str();
905 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
906 os << refCellName <<
" = " << externalMem;
911 ArrayAttr portNames = invoke.getPortNames();
912 ArrayAttr inputNames = invoke.getInputNames();
916 llvm::StringMap<std::string> inputsMap;
917 llvm::StringMap<std::string> outputsMap;
918 for (
auto [portNameAttr, inputNameAttr, input] :
919 llvm::zip(portNames, inputNames, invoke.getInputs())) {
920 StringRef portName = cast<StringAttr>(portNameAttr).getValue();
921 StringRef inputName = cast<StringAttr>(inputNameAttr).getValue();
929 StringRef inputMapKey = portName.drop_front(2 + callee.size());
930 if (portName.substr(1, callee.size()) == callee) {
932 if (isa_and_nonnull<hw::ConstantOp>(input.getDefiningOp())) {
933 hw::ConstantOp constant = cast<hw::ConstantOp>(input.getDefiningOp());
934 APInt value = constant.getValue();
935 std::string mapValue = std::to_string(value.getBitWidth()) +
936 apostrophe().data() +
"d" +
937 std::to_string(value.getZExtValue());
938 inputsMap[inputMapKey] = mapValue;
941 inputsMap[inputMapKey] = inputName.drop_front(1).str();
942 }
else if (inputName.substr(1, callee.size()) == callee)
943 outputsMap[inputName.drop_front(2 + callee.size())] =
944 portName.drop_front(1).str();
948 llvm::interleaveComma(inputsMap, os, [&](
const auto &iter) {
949 os << iter.getKey() <<
" = " << iter.getValue();
954 llvm::interleaveComma(outputsMap, os, [&](
const auto &iter) {
955 os << iter.getKey() <<
" = " << iter.getValue();
957 os << RParen() << semicolonEndL();
960void Emitter::emitConstant(ConstantOp constantOp) {
961 TypedAttr attr = constantOp.getValueAttr();
962 assert(isa<FloatAttr>(attr) &&
"must be a floating point constant");
963 auto fltAttr = cast<FloatAttr>(attr);
964 APFloat value = fltAttr.getValue();
965 auto type = cast<FloatType>(fltAttr.getType());
966 double doubleValue = value.convertToDouble();
967 auto floatBits = value.getSizeInBits(type.getFloatSemantics());
968 indent() << constantOp.getName().str() <<
space() << equals() <<
space()
969 <<
"std_float_const";
972 static constexpr int32_t
IEEE754 = 0;
973 os << LParen() << std::to_string(
IEEE754) << comma() << floatBits << comma()
974 << std::to_string(doubleValue) << RParen() << semicolonEndL();
982void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
983 auto cell = cast<CellInterface>(op);
984 indent() << getAttributes(op,
true) << cell.instanceName()
987 llvm::interleaveComma(op->getResults(), os, [&](
auto res) {
988 os << std::to_string(res.getType().getIntOrFloatBitWidth());
990 os << RParen() << semicolonEndL();
993void Emitter::emitLibraryPrimTypedByFirstInputPort(Operation *op) {
994 auto cell = cast<CellInterface>(op);
995 unsigned bitWidth = cell.getInputPorts()[0].getType().getIntOrFloatBitWidth();
996 StringRef opName = op->getName().getStringRef();
997 indent() << getAttributes(op,
true) << cell.instanceName()
999 << LParen() << bitWidth << RParen() << semicolonEndL();
1002void Emitter::emitLibraryPrimTypedByFirstOutputPort(
1003 Operation *op, std::optional<StringRef> calyxLibName) {
1004 auto cell = cast<CellInterface>(op);
1006 cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1007 StringRef opName = op->getName().getStringRef();
1008 indent() << getAttributes(op,
true) << cell.instanceName()
1011 << LParen() << bitWidth << RParen() << semicolonEndL();
1015 if (isa<IntToFpOpIEEE754>(cell.getOperation())) {
1017 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1020 auto inputPorts = cell.getInputPorts();
1021 assert(inputPorts.size() >= 2 &&
"There should be at least two input ports");
1025 size_t inputPortIndex = inputPorts.size() - 2;
1026 return cell.getInputPorts()[inputPortIndex].getType().getIntOrFloatBitWidth();
1029unsigned getIntWidth(Operation *op, CellInterface &cell,
bool isIntToFp) {
1030 auto inputPorts = cell.getInputPorts();
1031 assert(inputPorts.size() >= 2);
1033 size_t integerIndex = inputPorts.size() - 2;
1036 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1038 return cell.getInputPorts()[integerIndex].getType().getIntOrFloatBitWidth();
1041void Emitter::emitLibraryFloatingPoint(Operation *op) {
1042 auto cell = cast<CellInterface>(op);
1045 unsigned expWidth, sigWidth;
1046 switch (fpBitWidth) {
1064 op->emitError(
"The supported bitwidths are 16, 32, 64, and 128");
1069 if (
auto fpOp = dyn_cast<calyx::FloatingPointOpInterface>(op)) {
1070 opName = fpOp.getCalyxLibraryName();
1073 indent() << getAttributes(op,
true) << cell.instanceName()
1074 <<
space() << equals() <<
space() << opName << LParen();
1076 if (isa<calyx::IntToFpOpIEEE754>(op)) {
1078 os << intWidth << comma();
1081 os << expWidth << comma() << sigWidth << comma() << fpBitWidth;
1083 if (
auto fpToIntOp = dyn_cast<calyx::FpToIntOpIEEE754>(op)) {
1085 os << comma() << intWidth;
1088 os << RParen() << semicolonEndL();
1091void Emitter::emitAssignment(AssignOp op) {
1093 emitValue(op.getDest(),
true);
1095 if (op.getGuard()) {
1096 emitValue(op.getGuard(),
false);
1097 os << questionMark();
1099 emitValue(op.getSrc(),
false);
1100 os << semicolonEndL();
1103void Emitter::emitWires(WiresOp op) {
1104 emitCalyxSection(
"wires", [&]() {
1106 TypeSwitch<Operation *>(&bodyOp)
1107 .Case<GroupInterface>([&](
auto op) { emitGroup(op); })
1108 .Case<AssignOp>([&](
auto op) { emitAssignment(op); })
1111 .Default([&](
auto op) {
1112 emitOpError(op,
"not supported for emission inside wires section");
1118void Emitter::emitGroup(GroupInterface group) {
1119 auto emitGroupBody = [&]() {
1120 for (
auto &&bodyOp : *group.getBody()) {
1121 TypeSwitch<Operation *>(&bodyOp)
1122 .Case<AssignOp>([&](
auto op) { emitAssignment(op); })
1123 .Case<GroupDoneOp>([&](
auto op) { emitGroupPort(group, op,
"done"); })
1124 .Case<GroupGoOp>([&](
auto op) { emitGroupPort(group, op,
"go"); })
1125 .Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp, CycleOp>(
1127 .Default([&](
auto op) {
1128 emitOpError(op,
"not supported for emission inside group.");
1133 if (isa<StaticGroupOp>(group)) {
1134 auto staticGroup = cast<StaticGroupOp>(group);
1135 prefix = llvm::formatv(
"static<{0}> group", staticGroup.getLatency());
1137 prefix = isa<CombGroupOp>(group) ?
"comb group" :
"group";
1140 (group.symName().getValue() + getAttributes(group,
false))
1142 emitCalyxSection(prefix, emitGroupBody, groupHeader);
1145void Emitter::emitEnable(EnableOp enable) {
1146 indent() << getAttributes(enable,
true) << enable.getGroupName()
1150void Emitter::emitControl(ControlOp control) {
1152 if (control ==
nullptr)
1154 emitCalyxSection(
"control",
1155 [&]() { emitCalyxControl(control.getBodyBlock()); });
1164 llvm::raw_ostream &os) {
1165 Emitter emitter(os);
1166 if (failed(emitter.emitImports(module)))
1168 emitter.emitModule(module);
1169 emitter.emitCiderMetadata(module);
1170 return emitter.finalize();
1174 static mlir::TranslateFromMLIRRegistration toCalyx(
1175 "export-calyx",
"export Calyx",
1176 [](ModuleOp module, llvm::raw_ostream &os) {
1179 [](mlir::DialectRegistry ®istry) {
1181 .insert<calyx::CalyxDialect, comb::CombDialect, hw::HWDialect>();
assert(baseType &&"element must be base type")
unsigned getIntWidth(Operation *op, CellInterface &cell, bool isIntToFp)
unsigned getFPBitWidth(CellInterface &cell)
static StringRef removeCalyxPrefix(StringRef s)
Calling getName() on a calyx operation will return "calyx.${opname}".
static bool isPort(Value value)
Returns whether this value is either (1) a port on a ComponentOp or (2) a port on a cell interface.
static Block * getBodyBlock(FModuleLike mod)
Signals that the following operation is "control-like.".
PortInfo getPortInfo(BlockArgument arg)
Returns port information for the block argument provided.
void registerToCalyxTranslation()
mlir::LogicalResult exportCalyx(mlir::ModuleOp module, llvm::raw_ostream &os)
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
This holds information about the port for either a Component or Cell.
DictionaryAttr attributes
This holds a decoded list of input/inout and output ports for a module or instance.