CIRCT  20.0.0git
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/Dialect/SV/SVOps.h"
27 #include "circt/Support/Debug.h"
28 #include "circt/Support/Path.h"
29 #include "mlir/IR/Attributes.h"
30 #include "mlir/Pass/Pass.h"
31 #include "mlir/Support/FileUtilities.h"
32 #include "llvm/ADT/SmallPtrSet.h"
33 #include "llvm/Support/Debug.h"
34 #include "llvm/Support/FormatAdapters.h"
35 #include "llvm/Support/FormatVariadic.h"
36 #include "llvm/Support/MemoryBuffer.h"
37 #include "llvm/Support/Path.h"
38 
39 #define DEBUG_TYPE "firrtl-blackbox-reader"
40 
41 namespace circt {
42 namespace firrtl {
43 #define GEN_PASS_DEF_BLACKBOXREADER
44 #include "circt/Dialect/FIRRTL/Passes.h.inc"
45 } // namespace firrtl
46 } // namespace circt
47 
48 using namespace circt;
49 using namespace firrtl;
50 
51 //===----------------------------------------------------------------------===//
52 // Pass Implementation
53 //===----------------------------------------------------------------------===//
54 
55 namespace {
56 
57 /// Data extracted from BlackBoxInlineAnno or BlackBoxPathAnno.
58 struct AnnotationInfo {
59  /// The name of the file that should be created for this BlackBox.
60  StringAttr name;
61  /// The output directory information for this extmodule.
62  hw::OutputFileAttr outputFileAttr;
63  /// The body of the BlackBox. (This should be Verilog text.)
64  StringAttr inlineText;
65 
66 #if !defined(NDEBUG)
67  /// Pretty print the AnnotationInfo in a YAML-esque format.
68  void print(raw_ostream &os, unsigned indent = 0) const {
69  os << llvm::formatv("name: {1}\n"
70  "{0}outputFile: {2}\n"
71  "{0}exclude: {3}\n",
72  llvm::fmt_pad("", indent, 0), name,
73  outputFileAttr.getFilename(),
74  outputFileAttr.getExcludeFromFilelist().getValue());
75  };
76 #endif
77 };
78 
79 struct BlackBoxReaderPass
80  : public circt::firrtl::impl::BlackBoxReaderBase<BlackBoxReaderPass> {
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  // Check if module or any of its parents in the InstanceGraph is a DUT.
88  bool isDut(Operation *module);
89 
90  using BlackBoxReaderBase::inputPrefix;
91 
92 private:
93  /// A list of all files which will be included in the file list. This is
94  /// subset of all emitted files.
95  SmallVector<emit::FileOp> fileListFiles;
96 
97  /// The target directory to output black boxes into. Can be changed
98  /// through `firrtl.transforms.BlackBoxTargetDirAnno` annotations.
99  StringRef targetDir;
100 
101  /// The target directory for cover statements.
102  StringRef coverDir;
103 
104  /// The target directory for testbench files.
105  StringRef testBenchDir;
106 
107  /// The design-under-test (DUT) as indicated by the presence of a
108  /// "sifive.enterprise.firrtl.MarkDUTAnnotation". This will be null if no
109  /// annotation is present.
110  FModuleOp dut;
111 
112  /// The file list file name (sic) for black boxes. If set, generates a file
113  /// that lists all non-header source files for black boxes. Can be changed
114  /// through `firrtl.transforms.BlackBoxResourceFileNameAnno` annotations.
115  StringRef resourceFileName;
116 
117  /// InstanceGraph to determine modules which are under the DUT.
118  InstanceGraph *instanceGraph;
119 
120  /// A cache of the modules which have been marked as DUT or a testbench.
121  /// This is used to determine the output directory.
122  DenseMap<Operation *, bool> dutModuleMap;
123 
124  /// An ordered map of Verilog filenames to the annotation-derived information
125  /// that will be used to create this file. Due to situations where multiple
126  /// external modules may not deduplicate (e.g., they have different
127  /// parameters), multiple annotations may all want to write to the same file.
128  /// This always tracks the actual annotation that will be used. If a more
129  /// appropriate annotation is found (e.g., which will cause the file to be
130  /// written to the DUT directory and not the TestHarness directory), then this
131  /// will map will be updated.
132  llvm::MapVector<StringAttr, AnnotationInfo> emittedFileMap;
133 };
134 } // end anonymous namespace
135 
136 /// Emit the annotated source code for black boxes in a circuit.
137 void BlackBoxReaderPass::runOnOperation() {
138  LLVM_DEBUG(debugPassHeader(this) << "\n");
139  CircuitOp circuitOp = getOperation();
140  CircuitNamespace ns(circuitOp);
141 
142  instanceGraph = &getAnalysis<InstanceGraph>();
143  auto context = &getContext();
144 
145  // If this pass has changed anything.
146  bool anythingChanged = false;
147 
148  // Internalize some string attributes for easy reference later.
149 
150  // Determine the target directory and resource file name from the
151  // annotations present on the circuit operation.
152  targetDir = ".";
153  resourceFileName = "firrtl_black_box_resource_files.f";
154 
155  // Process black box annotations on the circuit. Some of these annotations
156  // will affect how the rest of the annotations are resolved.
157  SmallVector<Attribute, 4> filteredAnnos;
158  for (auto annot : AnnotationSet(circuitOp)) {
159  // Handle resource file name annotation.
160  if (annot.isClass(blackBoxResourceFileNameAnnoClass)) {
161  if (auto resourceFN = annot.getMember<StringAttr>("resourceFileName")) {
162  resourceFileName = resourceFN.getValue();
163  continue;
164  }
165 
166  circuitOp->emitError(blackBoxResourceFileNameAnnoClass)
167  << " annotation missing \"resourceFileName\" attribute";
168  signalPassFailure();
169  continue;
170  }
171  filteredAnnos.push_back(annot.getDict());
172 
173  // Get the testbench and cover directories.
174  if (annot.isClass(extractCoverageAnnoClass))
175  if (auto dir = annot.getMember<StringAttr>("directory")) {
176  coverDir = dir.getValue();
177  continue;
178  }
179 
180  if (annot.isClass(testBenchDirAnnoClass))
181  if (auto dir = annot.getMember<StringAttr>("dirname")) {
182  testBenchDir = dir.getValue();
183  continue;
184  }
185 
186  // Handle target dir annotation.
187  if (annot.isClass(blackBoxTargetDirAnnoClass)) {
188  if (auto target = annot.getMember<StringAttr>("targetDir")) {
189  targetDir = target.getValue();
190  continue;
191  }
192  circuitOp->emitError(blackBoxTargetDirAnnoClass)
193  << " annotation missing \"targetDir\" attribute";
194  signalPassFailure();
195  continue;
196  }
197  }
198  // Apply the filtered annotations to the circuit. If we updated the circuit
199  // and record that they changed.
200  anythingChanged |=
201  AnnotationSet(filteredAnnos, context).applyToOperation(circuitOp);
202 
203  LLVM_DEBUG(llvm::dbgs() << "Black box target directory: " << targetDir << "\n"
204  << "Black box resource file name: "
205  << resourceFileName << "\n");
206 
207  // Newly generated IR will be placed at the end of the circuit.
208  auto builder = circuitOp.getBodyBuilder();
209 
210  // Do a shallow walk of the circuit to collect information necessary before we
211  // do real work.
212  for (auto &op : *circuitOp.getBodyBlock()) {
213  FModuleOp module = dyn_cast<FModuleOp>(op);
214  // Find the DUT if it exists or error if there are multiple DUTs.
215  if (module)
216  if (failed(extractDUT(module, dut)))
217  return signalPassFailure();
218  }
219 
220  LLVM_DEBUG(llvm::dbgs() << "Visiting extmodules:\n");
221  auto bboxAnno =
222  builder.getDictionaryAttr({{builder.getStringAttr("class"),
223  builder.getStringAttr(blackBoxAnnoClass)}});
224  for (auto extmoduleOp : circuitOp.getBodyBlock()->getOps<FExtModuleOp>()) {
225  LLVM_DEBUG({
226  llvm::dbgs().indent(2)
227  << "- name: " << extmoduleOp.getModuleNameAttr() << "\n";
228  llvm::dbgs().indent(4) << "annotations:\n";
229  });
230  AnnotationSet annotations(extmoduleOp);
231  bool isCover =
232  !coverDir.empty() && annotations.hasAnnotation(verifBlackBoxAnnoClass);
233  bool foundBBoxAnno = false;
234  annotations.removeAnnotations([&](Annotation anno) {
235  AnnotationInfo annotationInfo;
236  if (!runOnAnnotation(extmoduleOp, anno, builder, isCover, annotationInfo))
237  return false;
238 
239  LLVM_DEBUG(annotationInfo.print(llvm::dbgs().indent(6) << "- ", 8));
240 
241  // If we have seen a black box trying to create a blackbox with this
242  // filename before, then compute the lowest commmon ancestor between the
243  // two blackbox paths. This is the same logic used in `AssignOutputDirs`.
244  // However, this needs to incorporate filenames that are only available
245  // _after_ output directories are assigned.
246  auto [ptr, inserted] =
247  emittedFileMap.try_emplace(annotationInfo.name, annotationInfo);
248  if (inserted) {
249  emittedFileMap[annotationInfo.name] = annotationInfo;
250  } else {
251  auto &fileAttr = ptr->second.outputFileAttr;
252  SmallString<64> directory(fileAttr.getDirectory());
253  makeCommonPrefix(directory,
254  annotationInfo.outputFileAttr.getDirectory());
255  fileAttr = hw::OutputFileAttr::getFromDirectoryAndFilename(
256  context, directory, annotationInfo.name.getValue(),
257  /*excludeFromFileList=*/
258  fileAttr.getExcludeFromFilelist().getValue());
259  // TODO: Check that the new text is the _exact same_ as the prior best.
260  }
261 
262  foundBBoxAnno = true;
263  return true;
264  });
265 
266  if (foundBBoxAnno) {
267  annotations.addAnnotations({bboxAnno});
268  anythingChanged = true;
269  }
270  annotations.applyToOperation(extmoduleOp);
271  }
272 
273  LLVM_DEBUG(llvm::dbgs() << "emittedFiles:\n");
274  Location loc = builder.getUnknownLoc();
275  for (auto &[verilogName, annotationInfo] : emittedFileMap) {
276  LLVM_DEBUG({
277  llvm::dbgs().indent(2) << "verilogName: " << verilogName << "\n";
278  llvm::dbgs().indent(2) << "annotationInfo:\n";
279  annotationInfo.print(llvm::dbgs().indent(4) << "- ", 6);
280  });
281 
282  auto fileName = ns.newName("blackbox_" + verilogName.getValue());
283 
284  auto fileOp = builder.create<emit::FileOp>(
285  loc, annotationInfo.outputFileAttr.getFilename(), fileName,
286  [&, text = annotationInfo.inlineText] {
287  builder.create<emit::VerbatimOp>(loc, text);
288  });
289 
290  if (!annotationInfo.outputFileAttr.getExcludeFromFilelist().getValue())
291  fileListFiles.push_back(fileOp);
292  }
293 
294  // If we have emitted any files, generate a file list operation that
295  // documents the additional annotation-controlled file listing to be
296  // created.
297  if (!fileListFiles.empty()) {
298  // Output the file list in sorted order.
299  llvm::sort(fileListFiles.begin(), fileListFiles.end(),
300  [](emit::FileOp fileA, emit::FileOp fileB) {
301  return fileA.getFileName() < fileB.getFileName();
302  });
303 
304  // Create the file list contents by enumerating the symbols to the files.
305  SmallVector<Attribute> symbols;
306  for (emit::FileOp file : fileListFiles)
307  symbols.push_back(FlatSymbolRefAttr::get(file.getSymNameAttr()));
308 
309  builder.create<emit::FileListOp>(
310  loc, builder.getStringAttr(resourceFileName),
311  builder.getArrayAttr(symbols),
312  builder.getStringAttr(ns.newName("blackbox_filelist")));
313  }
314 
315  // If nothing has changed we can preserve the analysis.
316  if (!anythingChanged)
317  markAllAnalysesPreserved();
318  markAnalysesPreserved<InstanceGraph>();
319 
320  // Clean up.
321  emittedFileMap.clear();
322  fileListFiles.clear();
323  LLVM_DEBUG(debugFooter() << "\n");
324 }
325 
326 /// Run on an operation-annotation pair. The annotation need not be a black box
327 /// annotation. Returns `true` if the annotation was indeed a black box
328 /// annotation (even if it was incomplete) and should be removed from the op.
329 bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno,
330  OpBuilder &builder, bool isCover,
331  AnnotationInfo &annotationInfo) {
332  // Handle inline annotation.
333  if (anno.isClass(blackBoxInlineAnnoClass)) {
334  auto name = anno.getMember<StringAttr>("name");
335  auto text = anno.getMember<StringAttr>("text");
336  if (!name || !text) {
337  op->emitError(blackBoxInlineAnnoClass)
338  << " annotation missing \"name\" or \"text\" attribute";
339  signalPassFailure();
340  return true;
341  }
342 
343  annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
344  annotationInfo.name = name;
345  annotationInfo.inlineText = text;
346  return true;
347  }
348 
349  // Handle path annotation.
350  if (anno.isClass(blackBoxPathAnnoClass)) {
351  auto path = anno.getMember<StringAttr>("path");
352  if (!path) {
353  op->emitError(blackBoxPathAnnoClass)
354  << " annotation missing \"path\" attribute";
355  signalPassFailure();
356  return true;
357  }
358  SmallString<128> inputPath(inputPrefix);
359  appendPossiblyAbsolutePath(inputPath, path.getValue());
360  auto text = loadFile(op, inputPath, builder);
361  if (!text) {
362  op->emitError("Cannot find file ") << inputPath;
363  signalPassFailure();
364  return false;
365  }
366  auto name = builder.getStringAttr(llvm::sys::path::filename(path));
367  annotationInfo.outputFileAttr = getOutputFile(op, name, isCover);
368  annotationInfo.name = name;
369  annotationInfo.inlineText = text;
370  return true;
371  }
372 
373  // Annotation was not concerned with black boxes.
374  return false;
375 }
376 
377 /// Copies a black box source file to the appropriate location in the target
378 /// directory.
379 StringAttr BlackBoxReaderPass::loadFile(Operation *op, StringRef inputPath,
380  OpBuilder &builder) {
381  LLVM_DEBUG(llvm::dbgs() << "Add black box source `"
382  << llvm::sys::path::filename(inputPath) << "` from `"
383  << inputPath << "`\n");
384 
385  // Open and read the input file.
386  std::string errorMessage;
387  auto input = mlir::openInputFile(inputPath, &errorMessage);
388  if (!input)
389  return {};
390 
391  // Return a StringAttr with the buffer contents.
392  return builder.getStringAttr(input->getBuffer());
393 }
394 
395 /// Determine the output file for some operation.
396 hw::OutputFileAttr BlackBoxReaderPass::getOutputFile(Operation *origOp,
397  StringAttr fileNameAttr,
398  bool isCover) {
399  auto outputFile = origOp->getAttrOfType<hw::OutputFileAttr>("output_file");
400  if (outputFile && !outputFile.isDirectory()) {
401  return {outputFile};
402  }
403 
404  // Exclude Verilog header files since we expect them to be included
405  // explicitly by compiler directives in other source files.
406  auto *context = &getContext();
407  auto fileName = fileNameAttr.getValue();
408  auto ext = llvm::sys::path::extension(fileName);
409  bool exclude = (ext == ".h" || ext == ".vh" || ext == ".svh");
410  auto outDir = targetDir;
411  // If the original operation has a specified output file that is not a
412  // directory, then just use that.
413  if (outputFile)
414  outDir = outputFile.getFilename();
415  // In order to output into the testbench directory, we need to have a
416  // testbench dir annotation, not have a blackbox target directory annotation
417  // (or one set to the current directory), have a DUT annotation, and the
418  // module needs to be in or under the DUT.
419  else if (!testBenchDir.empty() && targetDir == "." && dut && !isDut(origOp))
420  outDir = testBenchDir;
421  else if (isCover)
422  outDir = coverDir;
423 
424  // If targetDir is not set explicitly and this is a testbench module, then
425  // update the targetDir to be the "../testbench".
426  SmallString<128> outputFilePath(outDir);
427  llvm::sys::path::append(outputFilePath, fileName);
428  return hw::OutputFileAttr::getFromFilename(context, outputFilePath, exclude);
429 }
430 
431 /// Return true if module is in the DUT hierarchy.
432 /// NOLINTNEXTLINE(misc-no-recursion)
433 bool BlackBoxReaderPass::isDut(Operation *module) {
434  // Check if result already cached.
435  auto iter = dutModuleMap.find(module);
436  if (iter != dutModuleMap.end())
437  return iter->getSecond();
438  // Any module with the dutAnno, is the DUT.
440  dutModuleMap[module] = true;
441  return true;
442  }
443  auto *node = instanceGraph->lookup(cast<igraph::ModuleOpInterface>(module));
444  bool anyParentIsDut = false;
445  if (node)
446  for (auto *u : node->uses()) {
447  if (cast<InstanceOp>(u->getInstance().getOperation()).getLowerToBind())
448  return false;
449  // Recursively check the parents.
450  auto dut = isDut(u->getInstance()->getParentOfType<FModuleOp>());
451  // Cache the result.
452  dutModuleMap[module] = dut;
453  anyParentIsDut |= dut;
454  }
455  dutModuleMap[module] = anyParentIsDut;
456  return anyParentIsDut;
457 }
458 
459 //===----------------------------------------------------------------------===//
460 // Pass Creation
461 //===----------------------------------------------------------------------===//
462 
463 std::unique_ptr<mlir::Pass>
464 circt::firrtl::createBlackBoxReaderPass(std::optional<StringRef> inputPrefix) {
465  auto pass = std::make_unique<BlackBoxReaderPass>();
466  if (inputPrefix)
467  pass->inputPrefix = inputPrefix->str();
468  return pass;
469 }
static OutputFileAttr getOutputFile(igraph::ModuleOpInterface op)
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
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...
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
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
LogicalResult extractDUT(FModuleOp mod, FModuleOp &dut)
Utility that searches for a MarkDUTAnnotation on a specific module, mod, and tries to update a design...
constexpr const char * testBenchDirAnnoClass
constexpr const char * dutAnnoClass
std::unique_ptr< mlir::Pass > createBlackBoxReaderPass(std::optional< mlir::StringRef > inputPrefix={})
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.
constexpr const char * blackBoxResourceFileNameAnnoClass
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
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