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