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