CIRCT 21.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
499 (isIndented ? indent() : os)
500 << std::to_string(value.getBitWidth()) << apostrophe() << "d";
501 // We currently default to the decimal representation.
502 value.print(os, /*isSigned=*/false);
503 })
504 .Case<comb::AndOp>([&](auto op) { emitCombinationalValue(op, "&"); })
505 .Case<comb::OrOp>([&](auto op) { emitCombinationalValue(op, "|"); })
506 .Case<comb::XorOp>([&](auto op) {
507 // The XorOp is a bit different, since the Combinational dialect
508 // uses it to represent binary not.
509 if (!op.isBinaryNot()) {
510 emitOpError(op, "Only supporting Binary Not for XOR.");
511 return;
512 }
513 // The LHS is the value to be negated, and the RHS is a constant with
514 // all ones (guaranteed by isBinaryNot).
515 os << exclamationMark();
516 emitValue(op.getInputs()[0], /*isIndented=*/false);
517 })
518 .Case<CycleOp>([&](auto op) { emitCycleValue(op); })
519 .Default(
520 [&](auto op) { emitOpError(op, "not supported for emission"); });
521 }
522
523 /// Emits a port for a Group.
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()
529 << space() << equals() << space();
530 if (op.getGuard()) {
531 emitValue(op.getGuard(), /*isIndented=*/false);
532 os << questionMark();
533 }
534 emitValue(op.getSrc(), /*isIndented=*/false);
535 os << semicolonEndL();
536 }
537
538 /// Recursively emits the Calyx control.
539 void emitCalyxControl(Block *body) {
540 Operation *parent = body->getParentOp();
541 assert((isa<ControlOp>(parent) || parent->hasTrait<ControlLike>()) &&
542 "This should only be used to emit Calyx Control structures.");
543
544 // Check to see if this is a stand-alone EnableOp, i.e.
545 // calyx.control { calyx.enable @G }
546 if (auto enable = dyn_cast<EnableOp>(parent)) {
547 emitEnable(enable);
548 // Early return since an EnableOp has no body.
549 return;
550 }
551 // Attribute dictionary is always prepended for a control operation.
552 auto prependAttributes = [&](Operation *op, StringRef sym) {
553 return (getAttributes(op, /*atFormat=*/true) + sym).str();
554 };
555
556 for (auto &&op : *body) {
557
558 TypeSwitch<Operation *>(&op)
559 .Case<SeqOp>([&](auto op) {
560 emitCalyxSection(prependAttributes(op, "seq"),
561 [&]() { emitCalyxControl(op.getBodyBlock()); });
562 })
563 .Case<StaticSeqOp>([&](auto op) {
564 emitCalyxSection(prependAttributes(op, "static seq"),
565 [&]() { emitCalyxControl(op.getBodyBlock()); });
566 })
567 .Case<ParOp>([&](auto op) {
568 emitCalyxSection(prependAttributes(op, "par"),
569 [&]() { emitCalyxControl(op.getBodyBlock()); });
570 })
571 .Case<WhileOp>([&](auto op) {
572 indent() << prependAttributes(op, "while ");
573 emitValue(op.getCond(), /*isIndented=*/false);
574
575 if (auto groupName = op.getGroupName())
576 os << " with " << *groupName;
577
578 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
579 })
580 .Case<IfOp>([&](auto op) {
581 indent() << prependAttributes(op, "if ");
582 emitValue(op.getCond(), /*isIndented=*/false);
583
584 if (auto groupName = op.getGroupName())
585 os << " with " << *groupName;
586
587 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
588 if (op.elseBodyExists())
589 emitCalyxSection("else",
590 [&]() { emitCalyxControl(op.getElseBody()); });
591 })
592 .Case<StaticIfOp>([&](auto op) {
593 indent() << prependAttributes(op, "static if ");
594 emitValue(op.getCond(), /*isIndented=*/false);
595
596 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
597 if (op.elseBodyExists())
598 emitCalyxSection("else",
599 [&]() { emitCalyxControl(op.getElseBody()); });
600 })
601 .Case<RepeatOp>([&](auto op) {
602 indent() << prependAttributes(op, "repeat ");
603 os << op.getCount();
604
605 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
606 })
607 .Case<StaticRepeatOp>([&](auto op) {
608 indent() << prependAttributes(op, "static repeat ");
609 os << op.getCount();
610
611 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
612 })
613 .Case<StaticParOp>([&](auto op) {
614 emitCalyxSection(prependAttributes(op, "static par"),
615 [&]() { emitCalyxControl(op.getBodyBlock()); });
616 })
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.");
621 });
622 }
623 }
624
625 /// The stream we are emitting into.
626 llvm::raw_ostream &os;
627
628 /// Whether we have encountered any errors during emission.
629 bool encounteredError = false;
630
631 /// Current level of indentation. See `indent()` and
632 /// `addIndent()`/`reduceIndent()`.
633 unsigned currentIndent = 0;
634};
635
636} // end anonymous namespace
637
638LogicalResult Emitter::finalize() { return failure(encounteredError); }
639
640/// Emit an entire program.
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);
647 else
648 emitOpError(&bodyOp, "Unexpected op");
649 }
650}
651
652/// Emit a component.
653void Emitter::emitComponent(ComponentInterface op) {
654 std::string combinationalPrefix = op.isComb() ? "comb " : "";
655
656 indent() << combinationalPrefix << "component " << op.getName()
657 << getAttributes(op, /*atFormat=*/false, nullptr);
658 // Emit the ports.
659 emitComponentPorts(op);
660 os << space() << LBraceEndL();
661 addIndent();
662 WiresOp wires;
663 ControlOp control;
664
665 // Emit cells.
666 emitCalyxSection("cells", [&]() {
667 for (auto &&bodyOp : *op.getBodyBlock()) {
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) { /*Do nothing*/ })
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); })
686 .Case<MuxLibOp>(
687 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
688 .Case<MultPipeLibOp>(
689 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
690 .Case<RemUPipeLibOp, DivUPipeLibOp>([&](auto op) {
691 emitLibraryPrimTypedByFirstOutputPort(
692 op, /*calyxLibName=*/{"std_div_pipe"});
693 })
694 .Case<RemSPipeLibOp, DivSPipeLibOp>([&](auto op) {
695 emitLibraryPrimTypedByFirstOutputPort(
696 op, /*calyxLibName=*/{"std_sdiv_pipe"});
697 })
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");
703 });
704 }
705 });
706
707 emitWires(wires);
708 emitControl(control);
709 reduceIndent();
710 os << RBraceEndL();
711}
712
713/// Emit the ports of a component.
714void Emitter::emitComponentPorts(ComponentInterface op) {
715 auto emitPorts = [&](auto ports) {
716 os << LParen();
717 for (size_t i = 0, e = ports.size(); i < e; ++i) {
718 const PortInfo &port = ports[i];
719
720 // We only care about the bit width in the emitted .futil file.
721 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
722 os << getAttributes(op, /*atFormat=*/true, port.attributes)
723 << port.name.getValue() << colon() << bitWidth;
724
725 if (i + 1 < e)
726 os << comma();
727 }
728 os << RParen();
729 };
730 emitPorts(op.getInputPortInfo());
731 os << arrow();
732 emitPorts(op.getOutputPortInfo());
733}
734
735/// Emit a primitive extern
736void Emitter::emitPrimitiveExtern(hw::HWModuleExternOp op) {
737 Attribute filename = op->getAttrDictionary().get("filename");
738 indent() << "extern " << filename << space() << LBraceEndL();
739 addIndent();
740 indent() << "primitive " << op.getName();
741
742 if (!op.getParameters().empty()) {
743 os << LSquare();
744 llvm::interleaveComma(op.getParameters(), os, [&](Attribute param) {
745 auto paramAttr = cast<hw::ParamDeclAttr>(param);
746 os << paramAttr.getName().str();
747 });
748 os << RSquare();
749 }
750 os << getAttributes(op, /*atFormat=*/false);
751 // Emit the ports.
752 emitPrimitivePorts(op);
753 os << semicolonEndL();
754 reduceIndent();
755 os << RBraceEndL();
756}
757
758/// Emit the ports of a component.
759void Emitter::emitPrimitivePorts(hw::HWModuleExternOp op) {
760 auto emitPorts = [&](auto ports, bool isInput) {
761 auto e = static_cast<size_t>(std::distance(ports.begin(), ports.end()));
762 os << LParen();
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)));
768
769 os << getAttributes(op, /*atFormat=*/true, portAttr)
770 << port.name.getValue() << colon();
771 // We only care about the bit width in the emitted .futil file.
772 // Emit parameterized or non-parameterized bit width.
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();
777 } else {
778 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
779 os << bitWidth;
780 }
781
782 if (i < e - 1)
783 os << comma();
784 }
785 os << RParen();
786 };
787 hw::ModulePortInfo ports(op.getPortList());
788 emitPorts(ports.getInputs(), true);
789 os << arrow();
790 emitPorts(ports.getOutputs(), false);
791}
792
793void Emitter::emitInstance(InstanceOp op) {
794 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
795 << space() << equals() << space() << op.getComponentName()
796 << LParen() << RParen() << semicolonEndL();
797}
798
799void Emitter::emitPrimitive(PrimitiveOp op) {
800 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
801 << space() << equals() << space() << op.getPrimitiveName()
802 << LParen();
803
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();
812 } else {
813 llvm_unreachable("Primitive parameter type not supported");
814 }
815 });
816 }
817
818 os << RParen() << semicolonEndL();
819}
820
821void Emitter::emitRegister(RegisterOp reg) {
822 size_t bitWidth = reg.getIn().getType().getIntOrFloatBitWidth();
823 indent() << getAttributes(reg, /*atFormat=*/true) << reg.instanceName()
824 << space() << equals() << space() << "std_reg" << LParen()
825 << std::to_string(bitWidth) << RParen() << semicolonEndL();
826}
827
828void Emitter::emitUndef(UndefLibOp op) {
829 size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
830 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
831 << space() << equals() << space() << "undef" << LParen()
832 << std::to_string(bitwidth) << RParen() << semicolonEndL();
833}
834
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.");
840 return;
841 }
842 indent() << getAttributes(memory, /*atFormat=*/true) << memory.instanceName()
843 << space() << equals() << space() << "std_mem_d"
844 << std::to_string(dimension) << LParen() << memory.getWidth()
845 << comma();
846 for (Attribute size : memory.getSizes()) {
847 APInt memSize = cast<IntegerAttr>(size).getValue();
848 memSize.print(os, /*isSigned=*/false);
849 os << comma();
850 }
851
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, /*isSigned=*/false);
856 if (i + 1 == e)
857 continue;
858 os << comma();
859 }
860 os << RParen() << semicolonEndL();
861}
862
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.");
868 return;
869 }
870 bool isRef = !memory->hasAttr("external");
871 indent();
872 if (isRef)
873 os << "ref ";
874 os << getAttributes(memory, /*atFormat=*/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, /*isSigned=*/false);
880 os << comma();
881 }
882
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, /*isSigned=*/false);
887 if (i + 1 == e)
888 continue;
889 os << comma();
890 }
891 os << RParen() << semicolonEndL();
892}
893
894void Emitter::emitInvoke(InvokeOp invoke) {
895 StringRef callee = invoke.getCallee();
896 indent() << "invoke " << callee;
897 auto refCellsMap = invoke.getRefCellsMap();
898 if (!refCellsMap.empty()) {
899 os << "[";
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();
904 auto externalMem =
905 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
906 os << refCellName << " = " << externalMem;
907 });
908 });
909 os << "]";
910 }
911 ArrayAttr portNames = invoke.getPortNames();
912 ArrayAttr inputNames = invoke.getInputNames();
913 /// Because the ports of all components of calyx.invoke are inside a (),
914 /// here the input and output ports are divided, inputs and outputs store
915 /// the connections for a subset of input and output ports of the instance.
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();
922 /// Classify the connection of ports,here's an example. calyx.invoke
923 /// @r(%r.in = %id.out, %out = %r.out) -> (i32, i32) %r.in = %id.out will be
924 /// stored in inputs, because %.r.in is the input port of the component, and
925 /// %out = %r.out will be stored in outputs, because %r.out is the output
926 /// port of the component, which is a bit different from calyx's native
927 /// compiler. Later on, the classified connection relations are outputted
928 /// uniformly and converted to calyx's native compiler format.
929 StringRef inputMapKey = portName.drop_front(2 + callee.size());
930 if (portName.substr(1, callee.size()) == callee) {
931 // If the input to the port is a number.
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;
939 continue;
940 }
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();
945 }
946 /// Emit inputs
947 os << LParen();
948 llvm::interleaveComma(inputsMap, os, [&](const auto &iter) {
949 os << iter.getKey() << " = " << iter.getValue();
950 });
951 os << RParen();
952 /// Emit outputs
953 os << LParen();
954 llvm::interleaveComma(outputsMap, os, [&](const auto &iter) {
955 os << iter.getKey() << " = " << iter.getValue();
956 });
957 os << RParen() << semicolonEndL();
958}
959
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";
970 // Currently defaults to IEEE-754 representation [1].
971 // [1]: https://github.com/calyxir/calyx/blob/main/primitives/float.futil
972 static constexpr int32_t IEEE754 = 0;
973 os << LParen() << std::to_string(IEEE754) << comma() << floatBits << comma()
974 << std::to_string(doubleValue) << RParen() << semicolonEndL();
975}
976
977/// Calling getName() on a calyx operation will return "calyx.${opname}". This
978/// function returns whatever is left after the first '.' in the string,
979/// removing the 'calyx' prefix.
980static StringRef removeCalyxPrefix(StringRef s) { return s.split(".").second; }
981
982void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
983 auto cell = cast<CellInterface>(op);
984 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
985 << space() << equals() << space()
986 << removeCalyxPrefix(op->getName().getStringRef()) << LParen();
987 llvm::interleaveComma(op->getResults(), os, [&](auto res) {
988 os << std::to_string(res.getType().getIntOrFloatBitWidth());
989 });
990 os << RParen() << semicolonEndL();
991}
992
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, /*atFormat=*/true) << cell.instanceName()
998 << space() << equals() << space() << removeCalyxPrefix(opName)
999 << LParen() << bitWidth << RParen() << semicolonEndL();
1000}
1001
1002void Emitter::emitLibraryPrimTypedByFirstOutputPort(
1003 Operation *op, std::optional<StringRef> calyxLibName) {
1004 auto cell = cast<CellInterface>(op);
1005 unsigned bitWidth =
1006 cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1007 StringRef opName = op->getName().getStringRef();
1008 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
1009 << space() << equals() << space()
1010 << (calyxLibName ? *calyxLibName : removeCalyxPrefix(opName))
1011 << LParen() << bitWidth << RParen() << semicolonEndL();
1012}
1013
1014unsigned getFPBitWidth(CellInterface &cell) {
1015 if (isa<IntToFpOpIEEE754>(cell.getOperation())) {
1016 // `std_intToFp` has the float point value in the first output port.
1017 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1018 }
1019
1020 auto inputPorts = cell.getInputPorts();
1021 assert(inputPorts.size() >= 2 && "There should be at least two input ports");
1022
1023 // magic number for the index of `left/right` input port for
1024 // `AddF`/`MulF`/`CompareF`; `in` input port for `FpToInt`.
1025 size_t inputPortIndex = inputPorts.size() - 2;
1026 return cell.getInputPorts()[inputPortIndex].getType().getIntOrFloatBitWidth();
1027}
1028
1029unsigned getIntWidth(Operation *op, CellInterface &cell, bool isIntToFp) {
1030 auto inputPorts = cell.getInputPorts();
1031 assert(inputPorts.size() >= 2);
1032
1033 size_t integerIndex = inputPorts.size() - 2;
1034 if (!isIntToFp) {
1035 // FpToInt case: output width determines integer width.
1036 return cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
1037 }
1038 return cell.getInputPorts()[integerIndex].getType().getIntOrFloatBitWidth();
1039}
1040
1041void Emitter::emitLibraryFloatingPoint(Operation *op) {
1042 auto cell = cast<CellInterface>(op);
1043
1044 unsigned fpBitWidth = getFPBitWidth(cell);
1045 unsigned expWidth, sigWidth;
1046 switch (fpBitWidth) {
1047 case 16:
1048 expWidth = 5;
1049 sigWidth = 11;
1050 break;
1051 case 32:
1052 expWidth = 8;
1053 sigWidth = 24;
1054 break;
1055 case 64:
1056 expWidth = 11;
1057 sigWidth = 53;
1058 break;
1059 case 128:
1060 expWidth = 15;
1061 sigWidth = 113;
1062 break;
1063 default:
1064 op->emitError("The supported bitwidths are 16, 32, 64, and 128");
1065 return;
1066 }
1067
1068 std::string opName;
1069 if (auto fpOp = dyn_cast<calyx::FloatingPointOpInterface>(op)) {
1070 opName = fpOp.getCalyxLibraryName();
1071 }
1072
1073 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
1074 << space() << equals() << space() << opName << LParen();
1075
1076 if (isa<calyx::IntToFpOpIEEE754>(op)) {
1077 unsigned intWidth = getIntWidth(op, cell, /*isIntToFp=*/true);
1078 os << intWidth << comma();
1079 }
1080
1081 os << expWidth << comma() << sigWidth << comma() << fpBitWidth;
1082
1083 if (auto fpToIntOp = dyn_cast<calyx::FpToIntOpIEEE754>(op)) {
1084 unsigned intWidth = getIntWidth(op, cell, /*isIntToFp=*/false);
1085 os << comma() << intWidth;
1086 }
1087
1088 os << RParen() << semicolonEndL();
1089}
1090
1091void Emitter::emitAssignment(AssignOp op) {
1092
1093 emitValue(op.getDest(), /*isIndented=*/true);
1094 os << space() << equals() << space();
1095 if (op.getGuard()) {
1096 emitValue(op.getGuard(), /*isIndented=*/false);
1097 os << questionMark();
1098 }
1099 emitValue(op.getSrc(), /*isIndented=*/false);
1100 os << semicolonEndL();
1101}
1102
1103void Emitter::emitWires(WiresOp op) {
1104 emitCalyxSection("wires", [&]() {
1105 for (auto &&bodyOp : *op.getBodyBlock()) {
1106 TypeSwitch<Operation *>(&bodyOp)
1107 .Case<GroupInterface>([&](auto op) { emitGroup(op); })
1108 .Case<AssignOp>([&](auto op) { emitAssignment(op); })
1109 .Case<hw::ConstantOp, calyx::ConstantOp, comb::AndOp, comb::OrOp,
1110 comb::XorOp, CycleOp>([&](auto op) { /* Do nothing. */ })
1111 .Default([&](auto op) {
1112 emitOpError(op, "not supported for emission inside wires section");
1113 });
1114 }
1115 });
1116}
1117
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>(
1126 [&](auto op) { /* Do nothing. */ })
1127 .Default([&](auto op) {
1128 emitOpError(op, "not supported for emission inside group.");
1129 });
1130 }
1131 };
1132 std::string prefix;
1133 if (isa<StaticGroupOp>(group)) {
1134 auto staticGroup = cast<StaticGroupOp>(group);
1135 prefix = llvm::formatv("static<{0}> group", staticGroup.getLatency());
1136 } else {
1137 prefix = isa<CombGroupOp>(group) ? "comb group" : "group";
1138 }
1139 auto groupHeader =
1140 (group.symName().getValue() + getAttributes(group, /*atFormat=*/false))
1141 .str();
1142 emitCalyxSection(prefix, emitGroupBody, groupHeader);
1143}
1144
1145void Emitter::emitEnable(EnableOp enable) {
1146 indent() << getAttributes(enable, /*atFormat=*/true) << enable.getGroupName()
1147 << semicolonEndL();
1148}
1149
1150void Emitter::emitControl(ControlOp control) {
1151 // A valid Calyx program does not necessarily need a control section.
1152 if (control == nullptr)
1153 return;
1154 emitCalyxSection("control",
1155 [&]() { emitCalyxControl(control.getBodyBlock()); });
1156}
1157
1158//===----------------------------------------------------------------------===//
1159// Driver
1160//===----------------------------------------------------------------------===//
1161
1162// Emit the specified Calyx circuit into the given output stream.
1163mlir::LogicalResult circt::calyx::exportCalyx(mlir::ModuleOp module,
1164 llvm::raw_ostream &os) {
1165 Emitter emitter(os);
1166 if (failed(emitter.emitImports(module)))
1167 return failure();
1168 emitter.emitModule(module);
1169 emitter.emitCiderMetadata(module);
1170 return emitter.finalize();
1171}
1172
1174 static mlir::TranslateFromMLIRRegistration toCalyx(
1175 "export-calyx", "export Calyx",
1176 [](ModuleOp module, llvm::raw_ostream &os) {
1177 return exportCalyx(module, os);
1178 },
1179 [](mlir::DialectRegistry &registry) {
1180 registry
1181 .insert<calyx::CalyxDialect, comb::CombDialect, hw::HWDialect>();
1182 });
1183}
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:135
static Block * getBodyBlock(FModuleLike mod)
Signals that the following operation is "control-like.".
Definition CalyxOps.h:42
PortInfo getPortInfo(BlockArgument arg)
Returns port information for the block argument provided.
Definition CalyxOps.cpp:142
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)
Definition seq.py:21
This holds information about the port for either a Component or Cell.
Definition CalyxOps.h:89
DictionaryAttr attributes
Definition CalyxOps.h:93
This holds a decoded list of input/inout and output ports for a module or instance.