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