CIRCT 21.0.0git
Loading...
Searching...
No Matches
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 "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
31namespace circt {
32namespace firrtl {
33#define GEN_PASS_DEF_RESOLVETRACES
34#include "circt/Dialect/FIRRTL/Passes.h.inc"
35} // namespace firrtl
36} // namespace circt
37
38using namespace circt;
39using 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).
45LogicalResult 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
81private:
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.
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) {
156 portTarget.getPortNo(), portTarget.getModule(), 0));
157 })
158 .Case<OpAnnoTarget>([&](OpAnnoTarget opTarget) {
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
427std::unique_ptr<mlir::Pass>
428circt::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 ...
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...
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...
std::unique_ptr< mlir::Pass > createResolveTracesPass(mlir::StringRef outputAnnotationFilename="")
constexpr const char * dontTouchAnnoClass
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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.