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