CIRCT 20.0.0git
Loading...
Searching...
No Matches
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 void runOnOperation() override;
80 bool runOnAnnotation(Operation *op, Annotation anno, OpBuilder &builder,
81 bool isCover, AnnotationInfo &annotationInfo);
82 StringAttr loadFile(Operation *op, StringRef inputPath, OpBuilder &builder);
83 hw::OutputFileAttr getOutputFile(Operation *origOp, StringAttr fileNameAttr,
84 bool isCover = false);
85
86 using BlackBoxReaderBase::inputPrefix;
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 /// The file list file name (sic) for black boxes. If set, generates a file
104 /// that lists all non-header source files for black boxes. Can be changed
105 /// through `firrtl.transforms.BlackBoxResourceFileNameAnno` annotations.
106 StringRef resourceFileName;
107
108 /// Analyses used by this pass.
109 InstanceGraph *instanceGraph;
110 InstanceInfo *instanceInfo;
111
112 /// A cache of the modules which have been marked as DUT or a testbench.
113 /// This is used to determine the output directory.
114 DenseMap<Operation *, bool> dutModuleMap;
115
116 /// An ordered map of Verilog filenames to the annotation-derived information
117 /// that will be used to create this file. Due to situations where multiple
118 /// external modules may not deduplicate (e.g., they have different
119 /// parameters), multiple annotations may all want to write to the same file.
120 /// This always tracks the actual annotation that will be used. If a more
121 /// appropriate annotation is found (e.g., which will cause the file to be
122 /// written to the DUT directory and not the TestHarness directory), then this
123 /// will map will be updated.
124 llvm::MapVector<StringAttr, AnnotationInfo> emittedFileMap;
125};
126} // end anonymous namespace
127
128/// Emit the annotated source code for black boxes in a circuit.
129void BlackBoxReaderPass::runOnOperation() {
130 LLVM_DEBUG(debugPassHeader(this) << "\n");
131 CircuitOp circuitOp = getOperation();
132 CircuitNamespace ns(circuitOp);
133
134 instanceGraph = &getAnalysis<InstanceGraph>();
135 instanceInfo = &getAnalysis<InstanceInfo>();
136 auto context = &getContext();
137
138 // If this pass has changed anything.
139 bool anythingChanged = false;
140
141 // Internalize some string attributes for easy reference later.
142
143 // Determine the target directory and resource file name from the
144 // annotations present on the circuit operation.
145 targetDir = ".";
146 resourceFileName = "firrtl_black_box_resource_files.f";
147
148 // Process black box annotations on the circuit. Some of these annotations
149 // will affect how the rest of the annotations are resolved.
150 SmallVector<Attribute, 4> filteredAnnos;
151 for (auto annot : AnnotationSet(circuitOp)) {
152 // Handle resource file name annotation.
153 if (annot.isClass(blackBoxResourceFileNameAnnoClass)) {
154 if (auto resourceFN = annot.getMember<StringAttr>("resourceFileName")) {
155 resourceFileName = resourceFN.getValue();
156 continue;
157 }
158
159 circuitOp->emitError(blackBoxResourceFileNameAnnoClass)
160 << " annotation missing \"resourceFileName\" attribute";
161 signalPassFailure();
162 continue;
163 }
164 filteredAnnos.push_back(annot.getDict());
165
166 // Get the testbench and cover directories.
167 if (annot.isClass(extractCoverageAnnoClass))
168 if (auto dir = annot.getMember<StringAttr>("directory")) {
169 coverDir = dir.getValue();
170 continue;
171 }
172
173 if (annot.isClass(testBenchDirAnnoClass))
174 if (auto dir = annot.getMember<StringAttr>("dirname")) {
175 testBenchDir = dir.getValue();
176 continue;
177 }
178
179 // Handle target dir annotation.
180 if (annot.isClass(blackBoxTargetDirAnnoClass)) {
181 if (auto target = annot.getMember<StringAttr>("targetDir")) {
182 targetDir = target.getValue();
183 continue;
184 }
185 circuitOp->emitError(blackBoxTargetDirAnnoClass)
186 << " annotation missing \"targetDir\" attribute";
187 signalPassFailure();
188 continue;
189 }
190 }
191 // Apply the filtered annotations to the circuit. If we updated the circuit
192 // and record that they changed.
193 anythingChanged |=
194 AnnotationSet(filteredAnnos, context).applyToOperation(circuitOp);
195
196 LLVM_DEBUG(llvm::dbgs() << "Black box target directory: " << targetDir << "\n"
197 << "Black box resource file name: "
198 << resourceFileName << "\n");
199
200 // Newly generated IR will be placed at the end of the circuit.
201 auto builder = circuitOp.getBodyBuilder();
202
203 LLVM_DEBUG(llvm::dbgs() << "Visiting extmodules:\n");
204 auto bboxAnno =
205 builder.getDictionaryAttr({{builder.getStringAttr("class"),
206 builder.getStringAttr(blackBoxAnnoClass)}});
207 for (auto extmoduleOp : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
208 LLVM_DEBUG({
209 llvm::dbgs().indent(2)
210 << "- name: " << extmoduleOp.getModuleNameAttr() << "\n";
211 llvm::dbgs().indent(4) << "annotations:\n";
212 });
213 AnnotationSet annotations(extmoduleOp);
214 bool isCover =
215 !coverDir.empty() && annotations.hasAnnotation(verifBlackBoxAnnoClass);
216 bool foundBBoxAnno = false;
217 annotations.removeAnnotations([&](Annotation anno) {
218 AnnotationInfo annotationInfo;
219 if (!runOnAnnotation(extmoduleOp, anno, builder, isCover, annotationInfo))
220 return false;
221
222 LLVM_DEBUG(annotationInfo.print(llvm::dbgs().indent(6) << "- ", 8));
223
224 // If we have seen a black box trying to create a blackbox with this
225 // filename before, then compute the lowest commmon ancestor between the
226 // two blackbox paths. This is the same logic used in `AssignOutputDirs`.
227 // However, this needs to incorporate filenames that are only available
228 // _after_ output directories are assigned.
229 auto [ptr, inserted] =
230 emittedFileMap.try_emplace(annotationInfo.name, annotationInfo);
231 if (inserted) {
232 emittedFileMap[annotationInfo.name] = annotationInfo;
233 } else {
234 auto &fileAttr = ptr->second.outputFileAttr;
235 SmallString<64> directory(fileAttr.getDirectory());
236 makeCommonPrefix(directory,
237 annotationInfo.outputFileAttr.getDirectory());
238 fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
239 context, directory, annotationInfo.name.getValue(),
240 /*excludeFromFileList=*/
241 fileAttr.getExcludeFromFilelist().getValue());
242 // TODO: Check that the new text is the _exact same_ as the prior best.
243 }
244
245 foundBBoxAnno = true;
246 return true;
247 });
248
249 if (foundBBoxAnno) {
250 annotations.addAnnotations({bboxAnno});
251 anythingChanged = true;
252 }
253 annotations.applyToOperation(extmoduleOp);
254 }
255
256 LLVM_DEBUG(llvm::dbgs() << "emittedFiles:\n");
257 Location loc = builder.getUnknownLoc();
258 for (auto &[verilogName, annotationInfo] : emittedFileMap) {
259 LLVM_DEBUG({
260 llvm::dbgs().indent(2) << "verilogName: " << verilogName << "\n";
261 llvm::dbgs().indent(2) << "annotationInfo:\n";
262 annotationInfo.print(llvm::dbgs().indent(4) << "- ", 6);
263 });
264
265 auto fileName = ns.newName("blackbox_" + verilogName.getValue());
266
267 auto fileOp = builder.create<emit::FileOp>(
268 loc, annotationInfo.outputFileAttr.getFilename(), fileName,
269 [&, text = annotationInfo.inlineText] {
270 builder.create<emit::VerbatimOp>(loc, text);
271 });
272
273 if (!annotationInfo.outputFileAttr.getExcludeFromFilelist().getValue())
274 fileListFiles.push_back(fileOp);
275 }
276
277 // If we have emitted any files, generate a file list operation that
278 // documents the additional annotation-controlled file listing to be
279 // created.
280 if (!fileListFiles.empty()) {
281 // Output the file list in sorted order.
282 llvm::sort(fileListFiles.begin(), fileListFiles.end(),
283 [](emit::FileOp fileA, emit::FileOp fileB) {
284 return fileA.getFileName() < fileB.getFileName();
285 });
286
287 // Create the file list contents by enumerating the symbols to the files.
288 SmallVector<Attribute> symbols;
289 for (emit::FileOp file : fileListFiles)
290 symbols.push_back(FlatSymbolRefAttr::get(file.getSymNameAttr()));
291
292 builder.create<emit::FileListOp>(
293 loc, builder.getStringAttr(resourceFileName),
294 builder.getArrayAttr(symbols),
295 builder.getStringAttr(ns.newName("blackbox_filelist")));
296 }
297
298 // If nothing has changed we can preserve the analysis.
299 if (!anythingChanged)
300 markAllAnalysesPreserved();
301 markAnalysesPreserved<InstanceGraph, InstanceInfo>();
302
303 // Clean up.
304 emittedFileMap.clear();
305 fileListFiles.clear();
306 LLVM_DEBUG(debugFooter() << "\n");
307}
308
309/// Run on an operation-annotation pair. The annotation need not be a black box
310/// annotation. Returns `true` if the annotation was indeed a black box
311/// annotation (even if it was incomplete) and should be removed from the op.
312bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno,
313 OpBuilder &builder, bool isCover,
314 AnnotationInfo &annotationInfo) {
315 // Handle inline annotation.
317 auto name = anno.getMember<StringAttr>("name");
318 auto text = anno.getMember<StringAttr>("text");
319 if (!name || !text) {
320 op->emitError(blackBoxInlineAnnoClass)
321 << " annotation missing \"name\" or \"text\" attribute";
322 signalPassFailure();
323 return true;
324 }
325
326 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
327 annotationInfo.name = name;
328 annotationInfo.inlineText = text;
329 return true;
330 }
331
332 // Handle path annotation.
333 if (anno.isClass(blackBoxPathAnnoClass)) {
334 auto path = anno.getMember<StringAttr>("path");
335 if (!path) {
336 op->emitError(blackBoxPathAnnoClass)
337 << " annotation missing \"path\" attribute";
338 signalPassFailure();
339 return true;
340 }
341 SmallString<128> inputPath(inputPrefix);
342 appendPossiblyAbsolutePath(inputPath, path.getValue());
343 auto text = loadFile(op, inputPath, builder);
344 if (!text) {
345 op->emitError("Cannot find file ") << inputPath;
346 signalPassFailure();
347 return false;
348 }
349 auto name = builder.getStringAttr(llvm::sys::path::filename(path));
350 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
351 annotationInfo.name = name;
352 annotationInfo.inlineText = text;
353 return true;
354 }
355
356 // Annotation was not concerned with black boxes.
357 return false;
358}
359
360/// Copies a black box source file to the appropriate location in the target
361/// directory.
362StringAttr BlackBoxReaderPass::loadFile(Operation *op, StringRef inputPath,
363 OpBuilder &builder) {
364 LLVM_DEBUG(llvm::dbgs() << "Add black box source `"
365 << llvm::sys::path::filename(inputPath) << "` from `"
366 << inputPath << "`\n");
367
368 // Open and read the input file.
369 std::string errorMessage;
370 auto input = mlir::openInputFile(inputPath, &errorMessage);
371 if (!input)
372 return {};
373
374 // Return a StringAttr with the buffer contents.
375 return builder.getStringAttr(input->getBuffer());
376}
377
378/// Determine the output file for some operation.
379hw::OutputFileAttr BlackBoxReaderPass::getOutputFile(Operation *origOp,
380 StringAttr fileNameAttr,
381 bool isCover) {
382 auto outputFile = origOp->getAttrOfType<hw::OutputFileAttr>("output_file");
383 if (outputFile && !outputFile.isDirectory()) {
384 return {outputFile};
385 }
386
387 // Exclude Verilog header files since we expect them to be included
388 // explicitly by compiler directives in other source files.
389 auto *context = &getContext();
390 auto fileName = fileNameAttr.getValue();
391 auto ext = llvm::sys::path::extension(fileName);
392 bool exclude = (ext == ".h" || ext == ".vh" || ext == ".svh");
393 auto outDir = targetDir;
394
395 // If the original operation has a specified output file that is not a
396 // directory, then just use that.
397 if (outputFile)
398 outDir = outputFile.getFilename();
399 // In order to output into the testbench directory, we need to have a
400 // testbench dir annotation, not have a blackbox target directory annotation
401 // (or one set to the current directory), have a DUT annotation, and the
402 // module needs to be in or under the effective design.
403 else if (!testBenchDir.empty() && targetDir == "." &&
404 !instanceInfo->allInstancesInEffectiveDesign(
405 cast<igraph::ModuleOpInterface>(origOp)))
406 outDir = testBenchDir;
407 else if (isCover)
408 outDir = coverDir;
409
410 // If targetDir is not set explicitly and this is a testbench module, then
411 // update the targetDir to be the "../testbench".
412 SmallString<128> outputFilePath(outDir);
413 llvm::sys::path::append(outputFilePath, fileName);
414 return hw::OutputFileAttr::getFromFilename(context, outputFilePath, exclude);
415}
416
417//===----------------------------------------------------------------------===//
418// Pass Creation
419//===----------------------------------------------------------------------===//
420
421std::unique_ptr<mlir::Pass>
422circt::firrtl::createBlackBoxReaderPass(std::optional<StringRef> inputPrefix) {
423 auto pass = std::make_unique<BlackBoxReaderPass>();
424 if (inputPrefix)
425 pass->inputPrefix = inputPrefix->str();
426 return pass;
427}
static OutputFileAttr getOutputFile(igraph::ModuleOpInterface op)
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
std::unique_ptr< mlir::Pass > createBlackBoxReaderPass(std::optional< mlir::StringRef > inputPrefix={})
void makeCommonPrefix(SmallString< 64 > &a, StringRef b)
Truncate a to the common prefix of a and b.
constexpr const char * blackBoxResourceFileNameAnnoClass
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:23
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