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