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