CIRCT 24.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 = !coverDir.empty() &&
193 annotations.hasAnnotation(internalVerifBlackBoxAnnoClass);
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());
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.
285 if (anno.isClass(blackBoxInlineAnnoClass)) {
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 std::unique_ptr< Context > context
static void print(TypedAttr val, llvm::raw_ostream &os)
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
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.
bool anyInstanceInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if any instance of this module is within (or transitively within) the effective design.
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void makeCommonDirectoryPrefix(llvm::SmallVectorImpl< char > &a, StringRef b)
Truncate a in place to the longest common directory prefix of a and b, ensuring that the result ends ...
Definition Path.cpp:36
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