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