Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
BlackBoxReader.cpp
Go to the documentation of this file.
1//===- BlackBoxReader.cpp - Ingest black box sources ------------*- C++ -*-===//
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// Read Verilog source files for black boxes based on corresponding black box
9// annotations on the circuit and modules. Primarily based on:
10//
11// https://github.com/chipsalliance/firrtl/blob/master/src/main/scala/firrtl/
12// transforms/BlackBoxSourceHelper.scala
13//
14//===----------------------------------------------------------------------===//
15
26#include "circt/Support/Debug.h"
27#include "circt/Support/Path.h"
28#include "mlir/IR/Attributes.h"
29#include "mlir/Pass/Pass.h"
30#include "mlir/Support/FileUtilities.h"
31#include "llvm/Support/Debug.h"
32#include "llvm/Support/FormatAdapters.h"
33#include "llvm/Support/FormatVariadic.h"
34#include "llvm/Support/MemoryBuffer.h"
35#include "llvm/Support/Path.h"
36
37#define DEBUG_TYPE "firrtl-blackbox-reader"
38
39namespace circt {
40namespace firrtl {
41#define GEN_PASS_DEF_BLACKBOXREADER
42#include "circt/Dialect/FIRRTL/Passes.h.inc"
43} // namespace firrtl
44} // namespace circt
45
46using namespace circt;
47using namespace firrtl;
48
49//===----------------------------------------------------------------------===//
50// Pass Implementation
51//===----------------------------------------------------------------------===//
52
53namespace {
54
55/// Data extracted from BlackBoxInlineAnno or BlackBoxPathAnno.
56struct AnnotationInfo {
57 /// The name of the file that should be created for this BlackBox.
58 StringAttr name;
59 /// The output directory information for this extmodule.
60 hw::OutputFileAttr outputFileAttr;
61 /// The body of the BlackBox. (This should be Verilog text.)
62 StringAttr inlineText;
63
64#if !defined(NDEBUG)
65 /// Pretty print the AnnotationInfo in a YAML-esque format.
66 void print(raw_ostream &os, unsigned indent = 0) const {
67 os << llvm::formatv("name: {1}\n"
68 "{0}outputFile: {2}\n"
69 "{0}exclude: {3}\n",
70 llvm::fmt_pad("", indent, 0), name,
71 outputFileAttr.getFilename(),
72 outputFileAttr.getExcludeFromFilelist().getValue());
73 };
74#endif
75};
76
77struct BlackBoxReaderPass
78 : public circt::firrtl::impl::BlackBoxReaderBase<BlackBoxReaderPass> {
79 using Base::Base;
80
81 void runOnOperation() override;
82 bool runOnAnnotation(Operation *op, Annotation anno, OpBuilder &builder,
83 bool isCover, AnnotationInfo &annotationInfo);
84 StringAttr loadFile(Operation *op, StringRef inputPath, OpBuilder &builder);
85 hw::OutputFileAttr getOutputFile(Operation *origOp, StringAttr fileNameAttr,
86 bool isCover = false);
87
88private:
89 /// A list of all files which will be included in the file list. This is
90 /// subset of all emitted files.
91 SmallVector<emit::FileOp> fileListFiles;
92
93 /// The target directory to output black boxes into. Can be changed
94 /// through `firrtl.transforms.BlackBoxTargetDirAnno` annotations.
95 StringRef targetDir;
96
97 /// The target directory for cover statements.
98 StringRef coverDir;
99
100 /// The target directory for testbench files.
101 StringRef testBenchDir;
102
103 /// Analyses used by this pass.
104 InstanceGraph *instanceGraph;
105 InstanceInfo *instanceInfo;
106
107 /// A cache of the modules which have been marked as DUT or a testbench.
108 /// This is used to determine the output directory.
109 DenseMap<Operation *, bool> dutModuleMap;
110
111 /// An ordered map of Verilog filenames to the annotation-derived information
112 /// that will be used to create this file. Due to situations where multiple
113 /// external modules may not deduplicate (e.g., they have different
114 /// parameters), multiple annotations may all want to write to the same file.
115 /// This always tracks the actual annotation that will be used. If a more
116 /// appropriate annotation is found (e.g., which will cause the file to be
117 /// written to the DUT directory and not the TestHarness directory), then this
118 /// will map will be updated.
119 llvm::MapVector<StringAttr, AnnotationInfo> emittedFileMap;
120};
121} // end anonymous namespace
122
123/// Emit the annotated source code for black boxes in a circuit.
124void BlackBoxReaderPass::runOnOperation() {
125 LLVM_DEBUG(debugPassHeader(this) << "\n");
126 CircuitOp circuitOp = getOperation();
127 CircuitNamespace ns(circuitOp);
128
129 instanceGraph = &getAnalysis<InstanceGraph>();
130 instanceInfo = &getAnalysis<InstanceInfo>();
131 auto context = &getContext();
132
133 // If this pass has changed anything.
134 bool anythingChanged = false;
135
136 // Internalize some string attributes for easy reference later.
137
138 // Determine the target directory and resource file name from the
139 // annotations present on the circuit operation.
140 targetDir = ".";
141
142 // Process black box annotations on the circuit. Some of these annotations
143 // will affect how the rest of the annotations are resolved.
144 SmallVector<Attribute, 4> filteredAnnos;
145 for (auto annot : AnnotationSet(circuitOp)) {
146 filteredAnnos.push_back(annot.getDict());
147
148 // Get the testbench and cover directories.
149 if (annot.isClass(extractCoverageAnnoClass))
150 if (auto dir = annot.getMember<StringAttr>("directory")) {
151 coverDir = dir.getValue();
152 continue;
153 }
154
155 if (annot.isClass(testBenchDirAnnoClass))
156 if (auto dir = annot.getMember<StringAttr>("dirname")) {
157 testBenchDir = dir.getValue();
158 continue;
159 }
160
161 // Handle target dir annotation.
162 if (annot.isClass(blackBoxTargetDirAnnoClass)) {
163 if (auto target = annot.getMember<StringAttr>("targetDir")) {
164 targetDir = target.getValue();
165 continue;
166 }
167 circuitOp->emitError(blackBoxTargetDirAnnoClass)
168 << " annotation missing \"targetDir\" attribute";
169 signalPassFailure();
170 continue;
171 }
172 }
173 // Apply the filtered annotations to the circuit. If we updated the circuit
174 // and record that they changed.
175 anythingChanged |=
176 AnnotationSet(filteredAnnos, context).applyToOperation(circuitOp);
177
178 LLVM_DEBUG(llvm::dbgs() << "Black box target directory: " << targetDir
179 << "\n");
180
181 // Newly generated IR will be placed at the end of the circuit.
182 auto builder = circuitOp.getBodyBuilder();
183
184 LLVM_DEBUG(llvm::dbgs() << "Visiting extmodules:\n");
185 auto bboxAnno =
186 builder.getDictionaryAttr({{builder.getStringAttr("class"),
187 builder.getStringAttr(blackBoxAnnoClass)}});
188 for (auto extmoduleOp : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
189 LLVM_DEBUG({
190 llvm::dbgs().indent(2)
191 << "- name: " << extmoduleOp.getModuleNameAttr() << "\n";
192 llvm::dbgs().indent(4) << "annotations:\n";
193 });
194 AnnotationSet annotations(extmoduleOp);
195 bool isCover =
196 !coverDir.empty() && annotations.hasAnnotation(verifBlackBoxAnnoClass);
197 bool foundBBoxAnno = false;
198 annotations.removeAnnotations([&](Annotation anno) {
199 AnnotationInfo annotationInfo;
200 if (!runOnAnnotation(extmoduleOp, anno, builder, isCover, annotationInfo))
201 return false;
202
203 LLVM_DEBUG(annotationInfo.print(llvm::dbgs().indent(6) << "- ", 8));
204
205 // If we have seen a black box trying to create a blackbox with this
206 // filename before, then compute the lowest commmon ancestor between the
207 // two blackbox paths. This is the same logic used in `AssignOutputDirs`.
208 // However, this needs to incorporate filenames that are only available
209 // _after_ output directories are assigned.
210 auto [ptr, inserted] =
211 emittedFileMap.try_emplace(annotationInfo.name, annotationInfo);
212 if (inserted) {
213 emittedFileMap[annotationInfo.name] = annotationInfo;
214 } else {
215 auto &fileAttr = ptr->second.outputFileAttr;
216 SmallString<64> directory(fileAttr.getDirectory());
217 makeCommonPrefix(directory,
218 annotationInfo.outputFileAttr.getDirectory());
219 fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
220 context, directory, annotationInfo.name.getValue(),
221 /*excludeFromFileList=*/
222 fileAttr.getExcludeFromFilelist().getValue());
223 // TODO: Check that the new text is the _exact same_ as the prior best.
224 }
225
226 foundBBoxAnno = true;
227 return true;
228 });
229
230 if (foundBBoxAnno) {
231 annotations.addAnnotations({bboxAnno});
232 anythingChanged = true;
233 }
234 annotations.applyToOperation(extmoduleOp);
235 }
236
237 LLVM_DEBUG(llvm::dbgs() << "emittedFiles:\n");
238 Location loc = builder.getUnknownLoc();
239 for (auto &[verilogName, annotationInfo] : emittedFileMap) {
240 LLVM_DEBUG({
241 llvm::dbgs().indent(2) << "verilogName: " << verilogName << "\n";
242 llvm::dbgs().indent(2) << "annotationInfo:\n";
243 annotationInfo.print(llvm::dbgs().indent(4) << "- ", 6);
244 });
245
246 auto fileName = ns.newName("blackbox_" + verilogName.getValue());
247
248 auto fileOp = builder.create<emit::FileOp>(
249 loc, annotationInfo.outputFileAttr.getFilename(), fileName,
250 [&, text = annotationInfo.inlineText] {
251 builder.create<emit::VerbatimOp>(loc, text);
252 });
253
254 if (!annotationInfo.outputFileAttr.getExcludeFromFilelist().getValue())
255 fileListFiles.push_back(fileOp);
256 }
257
258 // If we have emitted any files, generate a file list operation that
259 // documents the additional annotation-controlled file listing to be
260 // created.
261 if (!fileListFiles.empty()) {
262 // Output the file list in sorted order.
263 llvm::sort(fileListFiles.begin(), fileListFiles.end(),
264 [](emit::FileOp fileA, emit::FileOp fileB) {
265 return fileA.getFileName() < fileB.getFileName();
266 });
267
268 // Create the file list contents by enumerating the symbols to the files.
269 SmallVector<Attribute> symbols;
270 for (emit::FileOp file : fileListFiles)
271 symbols.push_back(FlatSymbolRefAttr::get(file.getSymNameAttr()));
272 }
273
274 // If nothing has changed we can preserve the analysis.
275 if (!anythingChanged)
276 markAllAnalysesPreserved();
277 markAnalysesPreserved<InstanceGraph, InstanceInfo>();
278
279 // Clean up.
280 emittedFileMap.clear();
281 fileListFiles.clear();
282 LLVM_DEBUG(debugFooter() << "\n");
283}
284
285/// Run on an operation-annotation pair. The annotation need not be a black box
286/// annotation. Returns `true` if the annotation was indeed a black box
287/// annotation (even if it was incomplete) and should be removed from the op.
288bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno,
289 OpBuilder &builder, bool isCover,
290 AnnotationInfo &annotationInfo) {
291 // Handle inline annotation.
293 auto name = anno.getMember<StringAttr>("name");
294 auto text = anno.getMember<StringAttr>("text");
295 if (!name || !text) {
296 op->emitError(blackBoxInlineAnnoClass)
297 << " annotation missing \"name\" or \"text\" attribute";
298 signalPassFailure();
299 return true;
300 }
301
302 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
303 annotationInfo.name = name;
304 annotationInfo.inlineText = text;
305 return true;
306 }
307
308 // Handle path annotation.
309 if (anno.isClass(blackBoxPathAnnoClass)) {
310 auto path = anno.getMember<StringAttr>("path");
311 if (!path) {
312 op->emitError(blackBoxPathAnnoClass)
313 << " annotation missing \"path\" attribute";
314 signalPassFailure();
315 return true;
316 }
317 SmallString<128> inputPath(inputPrefix);
318 appendPossiblyAbsolutePath(inputPath, path.getValue());
319 auto text = loadFile(op, inputPath, builder);
320 if (!text) {
321 op->emitError("Cannot find file ") << inputPath;
322 signalPassFailure();
323 return false;
324 }
325 auto name = builder.getStringAttr(llvm::sys::path::filename(path));
326 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
327 annotationInfo.name = name;
328 annotationInfo.inlineText = text;
329 return true;
330 }
331
332 // Annotation was not concerned with black boxes.
333 return false;
334}
335
336/// Copies a black box source file to the appropriate location in the target
337/// directory.
338StringAttr BlackBoxReaderPass::loadFile(Operation *op, StringRef inputPath,
339 OpBuilder &builder) {
340 LLVM_DEBUG(llvm::dbgs() << "Add black box source `"
341 << llvm::sys::path::filename(inputPath) << "` from `"
342 << inputPath << "`\n");
343
344 // Open and read the input file.
345 std::string errorMessage;
346 auto input = mlir::openInputFile(inputPath, &errorMessage);
347 if (!input)
348 return {};
349
350 // Return a StringAttr with the buffer contents.
351 return builder.getStringAttr(input->getBuffer());
352}
353
354/// Determine the output file for some operation.
355hw::OutputFileAttr BlackBoxReaderPass::getOutputFile(Operation *origOp,
356 StringAttr fileNameAttr,
357 bool isCover) {
358 // If the original operation has a specified output file that is not a
359 // directory, then just use that.
360 auto outputFile = origOp->getAttrOfType<hw::OutputFileAttr>("output_file");
361 if (outputFile && !outputFile.isDirectory()) {
362 return {outputFile};
363 }
364
365 // Exclude Verilog header files since we expect them to be included
366 // explicitly by compiler directives in other source files.
367 auto *context = &getContext();
368 auto fileName = fileNameAttr.getValue();
369 auto ext = llvm::sys::path::extension(fileName);
370 bool exclude = (ext == ".h" || ext == ".vh" || ext == ".svh");
371 auto outDir = targetDir;
372
373 /// If the original operation has an output directory, use that.
374 if (outputFile)
375 outDir = outputFile.getFilename();
376 // In order to output into the testbench directory, we need to have a
377 // testbench dir annotation, not have a blackbox target directory annotation
378 // (or one set to the current directory), have a DUT annotation, and the
379 // module must not be instantiated under the DUT.
380 else if (!testBenchDir.empty() && targetDir == "." &&
381 !instanceInfo->anyInstanceInEffectiveDesign(
382 cast<igraph::ModuleOpInterface>(origOp)))
383 outDir = testBenchDir;
384 else if (isCover)
385 outDir = coverDir;
386
387 // If targetDir is not set explicitly and this is a testbench module, then
388 // update the targetDir to be the "../testbench".
389 SmallString<128> outputFilePath(outDir);
390 llvm::sys::path::append(outputFilePath, fileName);
391 return hw::OutputFileAttr::getFromFilename(context, outputFilePath, exclude);
392}
static OutputFileAttr getOutputFile(igraph::ModuleOpInterface op)
static void print(TypedAttr val, llvm::raw_ostream &os)
static Block * getBodyBlock(FModuleLike mod)
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
constexpr const char * extractCoverageAnnoClass
constexpr const char * blackBoxAnnoClass
constexpr const char * testBenchDirAnnoClass
constexpr const char * verifBlackBoxAnnoClass
constexpr const char * blackBoxPathAnnoClass
constexpr const char * blackBoxTargetDirAnnoClass
constexpr const char * blackBoxInlineAnnoClass
void makeCommonPrefix(SmallString< 64 > &a, StringRef b)
Truncate a to the common prefix of a and b.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void appendPossiblyAbsolutePath(llvm::SmallVectorImpl< char > &base, const llvm::Twine &suffix)
Append a path to an existing path, replacing it if the other path is absolute.
Definition Path.cpp:26
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition Debug.cpp:31
llvm::raw_ostream & debugFooter(int width=80)
Write a boilerplate footer to the debug stream to indicate that a pass has ended.
Definition Debug.cpp:35
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24