Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
23#include "circt/Support/Utils.h"
24#include "mlir/IR/ImplicitLocOpBuilder.h"
25#include "mlir/Pass/Pass.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
32namespace circt {
33namespace firrtl {
34#define GEN_PASS_DEF_RESOLVETRACES
35#include "circt/Dialect/FIRRTL/Passes.h.inc"
36} // namespace firrtl
37} // namespace circt
38
39using namespace circt;
40using 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).
46LogicalResult 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 Base::Base;
79
80 void runOnOperation() override;
81
82private:
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.
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) {
157 portTarget.getPortNo(), portTarget.getModule(), 0));
158 })
159 .Case<OpAnnoTarget>([&](OpAnnoTarget opTarget) {
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 emit::FileOp::create(builder, fileAttr, [&] {
421 sv::VerbatimOp::create(builder, jsonBuffer, ValueRange{},
422 builder.getArrayAttr(symbols));
423 });
424
425 return markAllAnalysesPreserved();
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 ...
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
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
The target of an inner symbol, the entity the symbol is a handle for.
This class represents a collection of InnerSymbolTable's.
static StringAttr getInnerSymbol(Operation *op)
Get InnerSymbol for an operation.
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
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...
constexpr const char * dontTouchAnnoClass
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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 Utils.h:40
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 runOnOperation() override
unsigned symbolIdx
Global symbol index used for substitutions, e.g., "{{42}}".
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...
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 > updateModuleTarget(FModuleLike &module, Annotation &anno)
Add a "target" field to an Annotation on a Module that indicates the current location of the module.
NLATable * nlaTable
Stores a pointer to an NLA Table.
hw::InnerSymbolTableCollection * istc
Stores a pointer to an inner symbol table collection.
unsigned getSymbolIndex(Attribute attr)
Get a symbol index and update symbol datastructures.
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.
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.