CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
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
21namespace circt {
22#define GEN_PASS_DEF_EXPORTCHISELINTERFACE
23#define GEN_PASS_DEF_EXPORTSPLITCHISELINTERFACE
24#include "circt/Conversion/Passes.h.inc"
25} // namespace circt
26
27using namespace circt;
28using namespace firrtl;
29
30#define DEBUG_TYPE "export-chisel-package"
31
32//===----------------------------------------------------------------------===//
33// Interface emission logic
34//===----------------------------------------------------------------------===//
35
36static const unsigned int indentIncrement = 2;
37
38namespace {
39class Emitter {
40public:
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
59private:
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.
223static 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.
253static 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
285namespace {
286struct 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
296private:
297 llvm::raw_ostream &os;
298};
299
300struct 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
315std::unique_ptr<mlir::Pass>
317 return std::make_unique<ExportChiselInterfacePass>(os);
318}
319
320std::unique_ptr<mlir::Pass>
322 return std::make_unique<ExportSplitChiselInterfacePass>(directory);
323}
324
325std::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.
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...
Direction flip(Direction direction)
Flip a port direction.
Direction
This represents the direction of a single port.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createExportSplitChiselInterfacePass(mlir::StringRef outputDirectory="./")
const char * getCirctVersionComment()
std::unique_ptr< mlir::Pass > createExportChiselInterfacePass()
This holds the name and type that describes the module's ports.