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