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();
498 auto &stream = isIndented ? indent() : os;
499 bool isNegative = value.isNegative();
500 StringRef base = isNegative ?
"b" :
"d";
502 stream << std::to_string(value.getBitWidth()) << apostrophe() << base;
506 value.toStringUnsigned(str, 2);
509 value.print(stream,
false);
512 .Case<comb::AndOp>([&](
auto op) { emitCombinationalValue(op,
"&"); })
513 .Case<comb::OrOp>([&](
auto op) { emitCombinationalValue(op,
"|"); })
514 .Case<comb::XorOp>([&](
auto op) {
517 if (!op.isBinaryNot()) {
518 emitOpError(op,
"Only supporting Binary Not for XOR.");
523 os << exclamationMark();
524 emitValue(op.getInputs()[0],
false);
526 .Case<CycleOp>([&](
auto op) { emitCycleValue(op); })
528 [&](
auto op) { emitOpError(op,
"not supported for emission"); });
532 template <
typename OpTy>
533 void emitGroupPort(GroupInterface group, OpTy op, StringRef portHole) {
534 assert((isa<GroupGoOp>(op) || isa<GroupDoneOp>(op)) &&
535 "Required to be a group port.");
536 indent() << group.symName().getValue() << LSquare() << portHole << RSquare()
539 emitValue(op.getGuard(),
false);
540 os << questionMark();
542 emitValue(op.getSrc(),
false);
543 os << semicolonEndL();
547 void emitCalyxControl(Block *body) {
548 Operation *parent = body->getParentOp();
550 "This should only be used to emit Calyx Control structures.");
554 if (
auto enable = dyn_cast<EnableOp>(parent)) {
560 auto prependAttributes = [&](Operation *op, StringRef sym) {
561 return (getAttributes(op,
true) + sym).str();
564 for (
auto &&op : *body) {
566 TypeSwitch<Operation *>(&op)
567 .Case<SeqOp>([&](
auto op) {
568 emitCalyxSection(prependAttributes(op,
"seq"),
569 [&]() { emitCalyxControl(op.getBodyBlock()); });
571 .Case<StaticSeqOp>([&](
auto op) {
572 emitCalyxSection(prependAttributes(op,
"static seq"),
573 [&]() { emitCalyxControl(op.getBodyBlock()); });
575 .Case<ParOp>([&](
auto op) {
576 emitCalyxSection(prependAttributes(op,
"par"),
577 [&]() { emitCalyxControl(op.getBodyBlock()); });
579 .Case<WhileOp>([&](
auto op) {
580 indent() << prependAttributes(op,
"while ");
581 emitValue(op.getCond(),
false);
583 if (
auto groupName = op.getGroupName())
584 os <<
" with " << *groupName;
586 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
588 .Case<IfOp>([&](
auto op) {
589 indent() << prependAttributes(op,
"if ");
590 emitValue(op.getCond(),
false);
592 if (
auto groupName = op.getGroupName())
593 os <<
" with " << *groupName;
595 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
596 if (op.elseBodyExists())
597 emitCalyxSection(
"else",
598 [&]() { emitCalyxControl(op.getElseBody()); });
600 .Case<StaticIfOp>([&](
auto op) {
601 indent() << prependAttributes(op,
"static if ");
602 emitValue(op.getCond(),
false);
604 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
605 if (op.elseBodyExists())
606 emitCalyxSection(
"else",
607 [&]() { emitCalyxControl(op.getElseBody()); });
609 .Case<RepeatOp>([&](
auto op) {
610 indent() << prependAttributes(op,
"repeat ");
613 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
615 .Case<StaticRepeatOp>([&](
auto op) {
616 indent() << prependAttributes(op,
"static repeat ");
619 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
621 .Case<StaticParOp>([&](
auto op) {
622 emitCalyxSection(prependAttributes(op,
"static par"),
623 [&]() { emitCalyxControl(op.getBodyBlock()); });
625 .Case<EnableOp>([&](
auto op) { emitEnable(op); })
626 .Case<InvokeOp>([&](
auto op) { emitInvoke(op); })
627 .Default([&](
auto op) {
628 emitOpError(op,
"not supported for emission inside control.");
634 llvm::raw_ostream &os;
637 bool encounteredError =
false;
641 unsigned currentIndent = 0;
646LogicalResult Emitter::finalize() {
return failure(encounteredError); }
649void Emitter::emitModule(ModuleOp op) {
650 for (
auto &bodyOp : *op.getBody()) {
651 if (
auto componentOp = dyn_cast<ComponentInterface>(bodyOp))
652 emitComponent(componentOp);
653 else if (
auto hwModuleExternOp = dyn_cast<hw::HWModuleExternOp>(bodyOp))
654 emitPrimitiveExtern(hwModuleExternOp);
656 emitOpError(&bodyOp,
"Unexpected op");
661void Emitter::emitComponent(ComponentInterface op) {
662 std::string combinationalPrefix = op.isComb() ?
"comb " :
"";
664 indent() << combinationalPrefix <<
"component " << op.getName()
665 << getAttributes(op,
false,
nullptr);
667 emitComponentPorts(op);
668 os <<
space() << LBraceEndL();
674 emitCalyxSection(
"cells", [&]() {
676 TypeSwitch<Operation *>(&bodyOp)
677 .Case<UndefLibOp>([&](
auto op) { emitUndef(op); })
678 .Case<WiresOp>([&](
auto op) { wires = op; })
679 .Case<ControlOp>([&](
auto op) { control = op; })
680 .Case<InstanceOp>([&](
auto op) { emitInstance(op); })
681 .Case<PrimitiveOp>([&](
auto op) { emitPrimitive(op); })
682 .Case<RegisterOp>([&](
auto op) { emitRegister(op); })
683 .Case<MemoryOp>([&](
auto op) { emitMemory(op); })
684 .Case<SeqMemoryOp>([&](
auto op) { emitSeqMemory(op); })
685 .Case<hw::ConstantOp>([&](
auto op) { })
686 .Case<calyx::ConstantOp>([&](
auto op) { emitConstant(op); })
687 .Case<SliceLibOp, PadLibOp, ExtSILibOp>(
688 [&](
auto op) { emitLibraryPrimTypedByAllPorts(op); })
689 .Case<LtLibOp, GtLibOp, EqLibOp, NeqLibOp, GeLibOp, LeLibOp, SltLibOp,
690 SgtLibOp, SeqLibOp, SneqLibOp, SgeLibOp, SleLibOp, AddLibOp,
691 SubLibOp, ShruLibOp, RshLibOp, SrshLibOp, LshLibOp, AndLibOp,
692 NotLibOp, OrLibOp, XorLibOp, WireLibOp>(
693 [&](
auto op) { emitLibraryPrimTypedByFirstInputPort(op); })
695 [&](
auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
696 .Case<MultPipeLibOp>(
697 [&](
auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
698 .Case<RemUPipeLibOp, DivUPipeLibOp>([&](
auto op) {
699 emitLibraryPrimTypedByFirstOutputPort(
700 op, {
"std_div_pipe"});
702 .Case<RemSPipeLibOp, DivSPipeLibOp>([&](
auto op) {
703 emitLibraryPrimTypedByFirstOutputPort(
704 op, {
"std_sdiv_pipe"});
706 .Case<AddFOpIEEE754, MulFOpIEEE754, CompareFOpIEEE754,
707 FpToIntOpIEEE754, IntToFpOpIEEE754, DivSqrtOpIEEE754>(
708 [&](
auto op) { emitLibraryFloatingPoint(op); })
709 .Default([&](
auto op) {
710 emitOpError(op,
"not supported for emission inside component");
716 emitControl(control);
722void Emitter::emitComponentPorts(ComponentInterface op) {
723 auto emitPorts = [&](
auto ports) {
725 for (
size_t i = 0, e = ports.size(); i < e; ++i) {
729 unsigned int bitWidth = port.
type.getIntOrFloatBitWidth();
730 os << getAttributes(op,
true, port.
attributes)
731 << port.
name.getValue() << colon() << bitWidth;
738 emitPorts(op.getInputPortInfo());
740 emitPorts(op.getOutputPortInfo());
745 Attribute filename = op->getAttrDictionary().get(
"filename");
746 indent() <<
"extern " << filename <<
space() << LBraceEndL();
748 indent() <<
"primitive " << op.getName();
750 if (!op.getParameters().empty()) {
752 llvm::interleaveComma(op.getParameters(), os, [&](Attribute param) {
753 auto paramAttr = cast<hw::ParamDeclAttr>(param);
754 os << paramAttr.getName().str();
758 os << getAttributes(op,
false);
760 emitPrimitivePorts(op);
761 os << semicolonEndL();
768 auto emitPorts = [&](
auto ports,
bool isInput) {
769 auto e =
static_cast<size_t>(std::distance(ports.begin(), ports.end()));
771 auto type = op.getHWModuleType();
772 for (
auto [i, port] :
llvm::enumerate(ports)) {
773 DictionaryAttr portAttr = cast_or_null<DictionaryAttr>(
774 op.getPortAttrs(isInput ? type.getPortIdForInputId(i)
775 : type.getPortIdForOutputId(i)));
777 os << getAttributes(op,
true, portAttr)
778 << port.
name.getValue() << colon();
781 if (hw::isParametricType(port.
type)) {
782 hw::ParamDeclRefAttr bitWidth = dyn_cast<hw::ParamDeclRefAttr>(
783 cast<hw::IntType>(port.
type).getWidth());
784 os << bitWidth.getName().str();
786 unsigned int bitWidth = port.
type.getIntOrFloatBitWidth();
796 emitPorts(ports.getInputs(),
true);
798 emitPorts(ports.getOutputs(),
false);
801void Emitter::emitInstance(InstanceOp op) {
802 indent() << getAttributes(op,
true) << op.instanceName()
803 <<
space() << equals() <<
space() << op.getComponentName()
804 << LParen() << RParen() << semicolonEndL();
807void Emitter::emitPrimitive(PrimitiveOp op) {
808 indent() << getAttributes(op,
true) << op.instanceName()
809 <<
space() << equals() <<
space() << op.getPrimitiveName()
812 if (op.getParameters().has_value()) {
813 llvm::interleaveComma(*op.getParameters(), os, [&](Attribute param) {
814 auto paramAttr = cast<hw::ParamDeclAttr>(param);
815 auto value = paramAttr.getValue();
816 if (auto intAttr = dyn_cast<IntegerAttr>(value)) {
817 os << intAttr.getInt();
818 }
else if (
auto fpAttr = dyn_cast<FloatAttr>(value)) {
819 os << fpAttr.getValue().convertToFloat();
821 llvm_unreachable(
"Primitive parameter type not supported");
826 os << RParen() << semicolonEndL();
829void Emitter::emitRegister(RegisterOp reg) {
830 size_t bitWidth =
reg.getIn().getType().getIntOrFloatBitWidth();
831 indent() << getAttributes(reg,
true) <<
reg.instanceName()
832 <<
space() << equals() <<
space() <<
"std_reg" << LParen()
833 << std::to_string(bitWidth) << RParen() << semicolonEndL();
836void Emitter::emitUndef(UndefLibOp op) {
837 size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
838 indent() << getAttributes(op,
true) << op.instanceName()
839 <<
space() << equals() <<
space() <<
"undef" << LParen()
840 << std::to_string(bitwidth) << RParen() << semicolonEndL();
843void Emitter::emitMemory(MemoryOp memory) {
844 size_t dimension = memory.getSizes().size();
845 if (dimension < 1 || dimension > 4) {
846 emitOpError(memory,
"Only memories with dimensionality in range [1, 4] are "
847 "supported by the native Calyx compiler.");
850 indent() << getAttributes(memory,
true) << memory.instanceName()
851 <<
space() << equals() <<
space() <<
"std_mem_d"
852 << std::to_string(dimension) << LParen() << memory.getWidth()
854 for (Attribute size : memory.getSizes()) {
855 APInt memSize = cast<IntegerAttr>(size).getValue();
856 memSize.print(os,
false);
860 ArrayAttr addrSizes = memory.getAddrSizes();
861 for (
size_t i = 0, e = addrSizes.size(); i != e; ++i) {
862 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
863 addrSize.print(os,
false);
868 os << RParen() << semicolonEndL();
871void Emitter::emitSeqMemory(SeqMemoryOp memory) {
872 size_t dimension = memory.getSizes().size();
873 if (dimension < 1 || dimension > 4) {
874 emitOpError(memory,
"Only memories with dimensionality in range [1, 4] are "
875 "supported by the native Calyx compiler.");
878 bool isRef = !memory->hasAttr(
"external");
882 os << getAttributes(memory,
true) << memory.instanceName()
883 <<
space() << equals() <<
space() <<
"seq_mem_d"
884 << std::to_string(dimension) << LParen() << memory.getWidth() << comma();
885 for (Attribute size : memory.getSizes()) {
886 APInt memSize = cast<IntegerAttr>(size).getValue();
887 memSize.print(os,
false);
891 ArrayAttr addrSizes = memory.getAddrSizes();
892 for (
size_t i = 0, e = addrSizes.size(); i != e; ++i) {
893 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
894 addrSize.print(os,
false);
899 os << RParen() << semicolonEndL();
902void Emitter::emitInvoke(InvokeOp invoke) {
903 StringRef callee = invoke.getCallee();
904 indent() <<
"invoke " << callee;
905 auto refCellsMap = invoke.getRefCellsMap();
906 if (!refCellsMap.empty()) {
908 llvm::interleaveComma(refCellsMap, os, [&](Attribute attr) {
909 auto dictAttr = cast<DictionaryAttr>(attr);
910 llvm::interleaveComma(dictAttr, os, [&](NamedAttribute namedAttr) {
911 auto refCellName = namedAttr.getName().str();
913 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
914 os << refCellName <<
" = " << externalMem;
919 ArrayAttr portNames = invoke.getPortNames();
920 ArrayAttr inputNames = invoke.getInputNames();
924 llvm::StringMap<std::string> inputsMap;
925 llvm::StringMap<std::string> outputsMap;
926 for (
auto [portNameAttr, inputNameAttr, input] :
927 llvm::zip(portNames, inputNames, invoke.getInputs())) {
928 StringRef portName = cast<StringAttr>(portNameAttr).getValue();
929 StringRef inputName = cast<StringAttr>(inputNameAttr).getValue();
937 StringRef inputMapKey = portName.drop_front(2 + callee.size());
938 if (portName.substr(1, callee.size()) == callee) {
940 if (isa_and_nonnull<hw::ConstantOp>(input.getDefiningOp())) {
941 hw::ConstantOp constant = cast<hw::ConstantOp>(input.getDefiningOp());
942 APInt value = constant.getValue();
943 std::string mapValue = std::to_string(value.getBitWidth()) +
944 apostrophe().data() +
"d" +
945 std::to_string(value.getZExtValue());
946 inputsMap[inputMapKey] = mapValue;
949 inputsMap[inputMapKey] = inputName.drop_front(1).str();
950 }
else if (inputName.substr(1, callee.size()) == callee)
951 outputsMap[inputName.drop_front(2 + callee.size())] =
952 portName.drop_front(1).str();
956 llvm::interleaveComma(inputsMap, os, [&](
const auto &iter) {
957 os << iter.getKey() <<
" = " << iter.getValue();
962 llvm::interleaveComma(outputsMap, os, [&](
const auto &iter) {
963 os << iter.getKey() <<
" = " << iter.getValue();
965 os << RParen() << semicolonEndL();
968void Emitter::emitConstant(ConstantOp constantOp) {
969 TypedAttr attr = constantOp.getValueAttr();
970 assert(isa<FloatAttr>(attr) &&
"must be a floating point constant");
971 auto fltAttr = cast<FloatAttr>(attr);
972 APFloat value = fltAttr.getValue();
973 auto type = cast<FloatType>(fltAttr.getType());
974 double doubleValue = value.convertToDouble();
975 auto floatBits = value.getSizeInBits(type.getFloatSemantics());
976 indent() << constantOp.getName().str() <<
space() << equals() <<
space()
977 <<
"std_float_const";
980 static constexpr int32_t
IEEE754 = 0;
981 os << LParen() << std::to_string(
IEEE754) << comma() << floatBits << comma()
982 << std::to_string(doubleValue) << RParen() << semicolonEndL();
990void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
991 auto cell = cast<CellInterface>(op);
992 indent() << getAttributes(op,
true) << cell.instanceName()
995 llvm::interleaveComma(op->getResults(), os, [&](
auto res) {
996 os << std::to_string(res.getType().getIntOrFloatBitWidth());
998 os << RParen() << semicolonEndL();
1001void Emitter::emitLibraryPrimTypedByFirstInputPort(Operation *op) {
1002 auto cell = cast<CellInterface>(op);
1003 unsigned bitWidth = cell.getInputPorts()[0].getType().getIntOrFloatBitWidth();
1004 StringRef opName = op->getName().getStringRef();
1005 indent() << getAttributes(op,
true) << cell.instanceName()
1007 << LParen() << bitWidth << RParen() << semicolonEndL();
1010void Emitter::emitLibraryPrimTypedByFirstOutputPort(
1011 Operation *op, std::optional<StringRef> calyxLibName) {
1012 auto cell = cast<CellInterface>(op);
1014 cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1015 StringRef opName = op->getName().getStringRef();
1016 indent() << getAttributes(op,
true) << cell.instanceName()
1019 << LParen() << bitWidth << RParen() << semicolonEndL();
1023 if (isa<IntToFpOpIEEE754>(cell.getOperation())) {
1025 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1028 auto inputPorts = cell.getInputPorts();
1029 assert(inputPorts.size() >= 2 &&
"There should be at least two input ports");
1033 size_t inputPortIndex = inputPorts.size() - 2;
1034 return cell.getInputPorts()[inputPortIndex].getType().getIntOrFloatBitWidth();
1037unsigned getIntWidth(Operation *op, CellInterface &cell,
bool isIntToFp) {
1038 auto inputPorts = cell.getInputPorts();
1039 assert(inputPorts.size() >= 2);
1041 size_t integerIndex = inputPorts.size() - 2;
1044 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1046 return cell.getInputPorts()[integerIndex].getType().getIntOrFloatBitWidth();
1037unsigned getIntWidth(Operation *op, CellInterface &cell,
bool isIntToFp) {
…}
1049void Emitter::emitLibraryFloatingPoint(Operation *op) {
1050 auto cell = cast<CellInterface>(op);
1053 unsigned expWidth, sigWidth;
1054 switch (fpBitWidth) {
1072 op->emitError(
"The supported bitwidths are 16, 32, 64, and 128");
1077 if (
auto fpOp = dyn_cast<calyx::FloatingPointOpInterface>(op)) {
1078 opName = fpOp.getCalyxLibraryName();
1081 indent() << getAttributes(op,
true) << cell.instanceName()
1082 <<
space() << equals() <<
space() << opName << LParen();
1084 if (isa<calyx::IntToFpOpIEEE754>(op)) {
1086 os << intWidth << comma();
1089 os << expWidth << comma() << sigWidth << comma() << fpBitWidth;
1091 if (
auto fpToIntOp = dyn_cast<calyx::FpToIntOpIEEE754>(op)) {
1093 os << comma() << intWidth;
1096 os << RParen() << semicolonEndL();
1099void Emitter::emitAssignment(AssignOp op) {
1101 emitValue(op.getDest(),
true);
1103 if (op.getGuard()) {
1104 emitValue(op.getGuard(),
false);
1105 os << questionMark();
1107 if (
auto constantOp =
1108 dyn_cast_or_null<calyx::ConstantOp>(op.getSrc().getDefiningOp())) {
1109 TypedAttr attr = constantOp.getValueAttr();
1110 assert(isa<FloatAttr>(attr) &&
"must be a floating point constant");
1111 auto fltAttr = dyn_cast<FloatAttr>(attr);
1112 assert(attr !=
nullptr &&
"must be a floating point constant");
1113 APFloat value = fltAttr.getValue();
1114 if (value.isInfinity() || value.isNaN() || value.isNegative()) {
1116 auto intValue = value.bitcastToAPInt();
1117 intValue.toStringUnsigned(str, 2);
1118 os << std::to_string(intValue.getBitWidth()) << apostrophe() <<
"b" << str
1123 emitValue(op.getSrc(),
false);
1124 os << semicolonEndL();
1127void Emitter::emitWires(WiresOp op) {
1128 emitCalyxSection(
"wires", [&]() {
1130 TypeSwitch<Operation *>(&bodyOp)
1131 .Case<GroupInterface>([&](
auto op) { emitGroup(op); })
1132 .Case<AssignOp>([&](
auto op) { emitAssignment(op); })
1135 .Default([&](
auto op) {
1136 emitOpError(op,
"not supported for emission inside wires section");
1142void Emitter::emitGroup(GroupInterface group) {
1143 auto emitGroupBody = [&]() {
1144 for (
auto &&bodyOp : *group.getBody()) {
1145 TypeSwitch<Operation *>(&bodyOp)
1146 .Case<AssignOp>([&](
auto op) { emitAssignment(op); })
1147 .Case<GroupDoneOp>([&](
auto op) { emitGroupPort(group, op,
"done"); })
1148 .Case<GroupGoOp>([&](
auto op) { emitGroupPort(group, op,
"go"); })
1149 .Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp, CycleOp>(
1151 .Default([&](
auto op) {
1152 emitOpError(op,
"not supported for emission inside group.");
1157 if (isa<StaticGroupOp>(group)) {
1158 auto staticGroup = cast<StaticGroupOp>(group);
1159 prefix = llvm::formatv(
"static<{0}> group", staticGroup.getLatency());
1161 prefix = isa<CombGroupOp>(group) ?
"comb group" :
"group";
1164 (group.symName().getValue() + getAttributes(group,
false))
1166 emitCalyxSection(prefix, emitGroupBody, groupHeader);
1169void Emitter::emitEnable(EnableOp enable) {
1170 indent() << getAttributes(enable,
true) << enable.getGroupName()
1174void Emitter::emitControl(ControlOp control) {
1176 if (control ==
nullptr)
1178 emitCalyxSection(
"control",
1179 [&]() { emitCalyxControl(control.getBodyBlock()); });
1188 llvm::raw_ostream &os) {
1189 Emitter emitter(os);
1190 if (failed(emitter.emitImports(module)))
1192 emitter.emitModule(module);
1193 emitter.emitCiderMetadata(module);
1194 return emitter.finalize();
1198 static mlir::TranslateFromMLIRRegistration toCalyx(
1199 "export-calyx",
"export Calyx",
1200 [](ModuleOp module, llvm::raw_ostream &os) {
1203 [](mlir::DialectRegistry ®istry) {
1205 .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)
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.