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