CIRCT 20.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 .Default([&](auto op) {
165 auto diag = op->emitOpError() << "not supported for emission";
166 return diag;
167 });
168 }
169 /// Maintains a unique list of libraries used throughout the lifetime of the
170 /// tracker.
171 llvm::SmallSet<StringRef, 4> usedLibraries;
172};
173
174//===----------------------------------------------------------------------===//
175// Emitter
176//===----------------------------------------------------------------------===//
177
178/// An emitter for Calyx dialect operations to .futil output.
179struct Emitter {
180 Emitter(llvm::raw_ostream &os) : os(os) {}
181 LogicalResult finalize();
182
183 // Indentation
184 raw_ostream &indent() { return os.indent(currentIndent); }
185 void addIndent() { currentIndent += 2; }
186 void reduceIndent() {
187 assert(currentIndent >= 2 && "Unintended indentation wrap");
188 currentIndent -= 2;
189 }
190
191 // Module emission
192 void emitModule(ModuleOp op);
193
194 // Metadata emission for the Cider debugger.
195 void emitCiderMetadata(mlir::ModuleOp op) {
196 auto metadata = op->getAttrOfType<ArrayAttr>("calyx.metadata");
197 if (!metadata)
198 return;
199
200 constexpr std::string_view metadataIdentifier = "metadata";
201 os << endl() << metadataIdentifier << space() << metadataLBrace();
202
203 for (auto sourceLoc : llvm::enumerate(metadata)) {
204 // <index>: <source-location>\n
205 os << std::to_string(sourceLoc.index()) << colon();
206 os << cast<StringAttr>(sourceLoc.value()).getValue() << endl();
207 }
208
209 os << metadataRBrace();
210 }
211
212 /// Import emission.
213 LogicalResult emitImports(ModuleOp op) {
214 auto emitImport = [&](StringRef library) {
215 // Libraries share a common relative path:
216 // primitives/<library-name>.futil
217 os << "import " << quote() << "primitives/" << library << period()
218 << "futil" << quote() << semicolonEndL();
219 };
220
221 auto libraryNames = importTracker.getLibraryNames(op);
222 if (failed(libraryNames))
223 return failure();
224
225 for (StringRef library : *libraryNames)
226 emitImport(library);
227
228 return success();
229 }
230
231 // Component emission
232 void emitComponent(ComponentInterface op);
233 void emitComponentPorts(ComponentInterface op);
234
235 // HWModuleExtern emission
236 void emitPrimitiveExtern(hw::HWModuleExternOp op);
237 void emitPrimitivePorts(hw::HWModuleExternOp op);
238
239 // Instance emission
240 void emitInstance(InstanceOp op);
241
242 // Primitive emission
243 void emitPrimitive(PrimitiveOp op);
244
245 // Wires emission
246 void emitWires(WiresOp op);
247
248 // Group emission
249 void emitGroup(GroupInterface group);
250
251 // Control emission
252 void emitControl(ControlOp control);
253
254 // Assignment emission
255 void emitAssignment(AssignOp op);
256
257 // Enable emission
258 void emitEnable(EnableOp enable);
259
260 // Register emission
261 void emitRegister(RegisterOp reg);
262
263 // Emit undefined op
264 void emitUndef(UndefLibOp op);
265
266 // Memory emission
267 void emitMemory(MemoryOp memory);
268
269 // Seq Memory emission
270 void emitSeqMemory(SeqMemoryOp memory);
271
272 // Invoke emission
273 void emitInvoke(InvokeOp invoke);
274
275 // Floating point Constant emission
276 void emitConstant(ConstantOp constant);
277
278 // Emits a library primitive with template parameters based on all in- and
279 // output ports.
280 // e.g.:
281 // $f.in0, $f.in1, $f.in2, $f.out : calyx.std_foo "f" : i1, i2, i3, i4
282 // emits:
283 // f = std_foo(1, 2, 3, 4);
284 void emitLibraryPrimTypedByAllPorts(Operation *op);
285
286 // Emits a library primitive with a single template parameter based on the
287 // first input port.
288 // e.g.:
289 // $f.in0, $f.in1, $f.out : calyx.std_foo "f" : i32, i32, i1
290 // emits:
291 // f = std_foo(32);
292 void emitLibraryPrimTypedByFirstInputPort(Operation *op);
293
294 // Emits a library primitive with a single template parameter based on the
295 // first output port.
296 // e.g.:
297 // $f.in0, $f.in1, $f.out : calyx.std_foo "f" : i32, i32, i1
298 // emits:
299 // f = std_foo(1);
300 void emitLibraryPrimTypedByFirstOutputPort(
301 Operation *op, std::optional<StringRef> calyxLibName = {});
302
303 // Emits a library floating point primitives
304 void emitLibraryFloatingPoint(Operation *op);
305
306private:
307 /// Used to track which imports are required for this program.
308 ImportTracker importTracker;
309
310 /// Emit an error and remark that emission failed.
311 InFlightDiagnostic emitError(Operation *op, const Twine &message) {
312 encounteredError = true;
313 return op->emitError(message);
314 }
315
316 /// Emit an error and remark that emission failed.
317 InFlightDiagnostic emitOpError(Operation *op, const Twine &message) {
318 encounteredError = true;
319 return op->emitOpError(message);
320 }
321
322 /// Calyx attributes are emitted in one of the two following formats:
323 /// (1) @<attribute-name>(<attribute-value>), e.g. `@go`, `@bound(5)`.
324 /// (2) <"<attribute-name>"=<attribute-value>>, e.g. `<"static"=1>`.
325 ///
326 /// Since ports are structural in nature and not operations, an
327 /// extra boolean value is added to determine whether this is a port of the
328 /// given operation.
329 ///
330 /// By default, this generates format (1) but can generate format (2) if
331 /// `at_format` is false.
332 std::string getAttribute(Operation *op, NamedAttribute attr, bool isPort,
333 bool atFormat) {
334
335 std::optional<StringRef> identifierOpt = getCalyxAttrIdentifier(attr);
336 // Verify this is a Calyx attribute
337 if (!identifierOpt.has_value())
338 return "";
339
340 StringRef identifier = *identifierOpt;
341 // Verify this attribute is supported for emission.
342 if (!isValidCalyxAttribute(identifier))
343 return "";
344
345 std::string output;
346 llvm::raw_string_ostream buffer(output);
347 buffer.reserveExtraSpace(32);
348
349 bool isBooleanAttribute =
350 llvm::find(booleanAttributes, identifier) != booleanAttributes.end();
351
352 if (isa<UnitAttr>(attr.getValue())) {
353 assert(isBooleanAttribute &&
354 "Non-boolean attributes must provide an integer value.");
355 if (!atFormat) {
356 buffer << quote() << identifier << quote() << equals() << "1";
357 } else {
358 buffer << addressSymbol() << identifier;
359 }
360 } else if (auto intAttr = dyn_cast<IntegerAttr>(attr.getValue())) {
361 APInt value = intAttr.getValue();
362 if (!atFormat) {
363 buffer << quote() << identifier << quote() << equals() << value;
364 } else {
365 buffer << addressSymbol() << identifier;
366 // The only time we may omit the value is when it is a Boolean attribute
367 // with value 1.
368 if (!isBooleanAttribute || intAttr.getValue() != 1) {
369 // Retrieve the unsigned representation of the value.
370 SmallVector<char, 4> s;
371 value.toStringUnsigned(s, /*Radix=*/10);
372 buffer << LParen() << s << RParen();
373 }
374 }
375 }
376 return buffer.str();
377 }
378
379 /// Emits the attributes of a dictionary. If the `attributes` dictionary is
380 /// not nullptr, we assume this is for a port.
381 std::string getAttributes(Operation *op, bool atFormat,
382 DictionaryAttr attributes = nullptr) {
383 bool isPort = attributes != nullptr;
384 bool atLeastOne = false;
385
386 if (!isPort)
387 attributes = op->getAttrDictionary();
388
389 std::string calyxAttributes;
390 llvm::raw_string_ostream buf(calyxAttributes);
391
392 if (!atFormat)
393 buf << LAngleBracket();
394
395 for (auto &attr : attributes) {
396 // If the output
397 if (auto out = getAttribute(op, attr, isPort, atFormat); !out.empty()) {
398 buf << out;
399 atLeastOne = true;
400 buf << (atFormat ? space() : comma());
401 }
402 }
403
404 if (atLeastOne) {
405 auto out = buf.str();
406 // Remove the last character which is an extra space or comma.
407 out.pop_back();
408 out.append(atFormat ? space() : RAngleBracket());
409 return out;
410 }
411
412 return "";
413 }
414
415 /// Helper function for emitting a Calyx section. It emits the body in the
416 /// following format:
417 /// {
418 /// <body>
419 /// }
420 template <typename Func>
421 void emitCalyxBody(Func emitBody) {
422 os << space() << LBraceEndL();
423 addIndent();
424 emitBody();
425 reduceIndent();
426 indent() << RBraceEndL();
427 }
428
429 /// Emits a Calyx section.
430 template <typename Func>
431 void emitCalyxSection(StringRef sectionName, Func emitBody,
432 StringRef symbolName = "") {
433 indent() << sectionName;
434 if (!symbolName.empty())
435 os << space() << symbolName;
436 emitCalyxBody(emitBody);
437 }
438
439 /// Helper function for emitting combinational operations.
440 template <typename CombinationalOp>
441 void emitCombinationalValue(CombinationalOp op, StringRef logicalSymbol) {
442 auto inputs = op.getInputs();
443 os << LParen();
444 for (size_t i = 0, e = inputs.size(); i != e; ++i) {
445 emitValue(inputs[i], /*isIndented=*/false);
446 if (i + 1 == e)
447 continue;
448 os << space() << logicalSymbol << space();
449 }
450 os << RParen();
451 }
452
453 void emitCycleValue(CycleOp op) {
454 os << "%";
455 if (op.getEnd().has_value()) {
456 os << LSquare();
457 os << op.getStart() << ":" << op.getEnd();
458 os << RSquare();
459 } else {
460 os << op.getStart();
461 }
462 }
463
464 /// Emits the value of a guard or assignment.
465 void emitValue(Value value, bool isIndented) {
466 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
467 // Emit component block argument.
468 StringAttr portName = getPortInfo(blockArg).name;
469 (isIndented ? indent() : os) << portName.getValue();
470 return;
471 }
472
473 auto *definingOp = value.getDefiningOp();
474 assert(definingOp && "Value does not have a defining operation.");
475
476 TypeSwitch<Operation *>(definingOp)
477 .Case<CellInterface>([&](auto cell) {
478 // A cell port should be defined as <instance-name>.<port-name>
479 (isIndented ? indent() : os)
480 << cell.instanceName() << period() << cell.portName(value);
481 })
482 .Case<hw::ConstantOp>([&](auto op) {
483 // A constant is defined as <bit-width>'<base><value>, where the base
484 // is `b` (binary), `o` (octal), `h` hexadecimal, or `d` (decimal).
485 APInt value = op.getValue();
486
487 (isIndented ? indent() : os)
488 << std::to_string(value.getBitWidth()) << apostrophe() << "d";
489 // We currently default to the decimal representation.
490 value.print(os, /*isSigned=*/false);
491 })
492 .Case<comb::AndOp>([&](auto op) { emitCombinationalValue(op, "&"); })
493 .Case<comb::OrOp>([&](auto op) { emitCombinationalValue(op, "|"); })
494 .Case<comb::XorOp>([&](auto op) {
495 // The XorOp is a bit different, since the Combinational dialect
496 // uses it to represent binary not.
497 if (!op.isBinaryNot()) {
498 emitOpError(op, "Only supporting Binary Not for XOR.");
499 return;
500 }
501 // The LHS is the value to be negated, and the RHS is a constant with
502 // all ones (guaranteed by isBinaryNot).
503 os << exclamationMark();
504 emitValue(op.getInputs()[0], /*isIndented=*/false);
505 })
506 .Case<CycleOp>([&](auto op) { emitCycleValue(op); })
507 .Default(
508 [&](auto op) { emitOpError(op, "not supported for emission"); });
509 }
510
511 /// Emits a port for a Group.
512 template <typename OpTy>
513 void emitGroupPort(GroupInterface group, OpTy op, StringRef portHole) {
514 assert((isa<GroupGoOp>(op) || isa<GroupDoneOp>(op)) &&
515 "Required to be a group port.");
516 indent() << group.symName().getValue() << LSquare() << portHole << RSquare()
517 << space() << equals() << space();
518 if (op.getGuard()) {
519 emitValue(op.getGuard(), /*isIndented=*/false);
520 os << questionMark();
521 }
522 emitValue(op.getSrc(), /*isIndented=*/false);
523 os << semicolonEndL();
524 }
525
526 /// Recursively emits the Calyx control.
527 void emitCalyxControl(Block *body) {
528 Operation *parent = body->getParentOp();
529 assert((isa<ControlOp>(parent) || parent->hasTrait<ControlLike>()) &&
530 "This should only be used to emit Calyx Control structures.");
531
532 // Check to see if this is a stand-alone EnableOp, i.e.
533 // calyx.control { calyx.enable @G }
534 if (auto enable = dyn_cast<EnableOp>(parent)) {
535 emitEnable(enable);
536 // Early return since an EnableOp has no body.
537 return;
538 }
539 // Attribute dictionary is always prepended for a control operation.
540 auto prependAttributes = [&](Operation *op, StringRef sym) {
541 return (getAttributes(op, /*atFormat=*/true) + sym).str();
542 };
543
544 for (auto &&op : *body) {
545
546 TypeSwitch<Operation *>(&op)
547 .Case<SeqOp>([&](auto op) {
548 emitCalyxSection(prependAttributes(op, "seq"),
549 [&]() { emitCalyxControl(op.getBodyBlock()); });
550 })
551 .Case<StaticSeqOp>([&](auto op) {
552 emitCalyxSection(prependAttributes(op, "static seq"),
553 [&]() { emitCalyxControl(op.getBodyBlock()); });
554 })
555 .Case<ParOp>([&](auto op) {
556 emitCalyxSection(prependAttributes(op, "par"),
557 [&]() { emitCalyxControl(op.getBodyBlock()); });
558 })
559 .Case<WhileOp>([&](auto op) {
560 indent() << prependAttributes(op, "while ");
561 emitValue(op.getCond(), /*isIndented=*/false);
562
563 if (auto groupName = op.getGroupName())
564 os << " with " << *groupName;
565
566 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
567 })
568 .Case<IfOp>([&](auto op) {
569 indent() << prependAttributes(op, "if ");
570 emitValue(op.getCond(), /*isIndented=*/false);
571
572 if (auto groupName = op.getGroupName())
573 os << " with " << *groupName;
574
575 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
576 if (op.elseBodyExists())
577 emitCalyxSection("else",
578 [&]() { emitCalyxControl(op.getElseBody()); });
579 })
580 .Case<StaticIfOp>([&](auto op) {
581 indent() << prependAttributes(op, "static if ");
582 emitValue(op.getCond(), /*isIndented=*/false);
583
584 emitCalyxBody([&]() { emitCalyxControl(op.getThenBody()); });
585 if (op.elseBodyExists())
586 emitCalyxSection("else",
587 [&]() { emitCalyxControl(op.getElseBody()); });
588 })
589 .Case<RepeatOp>([&](auto op) {
590 indent() << prependAttributes(op, "repeat ");
591 os << op.getCount();
592
593 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
594 })
595 .Case<StaticRepeatOp>([&](auto op) {
596 indent() << prependAttributes(op, "static repeat ");
597 os << op.getCount();
598
599 emitCalyxBody([&]() { emitCalyxControl(op.getBodyBlock()); });
600 })
601 .Case<StaticParOp>([&](auto op) {
602 emitCalyxSection(prependAttributes(op, "static par"),
603 [&]() { emitCalyxControl(op.getBodyBlock()); });
604 })
605 .Case<EnableOp>([&](auto op) { emitEnable(op); })
606 .Case<InvokeOp>([&](auto op) { emitInvoke(op); })
607 .Default([&](auto op) {
608 emitOpError(op, "not supported for emission inside control.");
609 });
610 }
611 }
612
613 /// The stream we are emitting into.
614 llvm::raw_ostream &os;
615
616 /// Whether we have encountered any errors during emission.
617 bool encounteredError = false;
618
619 /// Current level of indentation. See `indent()` and
620 /// `addIndent()`/`reduceIndent()`.
621 unsigned currentIndent = 0;
622};
623
624} // end anonymous namespace
625
626LogicalResult Emitter::finalize() { return failure(encounteredError); }
627
628/// Emit an entire program.
629void Emitter::emitModule(ModuleOp op) {
630 for (auto &bodyOp : *op.getBody()) {
631 if (auto componentOp = dyn_cast<ComponentInterface>(bodyOp))
632 emitComponent(componentOp);
633 else if (auto hwModuleExternOp = dyn_cast<hw::HWModuleExternOp>(bodyOp))
634 emitPrimitiveExtern(hwModuleExternOp);
635 else
636 emitOpError(&bodyOp, "Unexpected op");
637 }
638}
639
640/// Emit a component.
641void Emitter::emitComponent(ComponentInterface op) {
642 std::string combinationalPrefix = op.isComb() ? "comb " : "";
643
644 indent() << combinationalPrefix << "component " << op.getName()
645 << getAttributes(op, /*atFormat=*/false, nullptr);
646 // Emit the ports.
647 emitComponentPorts(op);
648 os << space() << LBraceEndL();
649 addIndent();
650 WiresOp wires;
651 ControlOp control;
652
653 // Emit cells.
654 emitCalyxSection("cells", [&]() {
655 for (auto &&bodyOp : *op.getBodyBlock()) {
656 TypeSwitch<Operation *>(&bodyOp)
657 .Case<UndefLibOp>([&](auto op) { emitUndef(op); })
658 .Case<WiresOp>([&](auto op) { wires = op; })
659 .Case<ControlOp>([&](auto op) { control = op; })
660 .Case<InstanceOp>([&](auto op) { emitInstance(op); })
661 .Case<PrimitiveOp>([&](auto op) { emitPrimitive(op); })
662 .Case<RegisterOp>([&](auto op) { emitRegister(op); })
663 .Case<MemoryOp>([&](auto op) { emitMemory(op); })
664 .Case<SeqMemoryOp>([&](auto op) { emitSeqMemory(op); })
665 .Case<hw::ConstantOp>([&](auto op) { /*Do nothing*/ })
666 .Case<calyx::ConstantOp>([&](auto op) { emitConstant(op); })
667 .Case<SliceLibOp, PadLibOp, ExtSILibOp>(
668 [&](auto op) { emitLibraryPrimTypedByAllPorts(op); })
669 .Case<LtLibOp, GtLibOp, EqLibOp, NeqLibOp, GeLibOp, LeLibOp, SltLibOp,
670 SgtLibOp, SeqLibOp, SneqLibOp, SgeLibOp, SleLibOp, AddLibOp,
671 SubLibOp, ShruLibOp, RshLibOp, SrshLibOp, LshLibOp, AndLibOp,
672 NotLibOp, OrLibOp, XorLibOp, WireLibOp>(
673 [&](auto op) { emitLibraryPrimTypedByFirstInputPort(op); })
674 .Case<MuxLibOp>(
675 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
676 .Case<MultPipeLibOp>(
677 [&](auto op) { emitLibraryPrimTypedByFirstOutputPort(op); })
678 .Case<RemUPipeLibOp, DivUPipeLibOp>([&](auto op) {
679 emitLibraryPrimTypedByFirstOutputPort(
680 op, /*calyxLibName=*/{"std_div_pipe"});
681 })
682 .Case<RemSPipeLibOp, DivSPipeLibOp>([&](auto op) {
683 emitLibraryPrimTypedByFirstOutputPort(
684 op, /*calyxLibName=*/{"std_sdiv_pipe"});
685 })
686 .Case<AddFOpIEEE754, MulFOpIEEE754, CompareFOpIEEE754>(
687 [&](auto op) { emitLibraryFloatingPoint(op); })
688 .Default([&](auto op) {
689 emitOpError(op, "not supported for emission inside component");
690 });
691 }
692 });
693
694 emitWires(wires);
695 emitControl(control);
696 reduceIndent();
697 os << RBraceEndL();
698}
699
700/// Emit the ports of a component.
701void Emitter::emitComponentPorts(ComponentInterface op) {
702 auto emitPorts = [&](auto ports) {
703 os << LParen();
704 for (size_t i = 0, e = ports.size(); i < e; ++i) {
705 const PortInfo &port = ports[i];
706
707 // We only care about the bit width in the emitted .futil file.
708 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
709 os << getAttributes(op, /*atFormat=*/true, port.attributes)
710 << port.name.getValue() << colon() << bitWidth;
711
712 if (i + 1 < e)
713 os << comma();
714 }
715 os << RParen();
716 };
717 emitPorts(op.getInputPortInfo());
718 os << arrow();
719 emitPorts(op.getOutputPortInfo());
720}
721
722/// Emit a primitive extern
723void Emitter::emitPrimitiveExtern(hw::HWModuleExternOp op) {
724 Attribute filename = op->getAttrDictionary().get("filename");
725 indent() << "extern " << filename << space() << LBraceEndL();
726 addIndent();
727 indent() << "primitive " << op.getName();
728
729 if (!op.getParameters().empty()) {
730 os << LSquare();
731 llvm::interleaveComma(op.getParameters(), os, [&](Attribute param) {
732 auto paramAttr = cast<hw::ParamDeclAttr>(param);
733 os << paramAttr.getName().str();
734 });
735 os << RSquare();
736 }
737 os << getAttributes(op, /*atFormat=*/false);
738 // Emit the ports.
739 emitPrimitivePorts(op);
740 os << semicolonEndL();
741 reduceIndent();
742 os << RBraceEndL();
743}
744
745/// Emit the ports of a component.
746void Emitter::emitPrimitivePorts(hw::HWModuleExternOp op) {
747 auto emitPorts = [&](auto ports, bool isInput) {
748 auto e = static_cast<size_t>(std::distance(ports.begin(), ports.end()));
749 os << LParen();
750 auto type = op.getHWModuleType();
751 for (auto [i, port] : llvm::enumerate(ports)) {
752 DictionaryAttr portAttr = cast_or_null<DictionaryAttr>(
753 op.getPortAttrs(isInput ? type.getPortIdForInputId(i)
754 : type.getPortIdForOutputId(i)));
755
756 os << getAttributes(op, /*atFormat=*/true, portAttr)
757 << port.name.getValue() << colon();
758 // We only care about the bit width in the emitted .futil file.
759 // Emit parameterized or non-parameterized bit width.
760 if (hw::isParametricType(port.type)) {
761 hw::ParamDeclRefAttr bitWidth = dyn_cast<hw::ParamDeclRefAttr>(
762 cast<hw::IntType>(port.type).getWidth());
763 os << bitWidth.getName().str();
764 } else {
765 unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
766 os << bitWidth;
767 }
768
769 if (i < e - 1)
770 os << comma();
771 }
772 os << RParen();
773 };
774 hw::ModulePortInfo ports(op.getPortList());
775 emitPorts(ports.getInputs(), true);
776 os << arrow();
777 emitPorts(ports.getOutputs(), false);
778}
779
780void Emitter::emitInstance(InstanceOp op) {
781 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
782 << space() << equals() << space() << op.getComponentName()
783 << LParen() << RParen() << semicolonEndL();
784}
785
786void Emitter::emitPrimitive(PrimitiveOp op) {
787 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
788 << space() << equals() << space() << op.getPrimitiveName()
789 << LParen();
790
791 if (op.getParameters().has_value()) {
792 llvm::interleaveComma(*op.getParameters(), os, [&](Attribute param) {
793 auto paramAttr = cast<hw::ParamDeclAttr>(param);
794 auto value = paramAttr.getValue();
795 if (auto intAttr = dyn_cast<IntegerAttr>(value)) {
796 os << intAttr.getInt();
797 } else if (auto fpAttr = dyn_cast<FloatAttr>(value)) {
798 os << fpAttr.getValue().convertToFloat();
799 } else {
800 llvm_unreachable("Primitive parameter type not supported");
801 }
802 });
803 }
804
805 os << RParen() << semicolonEndL();
806}
807
808void Emitter::emitRegister(RegisterOp reg) {
809 size_t bitWidth = reg.getIn().getType().getIntOrFloatBitWidth();
810 indent() << getAttributes(reg, /*atFormat=*/true) << reg.instanceName()
811 << space() << equals() << space() << "std_reg" << LParen()
812 << std::to_string(bitWidth) << RParen() << semicolonEndL();
813}
814
815void Emitter::emitUndef(UndefLibOp op) {
816 size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
817 indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
818 << space() << equals() << space() << "undef" << LParen()
819 << std::to_string(bitwidth) << RParen() << semicolonEndL();
820}
821
822void Emitter::emitMemory(MemoryOp memory) {
823 size_t dimension = memory.getSizes().size();
824 if (dimension < 1 || dimension > 4) {
825 emitOpError(memory, "Only memories with dimensionality in range [1, 4] are "
826 "supported by the native Calyx compiler.");
827 return;
828 }
829 indent() << getAttributes(memory, /*atFormat=*/true) << memory.instanceName()
830 << space() << equals() << space() << "std_mem_d"
831 << std::to_string(dimension) << LParen() << memory.getWidth()
832 << comma();
833 for (Attribute size : memory.getSizes()) {
834 APInt memSize = cast<IntegerAttr>(size).getValue();
835 memSize.print(os, /*isSigned=*/false);
836 os << comma();
837 }
838
839 ArrayAttr addrSizes = memory.getAddrSizes();
840 for (size_t i = 0, e = addrSizes.size(); i != e; ++i) {
841 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
842 addrSize.print(os, /*isSigned=*/false);
843 if (i + 1 == e)
844 continue;
845 os << comma();
846 }
847 os << RParen() << semicolonEndL();
848}
849
850void Emitter::emitSeqMemory(SeqMemoryOp memory) {
851 size_t dimension = memory.getSizes().size();
852 if (dimension < 1 || dimension > 4) {
853 emitOpError(memory, "Only memories with dimensionality in range [1, 4] are "
854 "supported by the native Calyx compiler.");
855 return;
856 }
857 bool isRef = !memory->hasAttr("external");
858 indent();
859 if (isRef)
860 os << "ref ";
861 os << getAttributes(memory, /*atFormat=*/true) << memory.instanceName()
862 << space() << equals() << space() << "seq_mem_d"
863 << std::to_string(dimension) << LParen() << memory.getWidth() << comma();
864 for (Attribute size : memory.getSizes()) {
865 APInt memSize = cast<IntegerAttr>(size).getValue();
866 memSize.print(os, /*isSigned=*/false);
867 os << comma();
868 }
869
870 ArrayAttr addrSizes = memory.getAddrSizes();
871 for (size_t i = 0, e = addrSizes.size(); i != e; ++i) {
872 APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
873 addrSize.print(os, /*isSigned=*/false);
874 if (i + 1 == e)
875 continue;
876 os << comma();
877 }
878 os << RParen() << semicolonEndL();
879}
880
881void Emitter::emitInvoke(InvokeOp invoke) {
882 StringRef callee = invoke.getCallee();
883 indent() << "invoke " << callee;
884 auto refCellsMap = invoke.getRefCellsMap();
885 if (!refCellsMap.empty()) {
886 os << "[";
887 llvm::interleaveComma(refCellsMap, os, [&](Attribute attr) {
888 auto dictAttr = cast<DictionaryAttr>(attr);
889 llvm::interleaveComma(dictAttr, os, [&](NamedAttribute namedAttr) {
890 auto refCellName = namedAttr.getName().str();
891 auto externalMem =
892 cast<FlatSymbolRefAttr>(namedAttr.getValue()).getValue();
893 os << refCellName << " = " << externalMem;
894 });
895 });
896 os << "]";
897 }
898 ArrayAttr portNames = invoke.getPortNames();
899 ArrayAttr inputNames = invoke.getInputNames();
900 /// Because the ports of all components of calyx.invoke are inside a (),
901 /// here the input and output ports are divided, inputs and outputs store
902 /// the connections for a subset of input and output ports of the instance.
903 llvm::StringMap<std::string> inputsMap;
904 llvm::StringMap<std::string> outputsMap;
905 for (auto [portNameAttr, inputNameAttr, input] :
906 llvm::zip(portNames, inputNames, invoke.getInputs())) {
907 StringRef portName = cast<StringAttr>(portNameAttr).getValue();
908 StringRef inputName = cast<StringAttr>(inputNameAttr).getValue();
909 /// Classify the connection of ports,here's an example. calyx.invoke
910 /// @r(%r.in = %id.out, %out = %r.out) -> (i32, i32) %r.in = %id.out will be
911 /// stored in inputs, because %.r.in is the input port of the component, and
912 /// %out = %r.out will be stored in outputs, because %r.out is the output
913 /// port of the component, which is a bit different from calyx's native
914 /// compiler. Later on, the classified connection relations are outputted
915 /// uniformly and converted to calyx's native compiler format.
916 StringRef inputMapKey = portName.drop_front(2 + callee.size());
917 if (portName.substr(1, callee.size()) == callee) {
918 // If the input to the port is a number.
919 if (isa_and_nonnull<hw::ConstantOp>(input.getDefiningOp())) {
920 hw::ConstantOp constant = cast<hw::ConstantOp>(input.getDefiningOp());
921 APInt value = constant.getValue();
922 std::string mapValue = std::to_string(value.getBitWidth()) +
923 apostrophe().data() + "d" +
924 std::to_string(value.getZExtValue());
925 inputsMap[inputMapKey] = mapValue;
926 continue;
927 }
928 inputsMap[inputMapKey] = inputName.drop_front(1).str();
929 } else if (inputName.substr(1, callee.size()) == callee)
930 outputsMap[inputName.drop_front(2 + callee.size())] =
931 portName.drop_front(1).str();
932 }
933 /// Emit inputs
934 os << LParen();
935 llvm::interleaveComma(inputsMap, os, [&](const auto &iter) {
936 os << iter.getKey() << " = " << iter.getValue();
937 });
938 os << RParen();
939 /// Emit outputs
940 os << LParen();
941 llvm::interleaveComma(outputsMap, os, [&](const auto &iter) {
942 os << iter.getKey() << " = " << iter.getValue();
943 });
944 os << RParen() << semicolonEndL();
945}
946
947void Emitter::emitConstant(ConstantOp constantOp) {
948 TypedAttr attr = constantOp.getValueAttr();
949 assert(isa<FloatAttr>(attr) && "must be a floating point constant");
950 auto fltAttr = cast<FloatAttr>(attr);
951 APFloat value = fltAttr.getValue();
952 auto type = cast<FloatType>(fltAttr.getType());
953 double doubleValue = value.convertToDouble();
954 auto floatBits = value.getSizeInBits(type.getFloatSemantics());
955 indent() << constantOp.getName().str() << space() << equals() << space()
956 << "std_float_const";
957 // Currently defaults to IEEE-754 representation [1].
958 // [1]: https://github.com/calyxir/calyx/blob/main/primitives/float.futil
959 static constexpr int32_t IEEE754 = 0;
960 os << LParen() << std::to_string(IEEE754) << comma() << floatBits << comma()
961 << std::to_string(doubleValue) << RParen() << semicolonEndL();
962}
963
964/// Calling getName() on a calyx operation will return "calyx.${opname}". This
965/// function returns whatever is left after the first '.' in the string,
966/// removing the 'calyx' prefix.
967static StringRef removeCalyxPrefix(StringRef s) { return s.split(".").second; }
968
969void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
970 auto cell = cast<CellInterface>(op);
971 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
972 << space() << equals() << space()
973 << removeCalyxPrefix(op->getName().getStringRef()) << LParen();
974 llvm::interleaveComma(op->getResults(), os, [&](auto res) {
975 os << std::to_string(res.getType().getIntOrFloatBitWidth());
976 });
977 os << RParen() << semicolonEndL();
978}
979
980void Emitter::emitLibraryPrimTypedByFirstInputPort(Operation *op) {
981 auto cell = cast<CellInterface>(op);
982 unsigned bitWidth = cell.getInputPorts()[0].getType().getIntOrFloatBitWidth();
983 StringRef opName = op->getName().getStringRef();
984 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
985 << space() << equals() << space() << removeCalyxPrefix(opName)
986 << LParen() << bitWidth << RParen() << semicolonEndL();
987}
988
989void Emitter::emitLibraryPrimTypedByFirstOutputPort(
990 Operation *op, std::optional<StringRef> calyxLibName) {
991 auto cell = cast<CellInterface>(op);
992 unsigned bitWidth =
993 cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
994 StringRef opName = op->getName().getStringRef();
995 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
996 << space() << equals() << space()
997 << (calyxLibName ? *calyxLibName : removeCalyxPrefix(opName))
998 << LParen() << bitWidth << RParen() << semicolonEndL();
999}
1000
1001void Emitter::emitLibraryFloatingPoint(Operation *op) {
1002 auto cell = cast<CellInterface>(op);
1003 // magic number for the index of `left/right` input port
1004 size_t inputPortIndex = cell.getInputPorts().size() - 3;
1005 unsigned bitWidth =
1006 cell.getInputPorts()[inputPortIndex].getType().getIntOrFloatBitWidth();
1007 // Since Calyx interacts with HardFloat, we'll also only be using expWidth and
1008 // sigWidth. See
1009 // http://www.jhauser.us/arithmetic/HardFloat-1/doc/HardFloat-Verilog.html
1010 unsigned expWidth, sigWidth;
1011 switch (bitWidth) {
1012 case 16:
1013 expWidth = 5;
1014 sigWidth = 11;
1015 break;
1016 case 32:
1017 expWidth = 8;
1018 sigWidth = 24;
1019 break;
1020 case 64:
1021 expWidth = 11;
1022 sigWidth = 53;
1023 break;
1024 case 128:
1025 expWidth = 15;
1026 sigWidth = 113;
1027 break;
1028 default:
1029 op->emitError("The supported bitwidths are 16, 32, 64, and 128");
1030 return;
1031 }
1032
1033 std::string opName;
1034 if (auto fpOp = dyn_cast<calyx::FloatingPointOpInterface>(op)) {
1035 opName = fpOp.getCalyxLibraryName();
1036 }
1037 indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
1038 << space() << equals() << space() << opName << LParen() << expWidth
1039 << comma() << sigWidth << comma() << bitWidth << RParen()
1040 << semicolonEndL();
1041}
1042
1043void Emitter::emitAssignment(AssignOp op) {
1044
1045 emitValue(op.getDest(), /*isIndented=*/true);
1046 os << space() << equals() << space();
1047 if (op.getGuard()) {
1048 emitValue(op.getGuard(), /*isIndented=*/false);
1049 os << questionMark();
1050 }
1051 emitValue(op.getSrc(), /*isIndented=*/false);
1052 os << semicolonEndL();
1053}
1054
1055void Emitter::emitWires(WiresOp op) {
1056 emitCalyxSection("wires", [&]() {
1057 for (auto &&bodyOp : *op.getBodyBlock()) {
1058 TypeSwitch<Operation *>(&bodyOp)
1059 .Case<GroupInterface>([&](auto op) { emitGroup(op); })
1060 .Case<AssignOp>([&](auto op) { emitAssignment(op); })
1061 .Case<hw::ConstantOp, calyx::ConstantOp, comb::AndOp, comb::OrOp,
1062 comb::XorOp, CycleOp>([&](auto op) { /* Do nothing. */ })
1063 .Default([&](auto op) {
1064 emitOpError(op, "not supported for emission inside wires section");
1065 });
1066 }
1067 });
1068}
1069
1070void Emitter::emitGroup(GroupInterface group) {
1071 auto emitGroupBody = [&]() {
1072 for (auto &&bodyOp : *group.getBody()) {
1073 TypeSwitch<Operation *>(&bodyOp)
1074 .Case<AssignOp>([&](auto op) { emitAssignment(op); })
1075 .Case<GroupDoneOp>([&](auto op) { emitGroupPort(group, op, "done"); })
1076 .Case<GroupGoOp>([&](auto op) { emitGroupPort(group, op, "go"); })
1077 .Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp, CycleOp>(
1078 [&](auto op) { /* Do nothing. */ })
1079 .Default([&](auto op) {
1080 emitOpError(op, "not supported for emission inside group.");
1081 });
1082 }
1083 };
1084 std::string prefix;
1085 if (isa<StaticGroupOp>(group)) {
1086 auto staticGroup = cast<StaticGroupOp>(group);
1087 prefix = llvm::formatv("static<{0}> group", staticGroup.getLatency());
1088 } else {
1089 prefix = isa<CombGroupOp>(group) ? "comb group" : "group";
1090 }
1091 auto groupHeader =
1092 (group.symName().getValue() + getAttributes(group, /*atFormat=*/false))
1093 .str();
1094 emitCalyxSection(prefix, emitGroupBody, groupHeader);
1095}
1096
1097void Emitter::emitEnable(EnableOp enable) {
1098 indent() << getAttributes(enable, /*atFormat=*/true) << enable.getGroupName()
1099 << semicolonEndL();
1100}
1101
1102void Emitter::emitControl(ControlOp control) {
1103 // A valid Calyx program does not necessarily need a control section.
1104 if (control == nullptr)
1105 return;
1106 emitCalyxSection("control",
1107 [&]() { emitCalyxControl(control.getBodyBlock()); });
1108}
1109
1110//===----------------------------------------------------------------------===//
1111// Driver
1112//===----------------------------------------------------------------------===//
1113
1114// Emit the specified Calyx circuit into the given output stream.
1115mlir::LogicalResult circt::calyx::exportCalyx(mlir::ModuleOp module,
1116 llvm::raw_ostream &os) {
1117 Emitter emitter(os);
1118 if (failed(emitter.emitImports(module)))
1119 return failure();
1120 emitter.emitModule(module);
1121 emitter.emitCiderMetadata(module);
1122 return emitter.finalize();
1123}
1124
1126 static mlir::TranslateFromMLIRRegistration toCalyx(
1127 "export-calyx", "export Calyx",
1128 [](ModuleOp module, llvm::raw_ostream &os) {
1129 return exportCalyx(module, os);
1130 },
1131 [](mlir::DialectRegistry &registry) {
1132 registry
1133 .insert<calyx::CalyxDialect, comb::CombDialect, hw::HWDialect>();
1134 });
1135}
assert(baseType &&"element must be base type")
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.