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 << cast<StringAttr>(sourceLoc.value()).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 (isa<UnitAttr>(attr.getValue())) {
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 = dyn_cast<IntegerAttr>(attr.getValue())) {
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 = dyn_cast<BlockArgument>(value)) {
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 = cast<hw::ParamDeclAttr>(param);
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 = dyn_cast<hw::ParamDeclRefAttr>(
734  cast<hw::IntType>(port.type).getWidth());
735  os << bitWidth.getName().str();
736  } else {
737  unsigned int bitWidth = port.type.getIntOrFloatBitWidth();
738  os << bitWidth;
739  }
740 
741  if (i < e - 1)
742  os << comma();
743  }
744  os << RParen();
745  };
746  hw::ModulePortInfo ports(op.getPortList());
747  emitPorts(ports.getInputs(), true);
748  os << arrow();
749  emitPorts(ports.getOutputs(), false);
750 }
751 
752 void Emitter::emitInstance(InstanceOp op) {
753  indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
754  << space() << equals() << space() << op.getComponentName()
755  << LParen() << RParen() << semicolonEndL();
756 }
757 
758 void Emitter::emitPrimitive(PrimitiveOp op) {
759  indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
760  << space() << equals() << space() << op.getPrimitiveName()
761  << LParen();
762 
763  if (op.getParameters().has_value()) {
764  llvm::interleaveComma(*op.getParameters(), os, [&](Attribute param) {
765  auto paramAttr = cast<hw::ParamDeclAttr>(param);
766  auto value = paramAttr.getValue();
767  if (auto intAttr = dyn_cast<IntegerAttr>(value)) {
768  os << intAttr.getInt();
769  } else if (auto fpAttr = dyn_cast<FloatAttr>(value)) {
770  os << fpAttr.getValue().convertToFloat();
771  } else {
772  llvm_unreachable("Primitive parameter type not supported");
773  }
774  });
775  }
776 
777  os << RParen() << semicolonEndL();
778 }
779 
780 void Emitter::emitRegister(RegisterOp reg) {
781  size_t bitWidth = reg.getIn().getType().getIntOrFloatBitWidth();
782  indent() << getAttributes(reg, /*atFormat=*/true) << reg.instanceName()
783  << space() << equals() << space() << "std_reg" << LParen()
784  << std::to_string(bitWidth) << RParen() << semicolonEndL();
785 }
786 
787 void Emitter::emitUndef(UndefLibOp op) {
788  size_t bitwidth = op.getOut().getType().getIntOrFloatBitWidth();
789  indent() << getAttributes(op, /*atFormat=*/true) << op.instanceName()
790  << space() << equals() << space() << "undef" << LParen()
791  << std::to_string(bitwidth) << RParen() << semicolonEndL();
792 }
793 
794 void Emitter::emitMemory(MemoryOp memory) {
795  size_t dimension = memory.getSizes().size();
796  if (dimension < 1 || dimension > 4) {
797  emitOpError(memory, "Only memories with dimensionality in range [1, 4] are "
798  "supported by the native Calyx compiler.");
799  return;
800  }
801  indent() << getAttributes(memory, /*atFormat=*/true) << memory.instanceName()
802  << space() << equals() << space() << "std_mem_d"
803  << std::to_string(dimension) << LParen() << memory.getWidth()
804  << comma();
805  for (Attribute size : memory.getSizes()) {
806  APInt memSize = cast<IntegerAttr>(size).getValue();
807  memSize.print(os, /*isSigned=*/false);
808  os << comma();
809  }
810 
811  ArrayAttr addrSizes = memory.getAddrSizes();
812  for (size_t i = 0, e = addrSizes.size(); i != e; ++i) {
813  APInt addrSize = cast<IntegerAttr>(addrSizes[i]).getValue();
814  addrSize.print(os, /*isSigned=*/false);
815  if (i + 1 == e)
816  continue;
817  os << comma();
818  }
819  os << RParen() << semicolonEndL();
820 }
821 
822 void Emitter::emitSeqMemory(SeqMemoryOp 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() << "seq_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 
850 void Emitter::emitInvoke(InvokeOp invoke) {
851  StringRef callee = invoke.getCallee();
852  indent() << "invoke " << callee;
853  ArrayAttr portNames = invoke.getPortNames();
854  ArrayAttr inputNames = invoke.getInputNames();
855  /// Because the ports of all components of calyx.invoke are inside a (),
856  /// here the input and output ports are divided, inputs and outputs store
857  /// the connections for a subset of input and output ports of the instance.
858  llvm::StringMap<std::string> inputsMap;
859  llvm::StringMap<std::string> outputsMap;
860  for (auto [portNameAttr, inputNameAttr, input] :
861  llvm::zip(portNames, inputNames, invoke.getInputs())) {
862  StringRef portName = cast<StringAttr>(portNameAttr).getValue();
863  StringRef inputName = cast<StringAttr>(inputNameAttr).getValue();
864  /// Classify the connection of ports,here's an example. calyx.invoke
865  /// @r(%r.in = %id.out, %out = %r.out) -> (i32, i32) %r.in = %id.out will be
866  /// stored in inputs, because %.r.in is the input port of the component, and
867  /// %out = %r.out will be stored in outputs, because %r.out is the output
868  /// port of the component, which is a bit different from calyx's native
869  /// compiler. Later on, the classified connection relations are outputted
870  /// uniformly and converted to calyx's native compiler format.
871  StringRef inputMapKey = portName.drop_front(2 + callee.size());
872  if (portName.substr(1, callee.size()) == callee) {
873  // If the input to the port is a number.
874  if (isa_and_nonnull<hw::ConstantOp>(input.getDefiningOp())) {
875  hw::ConstantOp constant = cast<hw::ConstantOp>(input.getDefiningOp());
876  APInt value = constant.getValue();
877  std::string mapValue = std::to_string(value.getBitWidth()) +
878  apostrophe().data() + "d" +
879  std::to_string(value.getZExtValue());
880  inputsMap[inputMapKey] = mapValue;
881  continue;
882  }
883  inputsMap[inputMapKey] = inputName.drop_front(1).str();
884  } else if (inputName.substr(1, callee.size()) == callee)
885  outputsMap[inputName.drop_front(2 + callee.size())] =
886  portName.drop_front(1).str();
887  }
888  /// Emit inputs
889  os << LParen();
890  llvm::interleaveComma(inputsMap, os, [&](const auto &iter) {
891  os << iter.getKey() << " = " << iter.getValue();
892  });
893  os << RParen();
894  /// Emit outputs
895  os << LParen();
896  llvm::interleaveComma(outputsMap, os, [&](const auto &iter) {
897  os << iter.getKey() << " = " << iter.getValue();
898  });
899  os << RParen() << semicolonEndL();
900 }
901 
902 /// Calling getName() on a calyx operation will return "calyx.${opname}". This
903 /// function returns whatever is left after the first '.' in the string,
904 /// removing the 'calyx' prefix.
905 static StringRef removeCalyxPrefix(StringRef s) { return s.split(".").second; }
906 
907 void Emitter::emitLibraryPrimTypedByAllPorts(Operation *op) {
908  auto cell = cast<CellInterface>(op);
909  indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
910  << space() << equals() << space()
911  << removeCalyxPrefix(op->getName().getStringRef()) << LParen();
912  llvm::interleaveComma(op->getResults(), os, [&](auto res) {
913  os << std::to_string(res.getType().getIntOrFloatBitWidth());
914  });
915  os << RParen() << semicolonEndL();
916 }
917 
918 void Emitter::emitLibraryPrimTypedByFirstInputPort(Operation *op) {
919  auto cell = cast<CellInterface>(op);
920  unsigned bitWidth = cell.getInputPorts()[0].getType().getIntOrFloatBitWidth();
921  StringRef opName = op->getName().getStringRef();
922  indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
923  << space() << equals() << space() << removeCalyxPrefix(opName)
924  << LParen() << bitWidth << RParen() << semicolonEndL();
925 }
926 
927 void Emitter::emitLibraryPrimTypedByFirstOutputPort(
928  Operation *op, std::optional<StringRef> calyxLibName) {
929  auto cell = cast<CellInterface>(op);
930  unsigned bitWidth =
931  cell.getOutputPorts()[0].getType().getIntOrFloatBitWidth();
932  StringRef opName = op->getName().getStringRef();
933  indent() << getAttributes(op, /*atFormat=*/true) << cell.instanceName()
934  << space() << equals() << space()
935  << (calyxLibName ? *calyxLibName : removeCalyxPrefix(opName))
936  << LParen() << bitWidth << RParen() << semicolonEndL();
937 }
938 
939 void Emitter::emitAssignment(AssignOp op) {
940 
941  emitValue(op.getDest(), /*isIndented=*/true);
942  os << space() << equals() << space();
943  if (op.getGuard()) {
944  emitValue(op.getGuard(), /*isIndented=*/false);
945  os << questionMark();
946  }
947  emitValue(op.getSrc(), /*isIndented=*/false);
948  os << semicolonEndL();
949 }
950 
951 void Emitter::emitWires(WiresOp op) {
952  emitCalyxSection("wires", [&]() {
953  for (auto &&bodyOp : *op.getBodyBlock()) {
954  TypeSwitch<Operation *>(&bodyOp)
955  .Case<GroupInterface>([&](auto op) { emitGroup(op); })
956  .Case<AssignOp>([&](auto op) { emitAssignment(op); })
957  .Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp, CycleOp>(
958  [&](auto op) { /* Do nothing. */ })
959  .Default([&](auto op) {
960  emitOpError(op, "not supported for emission inside wires section");
961  });
962  }
963  });
964 }
965 
966 void Emitter::emitGroup(GroupInterface group) {
967  auto emitGroupBody = [&]() {
968  for (auto &&bodyOp : *group.getBody()) {
969  TypeSwitch<Operation *>(&bodyOp)
970  .Case<AssignOp>([&](auto op) { emitAssignment(op); })
971  .Case<GroupDoneOp>([&](auto op) { emitGroupPort(group, op, "done"); })
972  .Case<GroupGoOp>([&](auto op) { emitGroupPort(group, op, "go"); })
973  .Case<hw::ConstantOp, comb::AndOp, comb::OrOp, comb::XorOp, CycleOp>(
974  [&](auto op) { /* Do nothing. */ })
975  .Default([&](auto op) {
976  emitOpError(op, "not supported for emission inside group.");
977  });
978  }
979  };
980  std::string prefix;
981  if (isa<StaticGroupOp>(group)) {
982  auto staticGroup = cast<StaticGroupOp>(group);
983  prefix = llvm::formatv("static<{0}> group", staticGroup.getLatency());
984  } else {
985  prefix = isa<CombGroupOp>(group) ? "comb group" : "group";
986  }
987  auto groupHeader =
988  (group.symName().getValue() + getAttributes(group, /*atFormat=*/false))
989  .str();
990  emitCalyxSection(prefix, emitGroupBody, groupHeader);
991 }
992 
993 void Emitter::emitEnable(EnableOp enable) {
994  indent() << getAttributes(enable, /*atFormat=*/true) << enable.getGroupName()
995  << semicolonEndL();
996 }
997 
998 void Emitter::emitControl(ControlOp control) {
999  // A valid Calyx program does not necessarily need a control section.
1000  if (control == nullptr)
1001  return;
1002  emitCalyxSection("control",
1003  [&]() { emitCalyxControl(control.getBodyBlock()); });
1004 }
1005 
1006 //===----------------------------------------------------------------------===//
1007 // Driver
1008 //===----------------------------------------------------------------------===//
1009 
1010 // Emit the specified Calyx circuit into the given output stream.
1011 mlir::LogicalResult circt::calyx::exportCalyx(mlir::ModuleOp module,
1012  llvm::raw_ostream &os) {
1013  Emitter emitter(os);
1014  if (failed(emitter.emitImports(module)))
1015  return failure();
1016  emitter.emitModule(module);
1017  emitter.emitCiderMetadata(module);
1018  return emitter.finalize();
1019 }
1020 
1022  static mlir::TranslateFromMLIRRegistration toCalyx(
1023  "export-calyx", "export Calyx",
1024  [](ModuleOp module, llvm::raw_ostream &os) {
1025  return exportCalyx(module, os);
1026  },
1027  [](mlir::DialectRegistry &registry) {
1028  registry
1029  .insert<calyx::CalyxDialect, comb::CombDialect, hw::HWDialect>();
1030  });
1031 }
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.
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:20
This holds information about the port for either a Component or Cell.
Definition: CalyxOps.h:85
DictionaryAttr attributes
Definition: CalyxOps.h:89