CIRCT  18.0.0git
ResolveTraces.cpp
Go to the documentation of this file.
1 //===- ResolveTraces.cpp - Resolve TraceAnnotations -------------*- 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 // Find any TraceAnnotations in the design, update their targets, and write the
9 // annotations out to an output annotation file.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
21 #include "circt/Dialect/HW/HWOps.h"
22 #include "circt/Dialect/SV/SVOps.h"
23 #include "mlir/IR/ImplicitLocOpBuilder.h"
24 #include "llvm/ADT/APSInt.h"
25 #include "llvm/ADT/TypeSwitch.h"
26 #include "llvm/Support/Debug.h"
27 #include "llvm/Support/JSON.h"
28 
29 #define DEBUG_TYPE "firrtl-resolve-traces"
30 
31 using namespace circt;
32 using namespace firrtl;
33 
34 /// Expand a TraceNameAnnotation (which has don't touch semantics) into a
35 /// TraceAnnotation (which does NOT have don't touch semantics) and separate
36 /// DontTouchAnnotations for targets that are not modules, external modules, or
37 /// instances (as these targets are not valid for a don't touch).
38 LogicalResult circt::firrtl::applyTraceName(const AnnoPathValue &target,
39  DictionaryAttr anno,
40  ApplyState &state) {
41 
42  auto *context = anno.getContext();
43 
44  NamedAttrList trace, dontTouch;
45  for (auto namedAttr : anno.getValue()) {
46  if (namedAttr.getName() == "class") {
47  trace.append("class", StringAttr::get(context, traceAnnoClass));
48  dontTouch.append("class", StringAttr::get(context, dontTouchAnnoClass));
49  continue;
50  }
51  trace.append(namedAttr);
52 
53  // When we see the "target", check to see if this is not targeting a module,
54  // extmodule, or instance (as these are invalid "don't touch" targets). If
55  // it is not, then add a DontTouchAnnotation.
56  if (namedAttr.getName() == "target" &&
57  !target.isOpOfType<FModuleOp, FExtModuleOp, InstanceOp>()) {
58  dontTouch.append(namedAttr);
59  state.addToWorklistFn(DictionaryAttr::getWithSorted(context, dontTouch));
60  }
61  }
62 
63  state.addToWorklistFn(DictionaryAttr::getWithSorted(context, trace));
64 
65  return success();
66 }
67 
68 struct ResolveTracesPass : public ResolveTracesBase<ResolveTracesPass> {
69  using ResolveTracesBase::outputAnnotationFilename;
70 
71  void runOnOperation() override;
72 
73 private:
74  /// Stores a pointer to an NLA Table. This is populated during
75  /// runOnOperation.
77 
78  /// Stores a pointer to an inner symbol table collection.
79  hw::InnerSymbolTableCollection *istc;
80 
81  /// Global symbol index used for substitutions, e.g., "{{42}}". This value is
82  /// the _next_ index that will be used.
83  unsigned symbolIdx = 0;
84 
85  /// Map of symbol to symbol index. This is used to reuse symbol
86  /// substitutions.
87  DenseMap<Attribute, unsigned> symbolMap;
88 
89  /// Symbol substitutions for the JSON verbatim op.
90  SmallVector<Attribute> symbols;
91 
92  /// Get a symbol index and update symbol datastructures.
93  unsigned getSymbolIndex(Attribute attr) {
94  auto iterator = symbolMap.find(attr);
95  if (iterator != symbolMap.end())
96  return iterator->getSecond();
97 
98  auto idx = symbolIdx++;
99  symbolMap.insert({attr, idx});
100  symbols.push_back(attr);
101 
102  return idx;
103  }
104 
105  /// Convert an annotation path to a string with symbol substitutions.
106  void buildTarget(AnnoPathValue &path, SmallString<64> &newTarget) {
107 
108  auto addSymbol = [&](Attribute attr) -> void {
109  newTarget.append("{{");
110  Twine(getSymbolIndex(attr)).toVector(newTarget);
111  newTarget.append("}}");
112  };
113 
114  newTarget.append("~");
115  newTarget.append(
116  path.ref.getModule()->getParentOfType<CircuitOp>().getName());
117  newTarget.append("|");
118 
119  if (path.isLocal()) {
120  addSymbol(
121  FlatSymbolRefAttr::get(path.ref.getModule().getModuleNameAttr()));
122  } else {
123  addSymbol(FlatSymbolRefAttr::get(path.instances.front()
124  ->getParentOfType<FModuleLike>()
125  .getModuleNameAttr()));
126  }
127 
128  for (auto inst : path.instances) {
129  newTarget.append("/");
130  addSymbol(hw::InnerRefAttr::get(
131  inst->getParentOfType<FModuleLike>().getModuleNameAttr(),
132  inst.getInnerSymAttr().getSymName()));
133  newTarget.append(":");
134  addSymbol(inst.getModuleNameAttr());
135  }
136 
137  // If this targets a module or an instance, then we're done. There is no
138  // "reference" part of the FIRRTL target.
139  if (path.ref.isa<OpAnnoTarget>() &&
140  path.isOpOfType<FModuleOp, FExtModuleOp, InstanceOp>())
141  return;
142 
143  newTarget.append(">");
144  auto innerSymStr =
145  TypeSwitch<AnnoTarget, StringAttr>(path.ref)
146  .Case<PortAnnoTarget>([&](PortAnnoTarget portTarget) {
147  return hw::InnerSymbolTable::getInnerSymbol(hw::InnerSymTarget(
148  portTarget.getPortNo(), portTarget.getModule(), 0));
149  })
150  .Case<OpAnnoTarget>([&](OpAnnoTarget opTarget) {
151  return hw::InnerSymbolTable::getInnerSymbol(opTarget.getOp());
152  })
153  .Default([](auto) {
154  assert(false && "unexpected annotation target type");
155  return StringAttr{};
156  });
157  addSymbol(hw::InnerRefAttr::get(path.ref.getModule().getModuleNameAttr(),
158  innerSymStr));
159 
160  auto type = dyn_cast<FIRRTLBaseType>(path.ref.getType());
161  assert(type && "expected a FIRRTLBaseType");
162  auto targetFieldID = path.fieldIdx;
163  while (targetFieldID) {
165  .Case<FVectorType>([&](FVectorType vector) {
166  auto index = vector.getIndexForFieldID(targetFieldID);
167  newTarget.append("[");
168  Twine(index).toVector(newTarget);
169  newTarget.append("]");
170  type = vector.getElementType();
171  targetFieldID -= vector.getFieldID(index);
172  })
173  .template Case<BundleType>([&](BundleType bundle) {
174  auto index = bundle.getIndexForFieldID(targetFieldID);
175  newTarget.append(".");
176  newTarget.append(bundle.getElementName(index));
177  type = bundle.getElementType(index);
178  targetFieldID -= bundle.getFieldID(index);
179  })
180  .Default([&](auto) { targetFieldID = 0; });
181  }
182  }
183 
184  /// Internal implementation that updates an Annotation to add a "target" field
185  /// based on the current location of the annotation in the circuit. The value
186  /// of the "target" will be a local target if the Annotation is local and a
187  /// non-local target if the Annotation is non-local.
188  AnnoPathValue updateTargetImpl(Annotation &anno, FModuleLike &module,
189  FIRRTLBaseType type, hw::InnerRefAttr name,
190  AnnoTarget target) {
191  SmallString<64> newTarget("~");
192  newTarget.append(module->getParentOfType<CircuitOp>().getName());
193  newTarget.append("|");
194 
195  SmallVector<InstanceOp> instances;
196 
197  if (auto nla = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
198  hw::HierPathOp path = nlaTable->getNLA(nla.getAttr());
199  for (auto part : path.getNamepath().getValue().drop_back()) {
200  auto inst = cast<hw::InnerRefAttr>(part);
201  instances.push_back(dyn_cast<InstanceOp>(
202  istc->getInnerSymbolTable(nlaTable->getModule(inst.getModule()))
203  .lookupOp(inst.getName())));
204  }
205  }
206 
207  AnnoPathValue path(instances, target, anno.getFieldID());
208 
209  return path;
210  }
211 
212  /// Add a "target" field to a port Annotation that indicates the current
213  /// location of the port in the circuit.
214  std::optional<AnnoPathValue> updatePortTarget(FModuleLike &module,
215  Annotation &anno,
216  unsigned portIdx,
217  hw::InnerRefAttr innerRef) {
218  auto type = getBaseType(type_cast<FIRRTLType>(module.getPortType(portIdx)));
219  return updateTargetImpl(anno, module, type, innerRef,
220  PortAnnoTarget(module, portIdx));
221  }
222 
223  /// Add a "target" field to an Annotation that indicates the current location
224  /// of a component in the circuit.
225  std::optional<AnnoPathValue> updateTarget(FModuleLike &module, Operation *op,
226  Annotation &anno,
227  hw::InnerRefAttr innerRef) {
228 
229  // Get the type of the operation either by checking for the
230  // result targeted by symbols on it (which are used to track the op)
231  // or by inspecting its single result.
232  auto is = dyn_cast<hw::InnerSymbolOpInterface>(op);
233  Type type;
234  if (is && is.getTargetResult())
235  type = is.getTargetResult().getType();
236  else {
237  if (op->getNumResults() != 1)
238  return std::nullopt;
239  type = op->getResultTypes().front();
240  }
241 
242  auto baseType = getBaseType(type_cast<FIRRTLType>(type));
243  return updateTargetImpl(anno, module, baseType, innerRef, OpAnnoTarget(op));
244  }
245 
246  /// Add a "target" field to an Annotation on a Module that indicates the
247  /// current location of the module. This will be local or non-local depending
248  /// on the Annotation.
249  std::optional<AnnoPathValue> updateModuleTarget(FModuleLike &module,
250  Annotation &anno) {
251  SmallVector<InstanceOp> instances;
252 
253  if (auto nla = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
254  hw::HierPathOp path = nlaTable->getNLA(nla.getAttr());
255  for (auto part : path.getNamepath().getValue().drop_back()) {
256  auto inst = cast<hw::InnerRefAttr>(part);
257  instances.push_back(cast<InstanceOp>(
258  istc->getInnerSymbolTable(nlaTable->getModule(inst.getModule()))
259  .lookupOp(inst.getName())));
260  }
261  }
262 
263  AnnoPathValue path(instances, OpAnnoTarget(module), 0);
264 
265  return path;
266  }
267 };
268 
270  LLVM_DEBUG(
271  llvm::dbgs() << "==----- Running ResolveTraces "
272  "-----------------------------------------------===\n"
273  << "Annotation Modifications:\n");
274 
275  // Grab the circuit (as this is used a few times below).
276  CircuitOp circuit = getOperation();
277  MLIRContext *context = circuit.getContext();
278 
279  // Populate pointer datastructures.
280  nlaTable = &getAnalysis<NLATable>();
281  istc = &getAnalysis<hw::InnerSymbolTableCollection>();
282 
283  // Function to find all Trace Annotations in the circuit, add a "target" field
284  // to them indicating the current local/non-local target of the operation/port
285  // the Annotation is attached to, copy the annotation into an
286  // "outputAnnotations" return vector, and delete the original Annotation. If
287  // a component or port is targeted by a Trace Annotation it will be given a
288  // symbol to prevent the output Trace Annotation from being made invalid by a
289  // later optimization.
290  auto onModule = [&](FModuleLike moduleLike) {
291  // Output Trace Annotations from this module only.
292  SmallVector<std::pair<Annotation, AnnoPathValue>> outputAnnotations;
293 
294  // A lazily constructed module namespace.
295  std::optional<hw::InnerSymbolNamespace> moduleNamespace;
296 
297  // Return a cached module namespace, lazily constructing it if needed.
298  auto getNamespace = [&](auto module) -> hw::InnerSymbolNamespace & {
299  if (!moduleNamespace)
300  moduleNamespace = hw::InnerSymbolNamespace(module);
301  return *moduleNamespace;
302  };
303 
304  // Visit the module.
305  AnnotationSet::removeAnnotations(moduleLike, [&](Annotation anno) {
306  if (!anno.isClass(traceAnnoClass))
307  return false;
308 
309  auto path = updateModuleTarget(moduleLike, anno);
310  if (!path)
311  return false;
312 
313  outputAnnotations.push_back({anno, *path});
314  return true;
315  });
316 
317  // Visit port annotations.
319  moduleLike, [&](unsigned portIdx, Annotation anno) {
320  if (!anno.isClass(traceAnnoClass))
321  return false;
322 
323  hw::InnerRefAttr innerRef =
324  getInnerRefTo(moduleLike, portIdx, getNamespace);
325  auto path = updatePortTarget(moduleLike, anno, portIdx, innerRef);
326  if (!path)
327  return false;
328 
329  outputAnnotations.push_back({anno, *path});
330  return true;
331  });
332 
333  // Visit component annotations.
334  moduleLike.walk([&](Operation *component) {
335  AnnotationSet::removeAnnotations(component, [&](Annotation anno) {
336  if (!anno.isClass(traceAnnoClass))
337  return false;
338 
339  hw::InnerRefAttr innerRef = getInnerRefTo(component, getNamespace);
340  auto path = updateTarget(moduleLike, component, anno, innerRef);
341  if (!path)
342  return false;
343 
344  outputAnnotations.push_back({anno, *path});
345  return true;
346  });
347  });
348 
349  return outputAnnotations;
350  };
351 
352  // Function to append one vector after another. This is used to merge results
353  // from parallel executions of "onModule".
354  auto appendVecs = [](auto &&a, auto &&b) {
355  a.append(b.begin(), b.end());
356  return std::forward<decltype(a)>(a);
357  };
358 
359  // Process all the modules in parallel or serially, depending on the
360  // multithreading context.
361  SmallVector<FModuleLike, 0> mods(circuit.getOps<FModuleLike>());
362  auto outputAnnotations = transformReduce(
363  context, mods, SmallVector<std::pair<Annotation, AnnoPathValue>>{},
364  appendVecs, onModule);
365 
366  // Do not generate an output Annotation file if no Annotations exist.
367  if (outputAnnotations.empty())
368  return markAllAnalysesPreserved();
369 
370  // Write out all the Trace Annotations to a JSON buffer.
371  std::string jsonBuffer;
372  llvm::raw_string_ostream jsonStream(jsonBuffer);
373  llvm::json::OStream json(jsonStream, /*IndentSize=*/2);
374  json.arrayBegin();
375  for (auto &[anno, path] : outputAnnotations) {
376  json.objectBegin();
377  json.attribute("class", anno.getClass());
378  SmallString<64> targetStr;
379  buildTarget(path, targetStr);
380  LLVM_DEBUG({
381  llvm::dbgs()
382  << " - chiselTarget: "
383  << anno.getDict().getAs<StringAttr>("chiselTarget").getValue() << "\n"
384  << " target: " << targetStr << "\n"
385  << " translated: " << path << "\n";
386  });
387  json.attribute("target", targetStr);
388  json.attribute("chiselTarget",
389  anno.getMember<StringAttr>("chiselTarget").getValue());
390  json.objectEnd();
391  }
392  json.arrayEnd();
393 
394  LLVM_DEBUG({
395  llvm::dbgs() << "Symbols:\n";
396  for (auto [id, symbol] : llvm::enumerate(symbols))
397  llvm::errs() << " - " << id << ": " << symbol << "\n";
398  });
399 
400  // Write the JSON-encoded Trace Annotation to a file called
401  // "$circuitName.anno.json". (This is implemented via an SVVerbatimOp that is
402  // inserted before the FIRRTL circuit.
403  auto b = OpBuilder::atBlockBegin(circuit.getBodyBlock());
404  auto verbatimOp = b.create<sv::VerbatimOp>(
405  b.getUnknownLoc(), jsonBuffer, ValueRange{}, b.getArrayAttr(symbols));
406  hw::OutputFileAttr fileAttr;
407  if (this->outputAnnotationFilename.empty())
408  fileAttr = hw::OutputFileAttr::getFromFilename(
409  context, circuit.getName() + ".anno.json",
410  /*excludeFromFilelist=*/true, false);
411  else
412  fileAttr = hw::OutputFileAttr::getFromFilename(
413  context, outputAnnotationFilename,
414  /*excludeFromFilelist=*/true, false);
415  verbatimOp->setAttr("output_file", fileAttr);
416 
417  return markAllAnalysesPreserved();
418 }
419 
420 std::unique_ptr<mlir::Pass>
421 circt::firrtl::createResolveTracesPass(StringRef outputAnnotationFilename) {
422  auto pass = std::make_unique<ResolveTracesPass>();
423  if (!outputAnnotationFilename.empty())
424  pass->outputAnnotationFilename = outputAnnotationFilename.str();
425  return pass;
426 }
assert(baseType &&"element must be base type")
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
unsigned getFieldID() const
Get the field id this attribute targets.
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 class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
Definition: FIRRTLTypes.h:515
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
Definition: FIRRTLTypes.h:525
This table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
hw::HierPathOp getNLA(StringAttr name)
Resolve a symbol to an NLA.
Definition: NLATable.cpp:48
FModuleLike getModule(StringAttr name)
Resolve a symbol to a Module.
Definition: NLATable.cpp:53
static StringAttr getInnerSymbol(Operation *op)
Get InnerSymbol for an operation.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
Definition: FIRRTLUtils.h:217
std::unique_ptr< mlir::Pass > createResolveTracesPass(mlir::StringRef outputAnnotationFilename="")
constexpr const char * traceAnnoClass
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
LogicalResult applyTraceName(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Expand a TraceNameAnnotation (which has don't touch semantics) into a TraceAnnotation (which does NOT...
static ResultTy transformReduce(MLIRContext *context, IterTy begin, IterTy end, ResultTy init, ReduceFuncTy reduce, TransformFuncTy transform)
Wrapper for llvm::parallelTransformReduce that performs the transform_reduce serially when MLIR multi...
Definition: FIRRTLUtils.h:288
constexpr const char * dontTouchAnnoClass
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & errs()
Definition: Utility.h:33
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
void runOnOperation() override
AnnoPathValue updateTargetImpl(Annotation &anno, FModuleLike &module, FIRRTLBaseType type, hw::InnerRefAttr name, AnnoTarget target)
Internal implementation that updates an Annotation to add a "target" field based on the current locat...
std::optional< AnnoPathValue > updateTarget(FModuleLike &module, Operation *op, Annotation &anno, hw::InnerRefAttr innerRef)
Add a "target" field to an Annotation that indicates the current location of a component in the circu...
NLATable * nlaTable
Stores a pointer to an NLA Table.
hw::InnerSymbolTableCollection * istc
Stores a pointer to an inner symbol table collection.
std::optional< AnnoPathValue > updateModuleTarget(FModuleLike &module, Annotation &anno)
Add a "target" field to an Annotation on a Module that indicates the current location of the module.
unsigned getSymbolIndex(Attribute attr)
Get a symbol index and update symbol datastructures.
std::optional< AnnoPathValue > updatePortTarget(FModuleLike &module, Annotation &anno, unsigned portIdx, hw::InnerRefAttr innerRef)
Add a "target" field to a port Annotation that indicates the current location of the port in the circ...
void buildTarget(AnnoPathValue &path, SmallString< 64 > &newTarget)
Convert an annotation path to a string with symbol substitutions.
SmallVector< Attribute > symbols
Symbol substitutions for the JSON verbatim op.
DenseMap< Attribute, unsigned > symbolMap
Map of symbol to symbol index.
SmallVector< InstanceOp > instances
An annotation target is used to keep track of something that is targeted by an Annotation.
FIRRTLType getType() const
Get the type of the target.
Operation * getOp() const
FModuleLike getModule() const
Get the parent module of the target.
State threaded through functions for resolving and applying annotations.
This represents an annotation targeting a specific operation.
This represents an annotation targeting a specific port of a module, memory, or instance.