CIRCT 22.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/ADT/MapVector.h"
32#include "llvm/Support/Debug.h"
33#include "llvm/Support/FormatAdapters.h"
34#include "llvm/Support/FormatVariadic.h"
35#include "llvm/Support/MemoryBuffer.h"
36#include "llvm/Support/Path.h"
37
38#define DEBUG_TYPE "firrtl-blackbox-reader"
39
40namespace circt {
41namespace firrtl {
42#define GEN_PASS_DEF_BLACKBOXREADER
43#include "circt/Dialect/FIRRTL/Passes.h.inc"
44} // namespace firrtl
45} // namespace circt
46
47using namespace circt;
48using namespace firrtl;
49
50//===----------------------------------------------------------------------===//
51// Pass Implementation
52//===----------------------------------------------------------------------===//
53
54namespace {
55
56/// Data extracted from BlackBoxInlineAnno or BlackBoxPathAnno.
57struct AnnotationInfo {
58 /// The name of the file that should be created for this BlackBox.
59 StringAttr name;
60 /// The output directory information for this extmodule.
61 hw::OutputFileAttr outputFileAttr;
62 /// The body of the BlackBox. (This should be Verilog text.)
63 StringAttr inlineText;
64
65#if !defined(NDEBUG)
66 /// Pretty print the AnnotationInfo in a YAML-esque format.
67 void print(raw_ostream &os, unsigned indent = 0) const {
68 os << llvm::formatv("name: {1}\n"
69 "{0}outputFile: {2}\n"
70 "{0}exclude: {3}\n",
71 llvm::fmt_pad("", indent, 0), name,
72 outputFileAttr.getFilename(),
73 outputFileAttr.getExcludeFromFilelist().getValue());
74 };
75#endif
76};
77
78struct BlackBoxReaderPass
79 : public circt::firrtl::impl::BlackBoxReaderBase<BlackBoxReaderPass> {
80 using Base::Base;
81
82 void runOnOperation() override;
83 bool runOnAnnotation(Operation *op, Annotation anno, OpBuilder &builder,
84 bool isCover, AnnotationInfo &annotationInfo);
85 StringAttr loadFile(Operation *op, StringRef inputPath, OpBuilder &builder);
86 hw::OutputFileAttr getOutputFile(Operation *origOp, StringAttr fileNameAttr,
87 bool isCover = false);
88
89private:
90 /// The target directory to output black boxes into. Can be changed
91 /// through `firrtl.transforms.BlackBoxTargetDirAnno` annotations.
92 StringRef targetDir;
93
94 /// The target directory for cover statements.
95 StringRef coverDir;
96
97 /// The target directory for testbench files.
98 StringRef testBenchDir;
99
100 /// Analyses used by this pass.
101 InstanceGraph *instanceGraph;
102 InstanceInfo *instanceInfo;
103
104 /// An ordered map of Verilog filenames to the annotation-derived information
105 /// that will be used to create this file. Due to situations where multiple
106 /// external modules may not deduplicate (e.g., they have different
107 /// parameters), multiple annotations may all want to write to the same file.
108 /// This always tracks the actual annotation that will be used. If a more
109 /// appropriate annotation is found (e.g., which will cause the file to be
110 /// written to the DUT directory and not the TestHarness directory), then this
111 /// will map will be updated.
112 llvm::MapVector<StringAttr, AnnotationInfo> filenameToAnnotationInfo;
113};
114} // end anonymous namespace
115
116/// Emit the annotated source code for black boxes in a circuit.
117void BlackBoxReaderPass::runOnOperation() {
119 CircuitOp circuitOp = getOperation();
120 CircuitNamespace ns(circuitOp);
121
122 instanceGraph = &getAnalysis<InstanceGraph>();
123 instanceInfo = &getAnalysis<InstanceInfo>();
124 auto context = &getContext();
125
126 // If this pass has changed anything.
127 bool anythingChanged = false;
128
129 // Internalize some string attributes for easy reference later.
130
131 // Determine the target directory and resource file name from the
132 // annotations present on the circuit operation.
133 targetDir = ".";
134
135 // Process black box annotations on the circuit. Some of these annotations
136 // will affect how the rest of the annotations are resolved.
137 SmallVector<Attribute, 4> filteredAnnos;
138 for (auto annot : AnnotationSet(circuitOp)) {
139 filteredAnnos.push_back(annot.getDict());
140
141 // Get the testbench and cover directories.
142 if (annot.isClass(extractCoverageAnnoClass))
143 if (auto dir = annot.getMember<StringAttr>("directory")) {
144 coverDir = dir.getValue();
145 continue;
146 }
147
148 if (annot.isClass(testBenchDirAnnoClass))
149 if (auto dir = annot.getMember<StringAttr>("dirname")) {
150 testBenchDir = dir.getValue();
151 continue;
152 }
153
154 // Handle target dir annotation.
155 if (annot.isClass(blackBoxTargetDirAnnoClass)) {
156 if (auto target = annot.getMember<StringAttr>("targetDir")) {
157 targetDir = target.getValue();
158 continue;
159 }
160 circuitOp->emitError(blackBoxTargetDirAnnoClass)
161 << " annotation missing \"targetDir\" attribute";
162 signalPassFailure();
163 continue;
164 }
165 }
166 // Apply the filtered annotations to the circuit. If we updated the circuit
167 // and record that they changed.
168 anythingChanged |=
169 AnnotationSet(filteredAnnos, context).applyToOperation(circuitOp);
170
171 LLVM_DEBUG(llvm::dbgs() << "Black box target directory: " << targetDir
172 << "\n");
173
174 // Newly generated IR will be placed at the end of the circuit.
175 auto builder = circuitOp.getBodyBuilder();
176
177 LLVM_DEBUG(llvm::dbgs() << "Visiting extmodules:\n");
178 auto bboxAnno =
179 builder.getDictionaryAttr({{builder.getStringAttr("class"),
180 builder.getStringAttr(blackBoxAnnoClass)}});
181 // Track which modules reference which files
182 llvm::MapVector<Operation *, SmallVector<StringAttr>>
183 verbatimExtmoduleToFiles;
184
185 for (auto extmoduleOp : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
186 LLVM_DEBUG({
187 llvm::dbgs().indent(2)
188 << "- name: " << extmoduleOp.getModuleNameAttr() << "\n";
189 llvm::dbgs().indent(4) << "annotations:\n";
190 });
191 AnnotationSet annotations(extmoduleOp);
192 bool isCover =
193 !coverDir.empty() && annotations.hasAnnotation(verifBlackBoxAnnoClass);
194 bool foundBBoxAnno = false;
195 annotations.removeAnnotations([&](Annotation anno) {
196 AnnotationInfo annotationInfo;
197 if (!runOnAnnotation(extmoduleOp, anno, builder, isCover, annotationInfo))
198 return false;
199
200 LLVM_DEBUG(annotationInfo.print(llvm::dbgs().indent(6) << "- ", 8));
201
202 // Track that this module references this file
203 verbatimExtmoduleToFiles[extmoduleOp].push_back(annotationInfo.name);
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] = filenameToAnnotationInfo.try_emplace(
211 annotationInfo.name, annotationInfo);
212 if (inserted) {
213 filenameToAnnotationInfo[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 // Now create VerbatimBlackBoxAnno for each module with its referenced files
238 for (auto &[moduleOp, fileNames] : verbatimExtmoduleToFiles) {
239 AnnotationSet annotations(moduleOp);
240 SmallVector<Attribute> verbatimFiles;
241 llvm::SmallPtrSet<StringAttr, 4> seenFiles;
242
243 for (StringAttr fileName : fileNames) {
244 if (!seenFiles.insert(fileName).second)
245 continue;
246
247 // Look up the computed LCA output file information
248 auto &annotationInfo = filenameToAnnotationInfo[fileName];
249 auto fileDict = builder.getDictionaryAttr(
250 {{builder.getStringAttr("content"), annotationInfo.inlineText},
251 {builder.getStringAttr("output_file"),
252 builder.getStringAttr(
253 annotationInfo.outputFileAttr.getFilename().getValue())}});
254 verbatimFiles.push_back(fileDict);
255 }
256
257 if (!verbatimFiles.empty()) {
258 annotations.addAnnotations({builder.getDictionaryAttr(
259 {{builder.getStringAttr("class"),
260 builder.getStringAttr(verbatimBlackBoxAnnoClass)},
261 {builder.getStringAttr("files"),
262 builder.getArrayAttr(verbatimFiles)}})});
263 annotations.applyToOperation(moduleOp);
264 anythingChanged = true;
265 }
266 }
267
268 // If nothing has changed we can preserve the analysis.
269 if (!anythingChanged)
270 markAllAnalysesPreserved();
271 markAnalysesPreserved<InstanceGraph, InstanceInfo>();
272
273 // Clean up.
274 filenameToAnnotationInfo.clear();
275 verbatimExtmoduleToFiles.clear();
276}
277
278/// Run on an operation-annotation pair. The annotation need not be a black box
279/// annotation. Returns `true` if the annotation was indeed a black box
280/// annotation (even if it was incomplete) and should be removed from the op.
281bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno,
282 OpBuilder &builder, bool isCover,
283 AnnotationInfo &annotationInfo) {
284 // Handle inline annotation.
286 auto name = anno.getMember<StringAttr>("name");
287 auto text = anno.getMember<StringAttr>("text");
288 if (!name || !text) {
289 op->emitError(blackBoxInlineAnnoClass)
290 << " annotation missing \"name\" or \"text\" attribute";
291 signalPassFailure();
292 return true;
293 }
294
295 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
296 annotationInfo.name = name;
297 annotationInfo.inlineText = text;
298 return true;
299 }
300
301 // Handle path annotation.
302 if (anno.isClass(blackBoxPathAnnoClass)) {
303 auto path = anno.getMember<StringAttr>("path");
304 if (!path) {
305 op->emitError(blackBoxPathAnnoClass)
306 << " annotation missing \"path\" attribute";
307 signalPassFailure();
308 return true;
309 }
310 SmallString<128> inputPath(inputPrefix);
311 appendPossiblyAbsolutePath(inputPath, path.getValue());
312 auto text = loadFile(op, inputPath, builder);
313 if (!text) {
314 op->emitError("Cannot find file ") << inputPath;
315 signalPassFailure();
316 return false;
317 }
318 auto name = builder.getStringAttr(llvm::sys::path::filename(path));
319 annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
320 annotationInfo.name = name;
321 annotationInfo.inlineText = text;
322 return true;
323 }
324
325 // Annotation was not concerned with black boxes.
326 return false;
327}
328
329/// Copies a black box source file to the appropriate location in the target
330/// directory.
331StringAttr BlackBoxReaderPass::loadFile(Operation *op, StringRef inputPath,
332 OpBuilder &builder) {
333 LLVM_DEBUG(llvm::dbgs() << "Add black box source `"
334 << llvm::sys::path::filename(inputPath) << "` from `"
335 << inputPath << "`\n");
336
337 // Open and read the input file.
338 std::string errorMessage;
339 auto input = mlir::openInputFile(inputPath, &errorMessage);
340 if (!input)
341 return {};
342
343 // Return a StringAttr with the buffer contents.
344 return builder.getStringAttr(input->getBuffer());
345}
346
347/// Determine the output file for some operation.
348hw::OutputFileAttr BlackBoxReaderPass::getOutputFile(Operation *origOp,
349 StringAttr fileNameAttr,
350 bool isCover) {
351 // If the original operation has a specified output file that is not a
352 // directory, then just use that.
353 auto outputFile = origOp->getAttrOfType<hw::OutputFileAttr>("output_file");
354 if (outputFile && !outputFile.isDirectory()) {
355 return {outputFile};
356 }
357
358 // Exclude Verilog header files since we expect them to be included
359 // explicitly by compiler directives in other source files.
360 auto *context = &getContext();
361 auto fileName = fileNameAttr.getValue();
362 auto ext = llvm::sys::path::extension(fileName);
363 bool exclude = (ext == ".h" || ext == ".vh" || ext == ".svh");
364 auto outDir = targetDir;
365
366 /// If the original operation has an output directory, use that.
367 if (outputFile)
368 outDir = outputFile.getFilename();
369 // In order to output into the testbench directory, we need to have a
370 // testbench dir annotation, not have a blackbox target directory annotation
371 // (or one set to the current directory), have a DUT annotation, and the
372 // module must not be instantiated under the DUT.
373 else if (!testBenchDir.empty() && targetDir == "." &&
374 !instanceInfo->anyInstanceInEffectiveDesign(
375 cast<igraph::ModuleOpInterface>(origOp)))
376 outDir = testBenchDir;
377 else if (isCover)
378 outDir = coverDir;
379
380 // If targetDir is not set explicitly and this is a testbench module, then
381 // update the targetDir to be the "../testbench".
382 SmallString<128> outputFilePath(outDir);
383 llvm::sys::path::append(outputFilePath, fileName);
384 return hw::OutputFileAttr::getFromFilename(context, outputFilePath, exclude);
385}
static OutputFileAttr getOutputFile(igraph::ModuleOpInterface op)
static void print(TypedAttr val, llvm::raw_ostream &os)
static Block * getBodyBlock(FModuleLike mod)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
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.
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
constexpr const char * verbatimBlackBoxAnnoClass
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
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24