CIRCT  20.0.0git
ExportChiselInterface.cpp
Go to the documentation of this file.
1 //===- ExportChiselInterface.cpp - Chisel Interface 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 is the Chisel interface emitter implementation.
10 //
11 //===----------------------------------------------------------------------===//
12 
15 #include "circt/Support/Version.h"
16 #include "mlir/Support/FileUtilities.h"
17 #include "llvm/Support/FileSystem.h"
18 #include "llvm/Support/Path.h"
19 #include "llvm/Support/ToolOutputFile.h"
20 
21 namespace circt {
22 #define GEN_PASS_DEF_EXPORTCHISELINTERFACE
23 #define GEN_PASS_DEF_EXPORTSPLITCHISELINTERFACE
24 #include "circt/Conversion/Passes.h.inc"
25 } // namespace circt
26 
27 using namespace circt;
28 using namespace firrtl;
29 
30 #define DEBUG_TYPE "export-chisel-package"
31 
32 //===----------------------------------------------------------------------===//
33 // Interface emission logic
34 //===----------------------------------------------------------------------===//
35 
36 static const unsigned int indentIncrement = 2;
37 
38 namespace {
39 class Emitter {
40 public:
41  Emitter(llvm::raw_ostream &os) : os(os) {}
42 
43  bool hasEmittedProbeType() { return hasEmittedProbe; }
44 
45  /// Emits an `ExtModule` class with port declarations for `module`.
46  LogicalResult emitModule(FModuleLike module) {
47  os << "class " << module.getModuleName() << " extends ExtModule {\n";
48 
49  for (const auto &port : module.getPorts()) {
50  if (failed(emitPort(port)))
51  return failure();
52  }
53 
54  os << "}\n";
55 
56  return success();
57  }
58 
59 private:
60  /// Emits an `IO` for the `port`.
61  LogicalResult emitPort(const PortInfo &port) {
62  os.indent(indentIncrement) << "val " << port.getName() << " = IO(";
63  if (failed(
64  emitPortType(port.loc, port.type, port.direction, indentIncrement)))
65  return failure();
66  os << ")\n";
67 
68  return success();
69  }
70 
71  /// Emits type construction expression for the port type, recursing into
72  /// aggregate types as needed.
73  LogicalResult emitPortType(Location location, Type type, Direction direction,
74  unsigned int indent,
75  bool hasEmittedDirection = false) {
76  auto emitTypeWithArguments =
77  [&]( // This is provided if the type is a base type, otherwise this is
78  // null
79  FIRRTLBaseType baseType, StringRef name,
80  // A lambda of type (bool hasEmittedDirection) -> LogicalResult.
81  auto emitArguments,
82  // Indicates whether parentheses around type arguments should be
83  // used.
84  bool emitParentheses = true) -> LogicalResult {
85  // Include the direction if the type is not a base (i.e. hardware) type or
86  // is not composed of flips and analog signals and we haven't already
87  // emitted the direction before recursing to this field.
88  // Chisel direction functions override any internal directions. In other
89  // words, Output(new Bundle {...}) erases all direction information inside
90  // the bundle. Because of this, directions are placed on the outermost
91  // passive members of a hardware type.
92  bool emitDirection =
93  !baseType || (!hasEmittedDirection && baseType.isPassive() &&
94  !baseType.containsAnalog());
95  if (emitDirection) {
96  switch (direction) {
97  case Direction::In:
98  os << "Input(";
99  break;
100  case Direction::Out:
101  os << "Output(";
102  break;
103  }
104  }
105 
106  bool emitConst = baseType && baseType.isConst();
107  if (emitConst)
108  os << "Const(";
109 
110  os << name;
111 
112  if (emitParentheses)
113  os << "(";
114 
115  if (failed(emitArguments(hasEmittedDirection || emitDirection)))
116  return failure();
117 
118  if (emitParentheses)
119  os << ')';
120 
121  if (emitConst)
122  os << ')';
123 
124  if (emitDirection)
125  os << ')';
126 
127  return success();
128  };
129 
130  // Emits a type that does not require arguments.
131  auto emitType = [&](FIRRTLBaseType baseType,
132  StringRef name) -> LogicalResult {
133  return emitTypeWithArguments(baseType, name,
134  [](bool) { return success(); });
135  };
136 
137  // Emits a type that requires a known width argument.
138  auto emitWidthQualifiedType = [&](auto type,
139  StringRef name) -> LogicalResult {
140  auto width = type.getWidth();
141  if (!width.has_value()) {
142  return LogicalResult(emitError(
143  location, "Expected width to be inferred for exported port"));
144  }
145  return emitTypeWithArguments(type, name, [&](bool) {
146  os << *width << ".W";
147  return success();
148  });
149  };
150 
151  return TypeSwitch<Type, LogicalResult>(type)
152  .Case<ClockType>(
153  [&](ClockType type) { return emitType(type, "Clock"); })
154  .Case<AsyncResetType>(
155  [&](AsyncResetType type) { return emitType(type, "AsyncReset"); })
156  .Case<ResetType>([&](ResetType) {
157  return emitError(
158  location, "Expected reset type to be inferred for exported port");
159  })
160  .Case<UIntType>([&](UIntType uIntType) {
161  return emitWidthQualifiedType(uIntType, "UInt");
162  })
163  .Case<SIntType>([&](SIntType sIntType) {
164  return emitWidthQualifiedType(sIntType, "SInt");
165  })
166  .Case<AnalogType>([&](AnalogType analogType) {
167  return emitWidthQualifiedType(analogType, "Analog");
168  })
169  .Case<BundleType>([&](BundleType bundleType) {
170  // Emit an anonymous bundle, emitting a `val` for each field.
171  return emitTypeWithArguments(
172  bundleType, "new Bundle ",
173  [&](bool hasEmittedDirection) {
174  os << "{\n";
175  unsigned int nestedIndent = indent + indentIncrement;
176  for (const auto &element : bundleType.getElements()) {
177  os.indent(nestedIndent)
178  << "val " << element.name.getValue() << " = ";
179  auto elementResult = emitPortType(
180  location, element.type,
181  element.isFlip ? direction::flip(direction) : direction,
182  nestedIndent, hasEmittedDirection);
183  if (failed(elementResult))
184  return failure();
185  os << '\n';
186  }
187  os.indent(indent) << "}";
188  return success();
189  },
190  false);
191  })
192  .Case<FVectorType>([&](FVectorType vectorType) {
193  // Emit a vector type, emitting the type of its element as an
194  // argument.
195  return emitTypeWithArguments(
196  vectorType, "Vec", [&](bool hasEmittedDirection) {
197  os << vectorType.getNumElements() << ", ";
198  return emitPortType(location, vectorType.getElementType(),
199  direction, indent, hasEmittedDirection);
200  });
201  })
202  .Case<RefType>([&](RefType refType) {
203  hasEmittedProbe = true;
204  StringRef name = refType.getForceable() ? "RWProbe" : "Probe";
205  return emitTypeWithArguments(
206  nullptr, name, [&](bool hasEmittedDirection) {
207  return emitPortType(location, refType.getType(), direction,
208  indent, hasEmittedDirection);
209  });
210  })
211  .Default([&](Type type) {
212  mlir::emitError(location) << "Unhandled type: " << type;
213  return failure();
214  });
215  }
216 
217  llvm::raw_ostream &os;
218  bool hasEmittedProbe = false;
219 };
220 } // namespace
221 
222 /// Exports a Chisel interface to the output stream.
223 static LogicalResult exportChiselInterface(CircuitOp circuit,
224  llvm::raw_ostream &os) {
225  // Emit version, package, and import declarations
226  os << circt::getCirctVersionComment() << "package shelf."
227  << circuit.getName().lower()
228  << "\n\nimport chisel3._\nimport chisel3.experimental._\n";
229 
230  std::string body;
231  llvm::raw_string_ostream bodyStream(body);
232  Emitter emitter(bodyStream);
233 
234  // Emit a class for the main circuit module.
235  for (auto moduleOp : circuit.getOps<FModuleOp>()) {
236  if (!moduleOp.isPublic())
237  continue;
238  if (failed(emitter.emitModule(moduleOp)))
239  return failure();
240  }
241 
242  // Emit an import for probe types if needed
243  if (emitter.hasEmittedProbeType())
244  os << "import chisel3.probe._\n";
245 
246  // Emit the body
247  os << '\n' << body;
248 
249  return success();
250 }
251 
252 /// Exports Chisel interface files for the circuit to the specified directory.
253 static LogicalResult exportSplitChiselInterface(CircuitOp circuit,
254  StringRef outputDirectory) {
255  // Create the output directory if needed.
256  std::error_code error = llvm::sys::fs::create_directories(outputDirectory);
257  if (error) {
258  circuit.emitError("cannot create output directory \"")
259  << outputDirectory << "\": " << error.message();
260  return failure();
261  }
262 
263  // Open the output file.
264  SmallString<128> interfaceFilePath(outputDirectory);
265  llvm::sys::path::append(interfaceFilePath, circuit.getName());
266  llvm::sys::path::replace_extension(interfaceFilePath, "scala");
267  std::string errorMessage;
268  auto interfaceFile = mlir::openOutputFile(interfaceFilePath, &errorMessage);
269  if (!interfaceFile) {
270  circuit.emitError(errorMessage);
271  return failure();
272  }
273 
274  // Export the interface to the file.
275  auto result = exportChiselInterface(circuit, interfaceFile->os());
276  if (succeeded(result))
277  interfaceFile->keep();
278  return result;
279 }
280 
281 //===----------------------------------------------------------------------===//
282 // ExportChiselInterfacePass and ExportSplitChiselInterfacePass
283 //===----------------------------------------------------------------------===//
284 
285 namespace {
286 struct ExportChiselInterfacePass
287  : public circt::impl::ExportChiselInterfaceBase<ExportChiselInterfacePass> {
288 
289  explicit ExportChiselInterfacePass(llvm::raw_ostream &os) : os(os) {}
290 
291  void runOnOperation() override {
292  if (failed(exportChiselInterface(getOperation(), os)))
293  signalPassFailure();
294  }
295 
296 private:
297  llvm::raw_ostream &os;
298 };
299 
300 struct ExportSplitChiselInterfacePass
301  : public circt::impl::ExportSplitChiselInterfaceBase<
302  ExportSplitChiselInterfacePass> {
303 
304  explicit ExportSplitChiselInterfacePass(StringRef directory) {
305  directoryName = directory.str();
306  }
307 
308  void runOnOperation() override {
309  if (failed(exportSplitChiselInterface(getOperation(), directoryName)))
310  signalPassFailure();
311  }
312 };
313 } // namespace
314 
315 std::unique_ptr<mlir::Pass>
317  return std::make_unique<ExportChiselInterfacePass>(os);
318 }
319 
320 std::unique_ptr<mlir::Pass>
322  return std::make_unique<ExportSplitChiselInterfacePass>(directory);
323 }
324 
325 std::unique_ptr<mlir::Pass> circt::createExportChiselInterfacePass() {
326  return createExportChiselInterfacePass(llvm::outs());
327 }
static LogicalResult exportSplitChiselInterface(CircuitOp circuit, StringRef outputDirectory)
Exports Chisel interface files for the circuit to the specified directory.
static const unsigned int indentIncrement
static LogicalResult exportChiselInterface(CircuitOp circuit, llvm::raw_ostream &os)
Exports a Chisel interface to the output stream.
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
bool isConst()
Returns true if this is a 'const' type that can only hold compile-time constant values.
bool isPassive() const
Return true if this is a "passive" type - one that contains no "flip" types recursively within itself...
Definition: FIRRTLTypes.h:154
Direction
This represents the direction of a single port.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createExportSplitChiselInterfacePass(mlir::StringRef outputDirectory="./")
std::unique_ptr< mlir::Pass > createExportChiselInterfacePass(llvm::raw_ostream &os)
const char * getCirctVersionComment()
This holds the name and type that describes the module's ports.
StringRef getName() const