CIRCT 24.0.0git
Loading...
Searching...
No Matches
CalyxEmitter.cpp
Go to the documentation of this file.
1//===- CalyxEmitter.cpp - Calyx dialect to .futil emitter -----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This implements an emitter for the native Calyx language, which uses
10// .futil as an alias.
11//
12//===----------------------------------------------------------------------===//
13
19#include "circt/Support/LLVM.h"
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"
27#include <bitset>
28#include <string>
29
30using namespace circt;
31using namespace calyx;
32using namespace mlir;
33
34namespace {
35
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"; }
59
60/// A list of integer attributes supported by the native Calyx compiler.
61constexpr std::array<StringRef, 7> integerAttributes{
62 "external", "static", "share", "bound",
63 "write_together", "read_together", "pos",
64};
65
66/// A list of boolean attributes supported by the native Calyx compiler.
67constexpr std::array<StringRef, 12> booleanAttributes{
68 "clk", "reset", "go", "done", "generated", "precious",
69 "toplevel", "stable", "nointerface", "inline", "state_share", "data",
70};
71
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("."));
78 }
79 return std::nullopt;
80 }
81
82 return identifier;
83}
84
85/// Determines whether the given identifier is a valid Calyx attribute.
86static bool isValidCalyxAttribute(StringRef identifier) {
87
88 return llvm::find(integerAttributes, identifier) != integerAttributes.end() ||
89 llvm::find(booleanAttributes, identifier) != booleanAttributes.end();
90}
91
92/// A tracker to determine which libraries should be imported for a given
93/// program.
94struct ImportTracker {
95public:
96 /// Returns the list of library names used for in this program.
97 /// E.g. if `primitives/core.futil` is used, returns { "core" }.
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))
102 // It is not a primitive.
103 continue;
104 auto libraryName = getLibraryFor(&op);
105 if (failed(libraryName))
106 return WalkResult::interrupt();
107 usedLibraries.insert(*libraryName);
108 }
109 return WalkResult::advance();
110 });
111 if (walkRes.wasInterrupted())
112 return failure();
113 return usedLibraries;
114 }
115
116private:
117 /// Returns the library name for a given Operation Type.
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";
123 return {sCompile};
124 })
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";
130 return {sCore};
131 })
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 =
137 "binary_operators";
138 return {sBinaryOperators};
139 })
140 .Case<MemoryOp>([&](auto op) -> FailureOr<StringRef> {
141 static constexpr std::string_view sMemories = "memories/comb";
142 return {sMemories};
143 })
144 .Case<SeqMemoryOp>([&](auto op) -> FailureOr<StringRef> {
145 static constexpr std::string_view sMemories = "memories/seq";
146 return {sMemories};
147 })
148 .Case<ConstantOp>([&](auto op) -> FailureOr<StringRef> {
149 static constexpr std::string_view sFloat = "float";
150 return {sFloat};
151 })
152 .Case<AddFOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
153 static constexpr std::string_view sFloatingPoint = "float/addFN";
154 return {sFloatingPoint};
155 })
156 .Case<MulFOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
157 static constexpr std::string_view sFloatingPoint = "float/mulFN";
158 return {sFloatingPoint};
159 })
160 .Case<CompareFOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
161 static constexpr std::string_view sFloatingPoint = "float/compareFN";
162 return {sFloatingPoint};
163 })
164 .Case<FpToIntOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
165 static constexpr std::string_view sFloatingPoint = "float/fpToInt";
166 return {sFloatingPoint};
167 })
168 .Case<IntToFpOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
169 static constexpr std::string_view sFloatingPoint = "float/intToFp";
170 return {sFloatingPoint};
171 })
172 .Case<DivSqrtOpIEEE754>([&](auto op) -> FailureOr<StringRef> {
173 static constexpr std::string_view sFloatingPoint = "float/divSqrtFN";
174 return {sFloatingPoint};
175 })
176 .Default([&](auto op) {
177 auto diag = op->emitOpError() << "not supported for emission";
178 return diag;
179 });
180 }
181 /// Maintains a unique list of libraries used throughout the lifetime of the
182 /// tracker.
183 llvm::SmallSet<StringRef, 4> usedLibraries;
184};
185
186//===----------------------------------------------------------------------===//
187// Emitter
188//===----------------------------------------------------------------------===//
189
190/// An emitter for Calyx dialect operations to .futil output.
191struct Emitter {
192 Emitter(llvm::raw_ostream &os) : os(os) {}
193 LogicalResult finalize();
194
195 // Indentation
196 raw_ostream &indent() { return os.indent(currentIndent); }
197 void addIndent() { currentIndent += 2; }
198 void reduceIndent() {
199 assert(currentIndent >= 2 && "Unintended indentation wrap");
200 currentIndent -= 2;
201 }
202
203 // Module emission
204 void emitModule(ModuleOp op);
205
206 // Metadata emission for the Cider debugger.
207 void emitCiderMetadata(mlir::ModuleOp op) {
208 auto metadata = op->getAttrOfType<ArrayAttr>("calyx.metadata");
209 if (!metadata)
210 return;
211
212 constexpr std::string_view metadataIdentifier = "metadata";
213 os << endl() << metadataIdentifier << space() << metadataLBrace();
214
215 for (auto sourceLoc : llvm::enumerate(metadata)) {
216 // <index>: <source-location>\n
217 os << std::to_string(sourceLoc.index()) << colon();
218 os << cast<StringAttr>(sourceLoc.value()).getValue() << endl();
219 }
220
221 os << metadataRBrace();
222 }
223
224 /// Import emission.
225 LogicalResult emitImports(ModuleOp op) {
226 auto emitImport = [&](StringRef library) {
227 // Libraries share a common relative path:
228 // primitives/<library-name>.futil
229 os << "import " << quote() << "primitives/" << library << period()
230 << "futil" << quote() << semicolonEndL();
231 };
232
233 auto libraryNames = importTracker.getLibraryNames(op);
234 if (failed(libraryNames))
235 return failure();
236
237 for (StringRef library : *libraryNames)
238 emitImport(library);
239
240 return success();
241 }
242
243 // Component emission
244 void emitComponent(ComponentInterface op);
245 void emitComponentPorts(ComponentInterface op);
246
247 // HWModuleExtern emission
248 void emitPrimitiveExtern(hw::HWModuleExternOp op);
249 void emitPrimitivePorts(hw::HWModuleExternOp op);
250
251 // Instance emission
252 void emitInstance(InstanceOp op);
253
254 // Primitive emission
255 void emitPrimitive(PrimitiveOp op);
256
257 // Wires emission
258 void emitWires(WiresOp op);
259
260 // Group emission
261 void emitGroup(GroupInterface group);
262
263 // Control emission
264 void emitControl(ControlOp control);
265
266 // Assignment emission
267 void emitAssignment(AssignOp op);
268
269 // Enable emission
270 void emitEnable(EnableOp enable);
271
272 // Register emission
273 void emitRegister(RegisterOp reg);
274
275 // Emit undefined op
276 void emitUndef(UndefLibOp op);
277
278 // Memory emission
279 void emitMemory(MemoryOp memory);
280
281 // Seq Memory emission
282 void emitSeqMemory(SeqMemoryOp memory);
283
284 // Invoke emission
285 void emitInvoke(InvokeOp invoke);
286
287 // Floating point Constant emission
288 void emitConstant(ConstantOp constant);
289
290 // Emits a library primitive with template parameters based on all in- and
291 // output ports.
292 // e.g.:
293 // $f.in0, $f.in1, $f.in2, $f.out : calyx.std_foo "f" : i1, i2, i3, i4
294 // emits:
295 // f = std_foo(1, 2, 3, 4);
296 void emitLibraryPrimTypedByAllPorts(Operation *op);
297
298 // Emits a library primitive with a single template parameter based on the
299 // first input port.
300 // e.g.:
301 // $f.in0, $f.in1, $f.out : calyx.std_foo "f" : i32, i32, i1
302 // emits:
303 // f = std_foo(32);
304 void emitLibraryPrimTypedByFirstInputPort(Operation *op);
305
306 // Emits a library primitive with a single template parameter based on the
307 // first output port.
308 // e.g.:
309 // $f.in0, $f.in1, $f.out : calyx.std_foo "f" : i32, i32, i1
310 // emits:
311 // f = std_foo(1);
312 void emitLibraryPrimTypedByFirstOutputPort(
313 Operation *op, std::optional<StringRef> calyxLibName = {});
314
315 // Emits a library floating point primitives
316 void emitLibraryFloatingPoint(Operation *op);
317
318private:
319 /// Used to track which imports are required for this program.
320 ImportTracker importTracker;
321
322 /// Emit an error and remark that emission failed.
323 InFlightDiagnostic emitError(Operation *op, const Twine &message) {
324 encounteredError = true;
325 return op->emitError(message);
326 }
327
328 /// Emit an error and remark that emission failed.
329 InFlightDiagnostic emitOpError(Operation *op, const Twine &message) {
330 encounteredError = true;
331 return op->emitOpError(message);
332 }
333
334 /// Calyx attributes are emitted in one of the two following formats:
335 /// (1) @<attribute-name>(<attribute-value>), e.g. `@go`, `@bound(5)`.
336 /// (2) <"<attribute-name>"=<attribute-value>>, e.g. `<"static"=1>`.
337 ///
338 /// Since ports are structural in nature and not operations, an
339 /// extra boolean value is added to determine whether this is a port of the
340 /// given operation.
341 ///
342 /// By default, this generates format (1) but can generate format (2) if
343 /// `at_format` is false.
344 std::string getAttribute(Operation *op, NamedAttribute attr, bool isPort,
345 bool atFormat) {
346
347 std::optional<StringRef> identifierOpt = getCalyxAttrIdentifier(attr);
348 // Verify this is a Calyx attribute
349 if (!identifierOpt.has_value())
350 return "";
351
352 StringRef identifier = *identifierOpt;
353 // Verify this attribute is supported for emission.
354 if (!isValidCalyxAttribute(identifier))
355 return "";
356
357 std::string output;
358 llvm::raw_string_ostream buffer(output);
359 buffer.reserveExtraSpace(32);
360
361 bool isBooleanAttribute =
362 llvm::find(booleanAttributes, identifier) != booleanAttributes.end();
363
364 if (isa<UnitAttr>(attr.getValue())) {
365 assert(isBooleanAttribute &&
366 "Non-boolean attributes must provide an integer value.");
367 if (!atFormat) {
368 buffer << quote() << identifier << quote() << equals() << "1";
369 } else {
370 buffer << addressSymbol() << identifier;
371 }
372 } else if (auto intAttr = dyn_cast<IntegerAttr>(attr.getValue())) {
373 APInt value = intAttr.getValue();
374 if (!atFormat) {
375 buffer << quote() << identifier << quote() << equals() << value;
376 } else {
377 buffer << addressSymbol() << identifier;
378 // The only time we may omit the value is when it is a Boolean attribute
379 // with value 1.
380 if (!isBooleanAttribute || intAttr.getValue() != 1) {
381 // Retrieve the unsigned representation of the value.
382 SmallVector<char, 4> s;
383 value.toStringUnsigned(s, /*Radix=*/10);
384 buffer << LParen() << s << RParen();
385 }
386 }
387 }
388 return buffer.str();
389 }
390
391 /// Emits the attributes of a dictionary. If the `attributes` dictionary is
392 /// not nullptr, we assume this is for a port.
393 std::string getAttributes(Operation *op, bool atFormat,
394 DictionaryAttr attributes = nullptr) {
395 bool isPort = attributes != nullptr;
396 bool atLeastOne = false;
397
398 if (!isPort)
399 attributes = op->getAttrDictionary();
400
401 std::string calyxAttributes;
402 llvm::raw_string_ostream buf(calyxAttributes);
403
404 if (!atFormat)
405 buf << LAngleBracket();
406
407 for (auto &attr : attributes) {
408 // If the output
409 if (auto out = getAttribute(op, attr, isPort, atFormat); !out.empty()) {
410 buf << out;
411 atLeastOne = true;
412 buf << (atFormat ? space() : comma());
413 }
414 }
415
416 if (atLeastOne) {
417 auto out = buf.str();
418 // Remove the last character which is an extra space or comma.
419 out.pop_back();
420 out.append(atFormat ? space() : RAngleBracket());
421 return out;
422 }
423
424 return "";
425 }
426
427 /// Helper function for emitting a Calyx section. It emits the body in the
428 /// following format:
429 /// {
430 /// <body>
431 /// }
432 template <typename Func>
433 void emitCalyxBody(Func emitBody) {
434 os << space() << LBraceEndL();
435 addIndent();
436 emitBody();
437 reduceIndent();
438 indent() << RBraceEndL();
439 }
440
441 /// Emits a Calyx section.
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);
449 }
450
451 /// Helper function for emitting combinational operations.
452 template <typename CombinationalOp>
453 void emitCombinationalValue(CombinationalOp op, StringRef logicalSymbol) {
454 auto inputs = op.getInputs();
455 os << LParen();
456 for (size_t i = 0, e = inputs.size(); i != e; ++i) {
457 emitValue(inputs[i], /*isIndented=*/false);
458 if (i + 1 == e)
459 continue;
460 os << space() << logicalSymbol << space();
461 }
462 os << RParen();
463 }
464
465 void emitCycleValue(CycleOp op) {
466 os << "%";
467 if (op.getEnd().has_value()) {
468 os << LSquare();
469 os << op.getStart() << ":" << op.getEnd();
470 os << RSquare();
471 } else {
472 os << op.getStart();
473 }
474 }
475
476 /// Emits the value of a guard or assignment.
477 void emitValue(Value value, bool isIndented) {
478 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
479 // Emit component block argument.
480 StringAttr portName = getPortInfo(blockArg).name;
481 (isIndented ? indent() : os) << portName.getValue();
482 return;
483 }
484
485 auto *definingOp = value.getDefiningOp();
486 assert(definingOp && "Value does not have a defining operation.");
487
488 TypeSwitch<Operation *>(definingOp)
489 .Case<CellInterface>([&](auto cell) {
490 // A cell port should be defined as <instance-name>.<port-name>
491 (isIndented ? indent() : os)
492 << cell.instanceName() << period() << cell.portName(value);
493 })
494 .Case<hw::ConstantOp>([&](auto op) {
495 // A constant is defined as <bit-width>'<base><value>, where the base
496 // is `b` (binary), `o` (octal), `h` hexadecimal, or `d` (decimal).
497 APInt value = op.getValue();
498 auto &stream = isIndented ? indent() : os;
499 bool isNegative = value.isNegative();
500 StringRef base = isNegative ? "b" : "d";
501
502 stream << std::to_string(value.getBitWidth()) << apostrophe() << base;
503
504 if (isNegative) {
505 SmallString<8> str;
506 value.toStringUnsigned(str, /*Radix=*/2);
507 stream << str;
508 } else {
509 value.print(stream, /*isSigned=*/false);
510 }
511 })
512 .Case<comb::AndOp>([&](auto op) { emitCombinationalValue(op, "&"); })
513 .Case<comb::OrOp>([&](auto op) { emitCombinationalValue(op, "|"); })
514 .Case<comb::XorOp>([&](auto op) {
515 // The XorOp is a bit different, since the Combinational dialect
516 // uses it to represent binary not.
517 if (!op.isBinaryNot()) {
518 emitOpError(op, "Only supporting Binary Not for XOR.");
519 return;
520 }
521 // The LHS is the value to be negated, and the RHS is a constant with
522 // all ones (guaranteed by isBinaryNot).
523 os << exclamationMark();
524 emitValue(op.getInputs()[0], /*isIndented=*/false);
525 })
526 .Case<CycleOp>([&](auto op) { emitCycleValue(op); })
527 .Default(
528 [&](auto op) { emitOpError(op, "not supported for emission"); });
529 }
530
531 /// Emits a port for a Group.
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()
537 << space() << equals() << space();
538 if (op.getGuard()) {
539 emitValue(op.getGuard(), /*isIndented=*/false);
540 os << questionMark();
541 }
542 emitValue(op.getSrc(), /*isIndented=*/false);
543 os << semicolonEndL();
544 }
545
546 /// Recursively emits the Calyx control.
547 void emitCalyxControl(Block *body) {
548 Operation *parent = body->getParentOp();
549 assert((isa<ControlOp>(parent) || parent->hasTrait<ControlLike>()) &&
550 "This should only be used to emit Calyx Control structures.");
551
552 // Check to see if this is a stand-alone EnableOp, i.e.
553 // calyx.control { calyx.enable @G }
554 if (auto enable = dyn_cast<EnableOp>(parent)) {
555 emitEnable(enable);
556 // Early return since an EnableOp has no body.
557 return;
558 }
559 // Attribute dictionary is always prepended for a control operation.
560 auto prependAttributes = [&](Operation *op, StringRef sym) {
561 return (getAttributes(op, /*atFormat=*/true) + sym).str();
562 };
563
564 for (auto &&op : *body) {
565
566 TypeSwitch<Operation *>(&op)
567 .Case<SeqOp>([&](auto op) {
568 emitCalyxSection(prependAttributes(op, "seq"),
569 [&]() { emitCalyxControl(op.getBodyBlock()); });
570 })
571 .Case<StaticSeqOp>([&](auto op) {
572 emitCalyxSection(prependAttributes(op, "static seq"),
573 [&]() { emitCalyxControl(op.getBodyBlock()); });
574 })
575 .Case<ParOp>([&](auto op) {
576 emitCalyxSection(prependAttributes(op, "par"),
577 [&]() { emitCalyxControl(op.getBodyBlock()); });
578 })
579 .Case<WhileOp>([&](auto op) {
580 indent() << prependAttributes(op, "while ");
581 emitValue(op.getCond(), /*isIndented=*/false);
582
583 if (auto groupName = op.getGroupName())
584 os << " with " << *groupName;
585
586 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
587 })
588 .Case<IfOp>([&](auto op) {
589 indent() << prependAttributes(op, "if ");
590 emitValue(op.getCond(), /*isIndented=*/false);
591
592 if (auto groupName = op.getGroupName())
593 os << " with " << *groupName;
594
595 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
596 if (op.elseBodyExists())
597 emitCalyxSection("else",
598 [&]() { emitCalyxControl(op.getElseBody()); });
599 })
600 .Case<StaticIfOp>([&](auto op) {
601 indent() << prependAttributes(op, "static if ");
602 emitValue(op.getCond(), /*isIndented=*/false);
603
604 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
605 if (op.elseBodyExists())
606 emitCalyxSection("else",
607 [&]() { emitCalyxControl(op.getElseBody()); });
608 })
609 .Case<RepeatOp>([&](auto op) {
610 indent() << prependAttributes(op, "repeat ");
611 os << op.getCount();
612
613 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
614 })
615 .Case<StaticRepeatOp>([&](auto op) {
616 indent() << prependAttributes(op, "static repeat ");
617 os << op.getCount();
618
619 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
620 })
621 .Case<StaticParOp>([&](auto op) {
622 emitCalyxSection(prependAttributes(op, "static par"),
623 [&]() { emitCalyxControl(op.getBodyBlock()); });
624 })
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.");
629 });
630 }
631 }
632
633 /// The stream we are emitting into.
634 llvm::raw_ostream &os;
635
636 /// Whether we have encountered any errors during emission.
637 bool encounteredError = false;
638
639 /// Current level of indentation. See `indent()` and
640 /// `addIndent()`/`reduceIndent()`.
641 unsigned currentIndent = 0;
642};
643
644} // end anonymous namespace
645
646LogicalResult Emitter::finalize() { return failure(encounteredError); }
647
648/// Emit an entire program.
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);
655 else
656 emitOpError(&bodyOp, "Unexpected op");
657 }
658}
659
660/// Emit a component.
661void Emitter::emitComponent(ComponentInterface op) {
662 std::string combinationalPrefix = op.isComb() ? "comb " : "";
663
664 indent() << combinationalPrefix << "component " << op.getName()
665 << getAttributes(op, /*atFormat=*/false, nullptr);
666 // Emit the ports.
667 emitComponentPorts(op);
668 os << space() << LBraceEndL();
669 addIndent();
670 WiresOp wires;
671 ControlOp control;
672
673 // Emit cells.
674 emitCalyxSection("cells", [&]() {
675 for (auto &&bodyOp : *op.getBodyBlock()) {
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) { /*Do nothing*/ })
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); })
694 .Case<MuxLibOp>(
695 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
696 .Case<MultPipeLibOp>(
697 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
698 .Case<RemUPipeLibOp, DivUPipeLibOp>([&](auto op) {
699 emitLibraryPrimTypedByFirstOutputPort(
700 op, /*calyxLibName=*/{"std_div_pipe"});
701 })
702 .Case<RemSPipeLibOp, DivSPipeLibOp>([&](auto op) {
703 emitLibraryPrimTypedByFirstOutputPort(
704 op, /*calyxLibName=*/{"std_sdiv_pipe"});
705 })
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");
711 });
712 }
713 });
714
715 emitWires(wires);
716 emitControl(control);
717 reduceIndent();
718 os << RBraceEndL();
719}
720
721/// Emit the ports of a component.
722void Emitter::emitComponentPorts(ComponentInterface op) {
723 auto emitPorts = [&](auto ports) {
724 os << LParen();
725 for (size_t i = 0, e = ports.size(); i < e; ++i) {
726 const PortInfo &port = ports[i];
727
728 // We only care about the bit width in the emitted .futil file.
729 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
730 os << getAttributes(op, /*atFormat=*/true, port.attributes)
731 << port.name.getValue() << colon() << bitWidth;
732
733 if (i + 1 < e)
734 os << comma();
735 }
736 os << RParen();
737 };
738 emitPorts(op.getInputPortInfo());
739 os << arrow();
740 emitPorts(op.getOutputPortInfo());
741}
742
743/// Emit a primitive extern
744void Emitter::emitPrimitiveExtern(hw::HWModuleExternOp op) {
745 Attribute filename = op->getAttrDictionary().get("filename");
746 indent() << "extern " << filename << space() << LBraceEndL();
747 addIndent();
748 indent() << "primitive " << op.getName();
749
750 if (!op.getParameters().empty()) {
751 os << LSquare();
752 llvm::interleaveComma(op.getParameters(), os, [&](Attribute param) {
753 auto paramAttr = cast<hw::ParamDeclAttr>(param);
754 os << paramAttr.getName().str();
755 });
756 os << RSquare();
757 }
758 os << getAttributes(op, /*atFormat=*/false);
759 // Emit the ports.
760 emitPrimitivePorts(op);
761 os << semicolonEndL();
762 reduceIndent();
763 os << RBraceEndL();
764}
765
766/// Emit the ports of a component.
767void Emitter::emitPrimitivePorts(hw::HWModuleExternOp op) {
768 auto emitPorts = [&](auto ports, bool isInput) {
769 auto e = static_cast<size_t>(std::distance(ports.begin(), ports.end()));
770 os << LParen();
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)));
776
777 os << getAttributes(op, /*atFormat=*/true, portAttr)
778 << port.name.getValue() << colon();
779 // We only care about the bit width in the emitted .futil file.
780 // Emit parameterized or non-parameterized bit width.
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();
785 } else {
786 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
787 os << bitWidth;
788 }
789
790 if (i < e - 1)
791 os << comma();
792 }
793 os << RParen();
794 };
795 hw::ModulePortInfo ports(op.getPortList());
796 emitPorts(ports.getInputs(), true);
797 os << arrow();
798 emitPorts(ports.getOutputs(), false);
799}
800
801void Emitter::emitInstance(InstanceOp op) {
802 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
803 << space() << equals() << space() << op.getComponentName()
804 << LParen() << RParen() << semicolonEndL();
805}
806
807void Emitter::emitPrimitive(PrimitiveOp op) {
808 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
809 << space() << equals() << space() << op.getPrimitiveName()
810 << LParen();
811
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();
820 } else {
821 llvm_unreachable("Primitive parameter type not supported");
822 }
823 });
824 }
825
826 os << RParen() << semicolonEndL();
827}
828
829void Emitter::emitRegister(RegisterOp reg) {
830 size_t bitWidth = reg.getIn().getType().getIntOrFloatBitWidth();
831 indent() << getAttributes(reg, /*atFormat=*/true) << reg.instanceName()
832 << space() << equals() << space() << "std_reg" << LParen()
833 << std::to_string(bitWidth) << RParen() << semicolonEndL();
834}
835
836void Emitter::emitUndef(UndefLibOp op) {
837 size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
838 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
839 << space() << equals() << space() << "undef" << LParen()
840 << std::to_string(bitwidth) << RParen() << semicolonEndL();
841}
842
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.");
848 return;
849 }
850 indent() << getAttributes(memory, /*atFormat=*/true) << memory.instanceName()
851 << space() << equals() << space() << "std_mem_d"
852 << std::to_string(dimension) << LParen() << memory.getWidth()
853 << comma();
854 for (Attribute size : memory.getSizes()) {
855 APInt memSize = cast<IntegerAttr>(size).getValue();
856 memSize.print(os, /*isSigned=*/false);
857 os << comma();
858 }
859
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, /*isSigned=*/false);
864 if (i + 1 == e)
865 continue;
866 os << comma();
867 }
868 os << RParen() << semicolonEndL();
869}
870
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.");
876 return;
877 }
878 bool isRef = !memory->hasAttr("external");
879 indent();
880 if (isRef)
881 os << "ref ";
882 os << getAttributes(memory, /*atFormat=*/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, /*isSigned=*/false);
888 os << comma();
889 }
890
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, /*isSigned=*/false);
895 if (i + 1 == e)
896 continue;
897 os << comma();
898 }
899 os << RParen() << semicolonEndL();
900}
901
902void Emitter::emitInvoke(InvokeOp invoke) {
903 StringRef callee = invoke.getCallee();
904 indent() << "invoke " << callee;
905 auto refCellsMap = invoke.getRefCellsMap();
906 if (!refCellsMap.empty()) {
907 os << "[";
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();
912 auto externalMem =
913 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
914 os << refCellName << " = " << externalMem;
915 });
916 });
917 os << "]";
918 }
919 ArrayAttr portNames = invoke.getPortNames();
920 ArrayAttr inputNames = invoke.getInputNames();
921 /// Because the ports of all components of calyx.invoke are inside a (),
922 /// here the input and output ports are divided, inputs and outputs store
923 /// the connections for a subset of input and output ports of the instance.
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();
930 /// Classify the connection of ports,here's an example. calyx.invoke
931 /// @r(%r.in = %id.out, %out = %r.out) -> (i32, i32) %r.in = %id.out will be
932 /// stored in inputs, because %.r.in is the input port of the component, and
933 /// %out = %r.out will be stored in outputs, because %r.out is the output
934 /// port of the component, which is a bit different from calyx's native
935 /// compiler. Later on, the classified connection relations are outputted
936 /// uniformly and converted to calyx's native compiler format.
937 StringRef inputMapKey = portName.drop_front(2 + callee.size());
938 if (portName.substr(1, callee.size()) == callee) {
939 // If the input to the port is a number.
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;
947 continue;
948 }
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();
953 }
954 /// Emit inputs
955 os << LParen();
956 llvm::interleaveComma(inputsMap, os, [&](const auto &iter) {
957 os << iter.getKey() << " = " << iter.getValue();
958 });
959 os << RParen();
960 /// Emit outputs
961 os << LParen();
962 llvm::interleaveComma(outputsMap, os, [&](const auto &iter) {
963 os << iter.getKey() << " = " << iter.getValue();
964 });
965 os << RParen() << semicolonEndL();
966}
967
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.instanceName() << space() << equals() << space()
977 << "std_float_const";
978 // Currently defaults to IEEE-754 representation [1].
979 // [1]: https://github.com/calyxir/calyx/blob/main/primitives/float.futil
980 static constexpr int32_t IEEE754 = 0;
981 os << LParen() << std::to_string(IEEE754) << comma() << floatBits << comma()
982 << std::to_string(doubleValue) << RParen() << semicolonEndL();
983}
984
985/// Calling getName() on a calyx operation will return "calyx.${opname}". This
986/// function returns whatever is left after the first '.' in the string,
987/// removing the 'calyx' prefix.
988static StringRef removeCalyxPrefix(StringRef s) { return s.split(".").second; }
989
990void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
991 auto cell = cast<CellInterface>(op);
992 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
993 << space() << equals() << space()
994 << removeCalyxPrefix(op->getName().getStringRef()) << LParen();
995 llvm::interleaveComma(op->getResults(), os, [&](auto res) {
996 os << std::to_string(res.getType().getIntOrFloatBitWidth());
997 });
998 os << RParen() << semicolonEndL();
999}
1000
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, /*atFormat=*/true) << cell.instanceName()
1006 << space() << equals() << space() << removeCalyxPrefix(opName)
1007 << LParen() << bitWidth << RParen() << semicolonEndL();
1008}
1009
1010void Emitter::emitLibraryPrimTypedByFirstOutputPort(
1011 Operation *op, std::optional<StringRef> calyxLibName) {
1012 auto cell = cast<CellInterface>(op);
1013 unsigned bitWidth =
1014 cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1015 StringRef opName = op->getName().getStringRef();
1016 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
1017 << space() << equals() << space()
1018 << (calyxLibName ? *calyxLibName : removeCalyxPrefix(opName))
1019 << LParen() << bitWidth << RParen() << semicolonEndL();
1020}
1021
1022unsigned getFPBitWidth(CellInterface &cell) {
1023 if (isa<IntToFpOpIEEE754>(cell.getOperation())) {
1024 // `std_intToFp` has the float point value in the first output port.
1025 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1026 }
1027
1028 auto inputPorts = cell.getInputPorts();
1029 assert(inputPorts.size() >= 2 && "There should be at least two input ports");
1030
1031 // magic number for the index of `left/right` input port for
1032 // `AddF`/`MulF`/`CompareF`; `in` input port for `FpToInt`.
1033 size_t inputPortIndex = inputPorts.size() - 2;
1034 return cell.getInputPorts()[inputPortIndex].getType().getIntOrFloatBitWidth();
1035}
1036
1037unsigned getIntWidth(Operation *op, CellInterface &cell, bool isIntToFp) {
1038 auto inputPorts = cell.getInputPorts();
1039 assert(inputPorts.size() >= 2);
1040
1041 size_t integerIndex = inputPorts.size() - 2;
1042 if (!isIntToFp) {
1043 // FpToInt case: output width determines integer width.
1044 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1045 }
1046 return cell.getInputPorts()[integerIndex].getType().getIntOrFloatBitWidth();
1047}
1048
1049void Emitter::emitLibraryFloatingPoint(Operation *op) {
1050 auto cell = cast<CellInterface>(op);
1051
1052 unsigned fpBitWidth = getFPBitWidth(cell);
1053 unsigned expWidth, sigWidth;
1054 switch (fpBitWidth) {
1055 case 16:
1056 expWidth = 5;
1057 sigWidth = 11;
1058 break;
1059 case 32:
1060 expWidth = 8;
1061 sigWidth = 24;
1062 break;
1063 case 64:
1064 expWidth = 11;
1065 sigWidth = 53;
1066 break;
1067 case 128:
1068 expWidth = 15;
1069 sigWidth = 113;
1070 break;
1071 default:
1072 op->emitError("The supported bitwidths are 16, 32, 64, and 128");
1073 return;
1074 }
1075
1076 std::string opName;
1077 if (auto fpOp = dyn_cast<calyx::FloatingPointOpInterface>(op)) {
1078 opName = fpOp.getCalyxLibraryName();
1079 }
1080
1081 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
1082 << space() << equals() << space() << opName << LParen();
1083
1084 if (isa<calyx::IntToFpOpIEEE754>(op)) {
1085 unsigned intWidth = getIntWidth(op, cell, /*isIntToFp=*/true);
1086 os << intWidth << comma();
1087 }
1088
1089 os << expWidth << comma() << sigWidth << comma() << fpBitWidth;
1090
1091 if (auto fpToIntOp = dyn_cast<calyx::FpToIntOpIEEE754>(op)) {
1092 unsigned intWidth = getIntWidth(op, cell, /*isIntToFp=*/false);
1093 os << comma() << intWidth;
1094 }
1095
1096 os << RParen() << semicolonEndL();
1097}
1098
1099void Emitter::emitAssignment(AssignOp op) {
1100
1101 emitValue(op.getDest(), /*isIndented=*/true);
1102 os << space() << equals() << space();
1103 if (op.getGuard()) {
1104 emitValue(op.getGuard(), /*isIndented=*/false);
1105 os << questionMark();
1106 }
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()) {
1115 SmallString<8> str;
1116 auto intValue = value.bitcastToAPInt();
1117 intValue.toStringUnsigned(str, /*Radix=*/2);
1118 os << std::to_string(intValue.getBitWidth()) << apostrophe() << "b" << str
1119 << semicolonEndL();
1120 return;
1121 }
1122 }
1123 emitValue(op.getSrc(), /*isIndented=*/false);
1124 os << semicolonEndL();
1125}
1126
1127void Emitter::emitWires(WiresOp op) {
1128 emitCalyxSection("wires", [&]() {
1129 for (auto &&bodyOp : *op.getBodyBlock()) {
1130 TypeSwitch<Operation *>(&bodyOp)
1131 .Case<GroupInterface>([&](auto op) { emitGroup(op); })
1132 .Case<AssignOp>([&](auto op) { emitAssignment(op); })
1133 .Case<hw::ConstantOp, calyx::ConstantOp, comb::AndOp, comb::OrOp,
1134 comb::XorOp, CycleOp>([&](auto op) { /* Do nothing. */ })
1135 .Default([&](auto op) {
1136 emitOpError(op, "not supported for emission inside wires section");
1137 });
1138 }
1139 });
1140}
1141
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>(
1150 [&](auto op) { /* Do nothing. */ })
1151 .Default([&](auto op) {
1152 emitOpError(op, "not supported for emission inside group.");
1153 });
1154 }
1155 };
1156 std::string prefix;
1157 if (isa<StaticGroupOp>(group)) {
1158 auto staticGroup = cast<StaticGroupOp>(group);
1159 prefix = llvm::formatv("static<{0}> group", staticGroup.getLatency());
1160 } else {
1161 prefix = isa<CombGroupOp>(group) ? "comb group" : "group";
1162 }
1163 auto groupHeader =
1164 (group.symName().getValue() + getAttributes(group, /*atFormat=*/false))
1165 .str();
1166 emitCalyxSection(prefix, emitGroupBody, groupHeader);
1167}
1168
1169void Emitter::emitEnable(EnableOp enable) {
1170 indent() << getAttributes(enable, /*atFormat=*/true) << enable.getGroupName()
1171 << semicolonEndL();
1172}
1173
1174void Emitter::emitControl(ControlOp control) {
1175 // A valid Calyx program does not necessarily need a control section.
1176 if (control == nullptr)
1177 return;
1178 emitCalyxSection("control",
1179 [&]() { emitCalyxControl(control.getBodyBlock()); });
1180}
1181
1182//===----------------------------------------------------------------------===//
1183// Driver
1184//===----------------------------------------------------------------------===//
1185
1186// Emit the specified Calyx circuit into the given output stream.
1187mlir::LogicalResult circt::calyx::exportCalyx(mlir::ModuleOp module,
1188 llvm::raw_ostream &os) {
1189 Emitter emitter(os);
1190 if (failed(emitter.emitImports(module)))
1191 return failure();
1192 emitter.emitModule(module);
1193 emitter.emitCiderMetadata(module);
1194 return emitter.finalize();
1195}
1196
1198 static mlir::TranslateFromMLIRRegistration toCalyx(
1199 "export-calyx", "export Calyx",
1200 [](ModuleOp module, llvm::raw_ostream &os) {
1201 return exportCalyx(module, os);
1202 },
1203 [](mlir::DialectRegistry &registry) {
1204 registry
1205 .insert<calyx::CalyxDialect, comb::CombDialect, hw::HWDialect>();
1206 });
1207}
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.
Definition CalyxOps.cpp:136
false return failure()
Parse an optional keyword or string and set instance into 'result'.
if(parseOptionalKeywordOrOptionalString(parser, result.rawName, found)) return failure()
return success()
PortInfo getPortInfo(BlockArgument arg)
Returns port information for the block argument provided.
Definition CalyxOps.cpp:143
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)
Definition seq.py:21
DictionaryAttr attributes
Definition CalyxOps.h:93