CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerAnnotations.cpp
Go to the documentation of this file.
1//===- LowerAnnotations.cpp - Lower Annotations -----------------*- 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//
9// This file defines the LowerAnnotations pass. This pass processes FIRRTL
10// annotations, rewriting them, scattering them, and dealing with non-local
11// annotations.
12//
13//===----------------------------------------------------------------------===//
14
27#include "circt/Support/Debug.h"
28#include "mlir/IR/Diagnostics.h"
29#include "mlir/Pass/Pass.h"
30#include "llvm/ADT/PostOrderIterator.h"
31#include "llvm/ADT/StringExtras.h"
32#include "llvm/Support/Debug.h"
33
34#define DEBUG_TYPE "firrtl-lower-annotations"
35
36namespace circt {
37namespace firrtl {
38#define GEN_PASS_DEF_LOWERFIRRTLANNOTATIONS
39#include "circt/Dialect/FIRRTL/Passes.h.inc"
40} // namespace firrtl
41} // namespace circt
42
43using namespace circt;
44using namespace firrtl;
45using namespace chirrtl;
46
47/// Get annotations or an empty set of annotations.
48static ArrayAttr getAnnotationsFrom(Operation *op) {
49 if (auto annots = op->getAttrOfType<ArrayAttr>(getAnnotationAttrName()))
50 return annots;
51 return ArrayAttr::get(op->getContext(), {});
52}
53
54/// Construct the annotation array with a new thing appended.
55static ArrayAttr appendArrayAttr(ArrayAttr array, Attribute a) {
56 if (!array)
57 return ArrayAttr::get(a.getContext(), ArrayRef<Attribute>{a});
58 SmallVector<Attribute> old(array.begin(), array.end());
59 old.push_back(a);
60 return ArrayAttr::get(a.getContext(), old);
61}
62
63/// Update an ArrayAttribute by replacing one entry.
64static ArrayAttr replaceArrayAttrElement(ArrayAttr array, size_t elem,
65 Attribute newVal) {
66 SmallVector<Attribute> old(array.begin(), array.end());
67 old[elem] = newVal;
68 return ArrayAttr::get(array.getContext(), old);
69}
70
71/// Apply a new annotation to a resolved target. This handles ports,
72/// aggregates, modules, wires, etc.
73static void addAnnotation(AnnoTarget ref, unsigned fieldIdx,
74 ArrayRef<NamedAttribute> anno) {
75 auto *context = ref.getOp()->getContext();
76 DictionaryAttr annotation;
77 if (fieldIdx) {
78 SmallVector<NamedAttribute> annoField(anno.begin(), anno.end());
79 annoField.emplace_back(
80 StringAttr::get(context, "circt.fieldID"),
81 IntegerAttr::get(IntegerType::get(context, 32, IntegerType::Signless),
82 fieldIdx));
83 annotation = DictionaryAttr::get(context, annoField);
84 } else {
85 annotation = DictionaryAttr::get(context, anno);
86 }
87
88 if (isa<OpAnnoTarget>(ref)) {
89 auto newAnno = appendArrayAttr(getAnnotationsFrom(ref.getOp()), annotation);
90 ref.getOp()->setAttr(getAnnotationAttrName(), newAnno);
91 return;
92 }
93
94 auto portRef = cast<PortAnnoTarget>(ref);
95 auto portAnnoRaw = ref.getOp()->getAttr(getPortAnnotationAttrName());
96 ArrayAttr portAnno = dyn_cast_or_null<ArrayAttr>(portAnnoRaw);
97 if (!portAnno || portAnno.size() != getNumPorts(ref.getOp())) {
98 SmallVector<Attribute> emptyPortAttr(
99 getNumPorts(ref.getOp()),
100 ArrayAttr::get(ref.getOp()->getContext(), {}));
101 portAnno = ArrayAttr::get(ref.getOp()->getContext(), emptyPortAttr);
102 }
103 portAnno = replaceArrayAttrElement(
104 portAnno, portRef.getPortNo(),
105 appendArrayAttr(dyn_cast<ArrayAttr>(portAnno[portRef.getPortNo()]),
106 annotation));
107 ref.getOp()->setAttr("portAnnotations", portAnno);
108}
109
110/// Make an anchor for a non-local annotation. Use the expanded path to build
111/// the module and name list in the anchor.
112static FlatSymbolRefAttr buildNLA(const AnnoPathValue &target,
113 ApplyState &state) {
114 OpBuilder b(state.circuit.getBodyRegion());
115 SmallVector<Attribute> insts;
116 for (auto inst : target.instances) {
117 insts.push_back(OpAnnoTarget(inst).getNLAReference(
118 state.getNamespace(inst->getParentOfType<FModuleLike>())));
119 }
120
121 insts.push_back(
122 FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr()));
123
124 auto instAttr = ArrayAttr::get(state.circuit.getContext(), insts);
125 return state.hierPathCache.getRefFor(instAttr);
126}
127
128/// Scatter breadcrumb annotations corresponding to non-local annotations
129/// along the instance path. Returns symbol name used to anchor annotations to
130/// path.
131// FIXME: uniq annotation chain links
132static FlatSymbolRefAttr scatterNonLocalPath(const AnnoPathValue &target,
133 ApplyState &state) {
134
135 FlatSymbolRefAttr sym = buildNLA(target, state);
136 return sym;
137}
138
139//===----------------------------------------------------------------------===//
140// Standard Utility Resolvers
141//===----------------------------------------------------------------------===//
142
143/// Always resolve to the circuit, ignoring the annotation.
144static std::optional<AnnoPathValue> noResolve(DictionaryAttr anno,
145 ApplyState &state) {
146 return AnnoPathValue(state.circuit);
147}
148
149/// Implementation of standard resolution. First parses the target path, then
150/// resolves it.
151static std::optional<AnnoPathValue> stdResolveImpl(StringRef rawPath,
152 ApplyState &state) {
153 auto pathStr = canonicalizeTarget(rawPath);
154 StringRef path{pathStr};
155
156 auto tokens = tokenizePath(path);
157 if (!tokens) {
158 mlir::emitError(state.circuit.getLoc())
159 << "Cannot tokenize annotation path " << rawPath;
160 return {};
161 }
162
163 return resolveEntities(*tokens, state.circuit, state.symTbl,
164 state.targetCaches);
165}
166
167/// (SFC) FIRRTL SingleTargetAnnotation resolver. Uses the 'target' field of
168/// the annotation with standard parsing to resolve the path. This requires
169/// 'target' to exist and be normalized (per docs/FIRRTLAnnotations.md).
170std::optional<AnnoPathValue> circt::firrtl::stdResolve(DictionaryAttr anno,
171 ApplyState &state) {
172 auto target = anno.getNamed("target");
173 if (!target) {
174 mlir::emitError(state.circuit.getLoc())
175 << "No target field in annotation " << anno;
176 return {};
177 }
178 if (!isa<StringAttr>(target->getValue())) {
179 mlir::emitError(state.circuit.getLoc())
180 << "Target field in annotation doesn't contain string " << anno;
181 return {};
182 }
183 return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(), state);
184}
185
186/// Resolves with target, if it exists. If not, resolves to the circuit.
187std::optional<AnnoPathValue> circt::firrtl::tryResolve(DictionaryAttr anno,
188 ApplyState &state) {
189 auto target = anno.getNamed("target");
190 if (target)
191 return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(),
192 state);
193 return AnnoPathValue(state.circuit);
194}
195
196//===----------------------------------------------------------------------===//
197// Standard Utility Appliers
198//===----------------------------------------------------------------------===//
199
200/// An applier which puts the annotation on the target and drops the 'target'
201/// field from the annotation. Optionally handles non-local annotations.
203
204 DictionaryAttr anno,
205 ApplyState &state,
206 bool allowNonLocal) {
207 if (!allowNonLocal && !target.isLocal()) {
208 Annotation annotation(anno);
209 auto diag = mlir::emitError(target.ref.getOp()->getLoc())
210 << "is targeted by a non-local annotation \""
211 << annotation.getClass() << "\" with target "
212 << annotation.getMember("target")
213 << ", but this annotation cannot be non-local";
214 diag.attachNote() << "see current annotation: " << anno << "\n";
215 return failure();
216 }
217 SmallVector<NamedAttribute> newAnnoAttrs;
218 for (auto &na : anno) {
219 if (na.getName().getValue() != "target") {
220 newAnnoAttrs.push_back(na);
221 } else if (!target.isLocal()) {
222 auto sym = scatterNonLocalPath(target, state);
223 newAnnoAttrs.push_back(
224 {StringAttr::get(anno.getContext(), "circt.nonlocal"), sym});
225 }
226 }
227 addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
228 return success();
229}
230
231/// Just drop the annotation. This is intended for Annotations which are known,
232/// but can be safely ignored.
233LogicalResult drop(const AnnoPathValue &target, DictionaryAttr anno,
234 ApplyState &state) {
235 return success();
236}
237//===----------------------------------------------------------------------===//
238// Customized Appliers
239//===----------------------------------------------------------------------===//
240
241static LogicalResult applyDUTAnno(const AnnoPathValue &target,
242 DictionaryAttr anno, ApplyState &state) {
243 auto *op = target.ref.getOp();
244 auto loc = op->getLoc();
245
246 if (!target.isLocal())
247 return mlir::emitError(loc) << "must be local";
248
249 if (!isa<OpAnnoTarget>(target.ref) || !isa<FModuleLike>(op))
250 return mlir::emitError(loc) << "can only target to a module";
251
252 auto moduleOp = cast<FModuleLike>(op);
253
254 // DUT has public visibility.
255 moduleOp.setPublic();
256 SmallVector<NamedAttribute> newAnnoAttrs;
257 for (auto &na : anno)
258 if (na.getName().getValue() != "target")
259 newAnnoAttrs.push_back(na);
260 addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
261 return success();
262}
263
264// Like symbolizeConvention, but disallows the internal convention.
265static std::optional<Convention> parseConvention(llvm::StringRef str) {
266 return ::llvm::StringSwitch<::std::optional<Convention>>(str)
267 .Case("scalarized", Convention::Scalarized)
268 .Default(std::nullopt);
269}
270
271static LogicalResult applyConventionAnno(const AnnoPathValue &target,
272 DictionaryAttr anno,
273 ApplyState &state) {
274 auto *op = target.ref.getOp();
275 auto loc = op->getLoc();
276 auto error = [&]() {
277 auto diag = mlir::emitError(loc);
278 diag << "circuit.ConventionAnnotation ";
279 return diag;
280 };
281
282 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
283 if (!opTarget)
284 return error() << "must target a module object";
285
286 if (!target.isLocal())
287 return error() << "must be local";
288
289 auto conventionStrAttr =
290 tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
291 if (!conventionStrAttr)
292 return failure();
293
294 auto conventionStr = conventionStrAttr.getValue();
295 auto conventionOpt = parseConvention(conventionStr);
296 if (!conventionOpt)
297 return error() << "unknown convention " << conventionStr;
298
299 auto convention = *conventionOpt;
300
301 if (auto moduleOp = dyn_cast<FModuleOp>(op)) {
302 moduleOp.setConvention(convention);
303 return success();
304 }
305
306 if (auto extModuleOp = dyn_cast<FExtModuleOp>(op)) {
307 extModuleOp.setConvention(convention);
308 return success();
309 }
310
311 return error() << "can only target to a module or extmodule";
312}
313
314static LogicalResult applyBodyTypeLoweringAnno(const AnnoPathValue &target,
315 DictionaryAttr anno,
316 ApplyState &state) {
317 auto *op = target.ref.getOp();
318 auto loc = op->getLoc();
319 auto error = [&]() {
320 auto diag = mlir::emitError(loc);
321 diag << typeLoweringAnnoClass;
322 return diag;
323 };
324
325 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
326 if (!opTarget)
327 return error() << "must target a module object";
328
329 if (!target.isLocal())
330 return error() << "must be local";
331
332 auto moduleOp = dyn_cast<FModuleOp>(op);
333
334 if (!moduleOp)
335 return error() << "can only target to a module";
336
337 auto conventionStrAttr =
338 tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
339
340 if (!conventionStrAttr)
341 return failure();
342
343 auto conventionStr = conventionStrAttr.getValue();
344 auto conventionOpt = parseConvention(conventionStr);
345 if (!conventionOpt)
346 return error() << "unknown convention " << conventionStr;
347
348 auto convention = *conventionOpt;
349
350 if (convention == Convention::Internal)
351 // Convention is internal by default so there is nothing to change
352 return success();
353
354 auto conventionAttr = ConventionAttr::get(op->getContext(), convention);
355
356 // `includeHierarchy` only valid in BodyTypeLowering.
357 bool includeHierarchy = false;
358 if (auto includeHierarchyAttr = tryGetAs<BoolAttr>(
359 anno, anno, "includeHierarchy", loc, conventionAnnoClass))
360 includeHierarchy = includeHierarchyAttr.getValue();
361
362 if (includeHierarchy) {
363 // If includeHierarchy is true, update the convention for all modules in
364 // the hierarchy.
365 for (auto *node :
366 llvm::post_order(state.instancePathCache.instanceGraph[moduleOp])) {
367 if (!node)
368 continue;
369 if (auto fmodule = dyn_cast<FModuleOp>(*node->getModule()))
370 fmodule->setAttr("body_type_lowering", conventionAttr);
371 }
372 } else {
373 // Update the convention.
374 moduleOp->setAttr("body_type_lowering", conventionAttr);
375 }
376
377 return success();
378}
379
380static LogicalResult applyModulePrefixAnno(const AnnoPathValue &target,
381 DictionaryAttr anno,
382 ApplyState &state) {
383 auto *op = target.ref.getOp();
384 auto loc = op->getLoc();
385 auto error = [&]() {
386 auto diag = mlir::emitError(loc);
387 diag << modulePrefixAnnoClass << " ";
388 return diag;
389 };
390
391 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
392 if (!opTarget)
393 return error() << "must target an operation";
394
395 if (!isa<SeqMemOp, CombMemOp, MemOp>(opTarget.getOp()))
396 return error() << "must target a memory operation";
397
398 if (!target.isLocal())
399 return error() << "must be local";
400
401 auto prefixStrAttr =
402 tryGetAs<StringAttr>(anno, anno, "prefix", loc, modulePrefixAnnoClass);
403 if (!prefixStrAttr)
404 return failure();
405
406 if (auto mem = dyn_cast<SeqMemOp>(op))
407 mem.setPrefixAttr(prefixStrAttr);
408 else if (auto mem = dyn_cast<CombMemOp>(op))
409 mem.setPrefixAttr(prefixStrAttr);
410 else if (auto mem = dyn_cast<MemOp>(op))
411 mem.setPrefixAttr(prefixStrAttr);
412
413 return success();
414}
415
416static LogicalResult applyAttributeAnnotation(const AnnoPathValue &target,
417 DictionaryAttr anno,
418 ApplyState &state) {
419 auto *op = target.ref.getOp();
420
421 auto error = [&]() {
422 auto diag = mlir::emitError(op->getLoc());
423 diag << anno.getAs<StringAttr>("class").getValue() << " ";
424 return diag;
425 };
426
427 if (!isa<OpAnnoTarget>(target.ref))
428 return error()
429 << "must target an operation. Currently ports are not supported";
430
431 if (!target.isLocal())
432 return error() << "must be local";
433
434 if (!isa<FModuleOp, WireOp, NodeOp, RegOp, RegResetOp>(op))
435 return error()
436 << "unhandled operation. The target must be a module, wire, node or "
437 "register";
438
439 auto name = anno.getAs<StringAttr>("description");
440 auto svAttr = sv::SVAttributeAttr::get(name.getContext(), name);
441 sv::addSVAttributes(op, {svAttr});
442 return success();
443}
444
445/// Update a memory op with attributes about memory file loading.
446template <bool isInline>
447static LogicalResult applyLoadMemoryAnno(const AnnoPathValue &target,
448 DictionaryAttr anno,
449 ApplyState &state) {
450 if (!target.isLocal()) {
451 mlir::emitError(state.circuit.getLoc())
452 << "has a " << anno.get("class")
453 << " annotation which is non-local, but this annotation is not allowed "
454 "to be non-local";
455 return failure();
456 }
457
458 auto *op = target.ref.getOp();
459
460 if (!target.isOpOfType<MemOp, CombMemOp, SeqMemOp>()) {
461 mlir::emitError(op->getLoc())
462 << "can only apply a load memory annotation to a memory";
463 return failure();
464 }
465
466 // The two annotations have different case usage in "filename".
467 StringAttr filename = tryGetAs<StringAttr>(
468 anno, anno, isInline ? "filename" : "fileName", op->getLoc(),
469 anno.getAs<StringAttr>("class").getValue());
470 if (!filename)
471 return failure();
472
473 auto hexOrBinary =
474 tryGetAs<StringAttr>(anno, anno, "hexOrBinary", op->getLoc(),
475 anno.getAs<StringAttr>("class").getValue());
476 if (!hexOrBinary)
477 return failure();
478
479 auto hexOrBinaryValue = hexOrBinary.getValue();
480 if (hexOrBinaryValue != "h" && hexOrBinaryValue != "b") {
481 auto diag = mlir::emitError(op->getLoc())
482 << "has memory initialization annotation with invalid format, "
483 "'hexOrBinary' field must be either 'h' or 'b'";
484 diag.attachNote() << "the full annotation is: " << anno;
485 return failure();
486 }
487
488 op->setAttr("init", MemoryInitAttr::get(op->getContext(), filename,
489 hexOrBinaryValue == "b", isInline));
490
491 return success();
492}
493
494static LogicalResult applyOutputDirAnno(const AnnoPathValue &target,
495 DictionaryAttr anno,
496 ApplyState &state) {
497 auto *op = target.ref.getOp();
498 auto *context = op->getContext();
499 auto loc = op->getLoc();
500
501 auto error = [&]() {
502 return mlir::emitError(loc) << outputDirAnnoClass << " ";
503 };
504
505 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
506 if (!opTarget)
507 return error() << "must target a module";
508 if (!target.isLocal())
509 return error() << "must be local";
510
511 auto moduleOp = dyn_cast<FModuleOp>(op);
512 if (!moduleOp)
513 return error() << "must target a module";
514 if (!moduleOp.isPublic())
515 return error() << "must target a public module";
516 if (moduleOp->hasAttr("output_file"))
517 return error() << "target already has an output file";
518
519 auto dirname =
520 tryGetAs<StringAttr>(anno, anno, "dirname", loc, outputDirAnnoClass);
521 if (!dirname)
522 return failure();
523 if (dirname.empty())
524 return error() << "dirname must not be empty";
525
526 auto outputFile =
527 hw::OutputFileAttr::getAsDirectory(context, dirname.getValue());
528
529 moduleOp->setAttr("output_file", outputFile);
530 return success();
531}
532
533/// Convert from FullAsyncResetAnnotation to FullResetAnnotation
534static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target,
535 DictionaryAttr anno,
536 ApplyState &state) {
537 auto *op = target.ref.getOp();
538 auto *context = op->getContext();
539
540 mlir::emitWarning(op->getLoc())
541 << "'" << fullAsyncResetAnnoClass << "' is deprecated, use '"
542 << fullResetAnnoClass << "' instead";
543
544 NamedAttrList newAnno(anno.getValue());
545 newAnno.set("class", StringAttr::get(context, fullResetAnnoClass));
546 newAnno.append("resetType", StringAttr::get(context, "async"));
547
548 DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
549
550 return applyWithoutTarget<false>(target, newDictionary, state);
551}
552
553/// Convert from IgnoreFullAsyncResetAnnotation to
554/// ExcludeFromFullResetAnnotation
556 const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state) {
557 auto *op = target.ref.getOp();
558 auto *context = op->getContext();
559
560 mlir::emitWarning(op->getLoc())
561 << "'" << ignoreFullAsyncResetAnnoClass << "' is deprecated, use '"
562 << excludeFromFullResetAnnoClass << "' instead";
563
564 NamedAttrList newAnno(anno.getValue());
565 newAnno.set("class", StringAttr::get(context, excludeFromFullResetAnnoClass));
566
567 DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
568
569 return applyWithoutTarget<true, FModuleOp>(target, newDictionary, state);
570}
571
572//===----------------------------------------------------------------------===//
573// Driving table
574//===----------------------------------------------------------------------===//
575
576namespace circt::firrtl {
577/// Resolution and application of a "firrtl.annotations.NoTargetAnnotation".
578/// This should be used for any Annotation which does not apply to anything in
579/// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically,
580/// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or
581/// its passes, e.g., to set the output directory or to turn on a pass.
582/// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set
583/// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to
584/// cause the SFC to schedule a specified pass. Instead of leaving these
585/// floating or attaching them to the top-level MLIR module (which is a purer
586/// interpretation of "no target"), we choose to attach them to the Circuit even
587/// they do not "apply" to the Circuit. This gives later passes a common place,
588/// the Circuit, to search for these control Annotations.
590 applyWithoutTarget<false, CircuitOp>};
591
592static llvm::StringMap<AnnoRecord> annotationRecords{{
593
594 // Testing Annotation
595 {"circt.test", {stdResolve, applyWithoutTarget<true>}},
596 {"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
597 {"circt.testNT", {noResolve, applyWithoutTarget<>}},
598 {"circt.missing", {tryResolve, applyWithoutTarget<true>}},
599 // Grand Central Views/Interfaces Annotations
604 {companionAnnoClass, {stdResolve, applyWithoutTarget<>}},
605 {augmentedGroundTypeClass, {stdResolve, applyWithoutTarget<true>}},
606 // Grand Central Mem Tap Annotations
608 // Miscellaneous Annotations
612 {stdResolve, applyWithoutTarget<true, true, WireOp, NodeOp, RegOp,
613 RegResetOp, InstanceOp, MemOp, CombMemOp,
614 MemoryPortOp, SeqMemOp>}},
622 {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
627 {stdResolve, applyWithoutTarget<true, FModuleOp, FExtModuleOp>}},
628 {flattenAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
629 {inlineAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
631 {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
633 {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
635 {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
637 {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
639 {stdResolve, applyWithoutTarget<false, FModuleOp>}},
641 {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
645 {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
661 {extractBlackBoxAnnoClass, {stdResolve, applyWithoutTarget<false>}},
662 {fullResetAnnoClass, {stdResolve, applyWithoutTarget<false>}},
664 {stdResolve, applyWithoutTarget<true, FModuleOp>}},
671 {traceAnnoClass, {stdResolve, applyWithoutTarget<true>}},
672 {loadMemoryFromFileAnnoClass, {stdResolve, applyLoadMemoryAnno<false>}},
674 {stdResolve, applyLoadMemoryAnno<true>}},
678
679LogicalResult
680registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord,
681 const std::function<void(llvm::Twine)> &errorHandler) {
682
683 if (annotationRecords.insert({annoClass, annoRecord}).second)
684 return LogicalResult::success();
685 if (errorHandler)
686 errorHandler("annotation record '" + annoClass + "' is registered twice\n");
687 return LogicalResult::failure();
688}
689
690} // namespace circt::firrtl
691
692/// Lookup a record for a given annotation class. Optionally, returns the
693/// record for "circuit.missing" if the record doesn't exist.
694static const AnnoRecord *getAnnotationHandler(StringRef annoStr,
695 bool ignoreAnnotationUnknown) {
696 auto ii = annotationRecords.find(annoStr);
697 if (ii != annotationRecords.end())
698 return &ii->second;
699 if (ignoreAnnotationUnknown)
700 return &annotationRecords.find("circt.missing")->second;
701 return nullptr;
702}
703
704//===----------------------------------------------------------------------===//
705// Pass Infrastructure
706//===----------------------------------------------------------------------===//
707
708namespace {
709struct LowerAnnotationsPass
710 : public circt::firrtl::impl::LowerFIRRTLAnnotationsBase<
711 LowerAnnotationsPass> {
712 using Base::Base;
713
714 void runOnOperation() override;
715 LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
716 LogicalResult legacyToWiringProblems(ApplyState &state);
717 LogicalResult solveWiringProblems(ApplyState &state);
718
719 SmallVector<DictionaryAttr> worklistAttrs;
720};
721} // end anonymous namespace
722
723LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
724 ApplyState &state) {
725 LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
726
727 // Lookup the class
728 StringRef annoClassVal;
729 if (auto annoClass = anno.getNamed("class"))
730 annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
731 else if (ignoreAnnotationClassless)
732 annoClassVal = "circt.missing";
733 else
734 return mlir::emitError(state.circuit.getLoc())
735 << "Annotation without a class: " << anno;
736
737 // See if we handle the class
738 auto *record = getAnnotationHandler(annoClassVal, false);
739 if (!record) {
740 ++numUnhandled;
741 if (!ignoreAnnotationUnknown)
742 return mlir::emitError(state.circuit.getLoc())
743 << "Unhandled annotation: " << anno;
744
745 // Try again, requesting the fallback handler.
746 record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
747 assert(record);
748 }
749
750 // Try to apply the annotation
751 auto target = record->resolver(anno, state);
752 if (!target)
753 return mlir::emitError(state.circuit.getLoc())
754 << "Unable to resolve target of annotation: " << anno;
755 if (record->applier(*target, anno, state).failed())
756 return mlir::emitError(state.circuit.getLoc())
757 << "Unable to apply annotation: " << anno;
758 return success();
759}
760
761/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
762/// using the pin attribute as newNameHint
763LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
764 for (const auto &[name, problem] : state.legacyWiringProblems) {
765 if (!problem.source)
766 return mlir::emitError(state.circuit.getLoc())
767 << "Unable to resolve source for pin: " << name;
768
769 if (problem.sinks.empty())
770 return mlir::emitError(state.circuit.getLoc())
771 << "Unable to resolve sink(s) for pin: " << name;
772
773 for (const auto &sink : problem.sinks) {
774 state.wiringProblems.push_back(
775 {problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
776 }
777 }
778 return success();
779}
780
781/// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
782/// Wiring Problem is a mapping from a source to a sink that can be connected
783/// via a base Type or RefType as requested. This uses a two-step approach.
784/// First, all Wiring Problems are analyzed to compute pending modifications to
785/// modules. Second, modules are visited from leaves to roots to apply module
786/// modifications. Module modifications include addings ports and connecting
787/// things up.
788LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
789 // Utility function to extract the defining module from a value which may be
790 // either a BlockArgument or an Operation result.
791 auto getModule = [](Value value) {
792 if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
793 return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
794 return value.getDefiningOp()->getParentOfType<FModuleLike>();
795 };
796
797 // Utility function to determine where to insert connection operations.
798 auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
799 // Check for easy case: both are in the same block.
800 if (src.getParentBlock() == dest.getParentBlock())
801 return src.getParentBlock();
802
803 // If connecting across blocks, figure out where to connect.
804 (void)getModule;
805 assert(getModule(src) == getModule(dest));
806 // Helper to determine if 'a' is available at 'b's block.
807 auto safelyDoms = [&](Value a, Value b) {
808 if (isa<BlockArgument>(a))
809 return true;
810 if (isa<BlockArgument>(b))
811 return false;
812 // Handle cases where 'b' is in child op after 'a'.
813 auto *ancestor =
814 a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
815 return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
816 };
817 if (safelyDoms(src, dest))
818 return dest.getParentBlock();
819 if (safelyDoms(dest, src))
820 return src.getParentBlock();
821 return {};
822 };
823
824 auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
825 auto op =
826 dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
827 if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
828 op.getResultTypes()[0] == op.getOperandTypes()[0])
829 return op;
830 return {};
831 };
832
833 // Utility function to connect a destination to a source. Always use a
834 // ConnectOp as the widths may be uninferred.
835 SmallVector<Operation *> opsToErase;
836 auto connect = [&](Value src, Value dest,
837 ImplicitLocOpBuilder &builder) -> LogicalResult {
838 // Strip away noop unrealized_conversion_cast's, used as placeholders.
839 // In the future, these should be created/managed as part of creating WP's.
840 if (auto op = getNoopCast(dest)) {
841 dest = op.getOperand(0);
842 opsToErase.push_back(op);
843 std::swap(src, dest);
844 } else if (auto op = getNoopCast(src)) {
845 src = op.getOperand(0);
846 opsToErase.push_back(op);
847 }
848
849 if (foldFlow(dest) == Flow::Source)
850 std::swap(src, dest);
851
852 // Figure out where to insert operations.
853 auto *insertBlock = findInsertionBlock(src, dest);
854 if (!insertBlock)
855 return emitError(src.getLoc())
856 .append("This value is involved with a Wiring Problem where the "
857 "destination is in the same module but neither dominates the "
858 "other, which is not supported.")
859 .attachNote(dest.getLoc())
860 .append("The destination is here.");
861
862 // Insert at end, past invalidation in same block.
863 builder.setInsertionPointToEnd(insertBlock);
864
865 // Create RefSend/RefResolve if necessary.
866 if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
867 if (type_isa<RefType>(dest.getType()))
868 src = RefSendOp::create(builder, src);
869 else
870 src = RefResolveOp::create(builder, src);
871 }
872
873 // If the sink is a wire with no users, then convert this to a node.
874 // This is done to convert the undriven wires created for GCView's
875 // into the NodeOp's they're required to be in GrandCentral.cpp.
876 if (auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
877 destOp && dest.getUses().empty()) {
878 // Only perform this if the type is suitable (passive).
879 if (auto baseType = dyn_cast<FIRRTLBaseType>(src.getType());
880 baseType && baseType.isPassive()) {
881 // Note that the wire is replaced with the source type
882 // regardless, continue this behavior.
883 NodeOp::create(builder, src, destOp.getName())
884 .setAnnotationsAttr(destOp.getAnnotations());
885 opsToErase.push_back(destOp);
886 return success();
887 }
888 }
889
890 // Otherwise, just connect to the source.
891 emitConnect(builder, dest, src);
892
893 return success();
894 };
895
896 auto &instanceGraph = state.instancePathCache.instanceGraph;
897 auto *context = state.circuit.getContext();
898
899 // Examine all discovered Wiring Problems to determine modifications that need
900 // to be made per-module.
901 LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
902 DenseMap<FModuleLike, ModuleModifications> moduleModifications;
903 DenseSet<Value> visitedSinks;
904 for (auto e : llvm::enumerate(state.wiringProblems)) {
905 auto index = e.index();
906 auto problem = e.value();
907 // This is a unique index that is assigned to this specific wiring problem
908 // and is used as a key during wiring to know which Values (ports, sources,
909 // or sinks) should be connected.
910 auto source = problem.source;
911 auto sink = problem.sink;
912
913 // Check that no WiringProblems are trying to use the same sink. This
914 // should never happen.
915 if (!visitedSinks.insert(sink).second) {
916 auto diag = mlir::emitError(source.getLoc())
917 << "This sink is involved with a Wiring Problem which is "
918 "targeted by a source used by another Wiring Problem. "
919 "(This is both illegal and should be impossible.)";
920 diag.attachNote(source.getLoc()) << "The source is here";
921 return failure();
922 }
923 FModuleLike sourceModule = getModule(source);
924 FModuleLike sinkModule = getModule(sink);
925 if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
926 auto diag = mlir::emitError(source.getLoc())
927 << "This source is involved with a Wiring Problem which "
928 "includes an External Module port and External Module "
929 "ports anre not supported.";
930 diag.attachNote(sink.getLoc()) << "The sink is here.";
931 return failure();
932 }
933
934 LLVM_DEBUG({
935 llvm::dbgs() << " - index: " << index << "\n"
936 << " source:\n"
937 << " module: " << sourceModule.getModuleName() << "\n"
938 << " value: " << source << "\n"
939 << " sink:\n"
940 << " module: " << sinkModule.getModuleName() << "\n"
941 << " value: " << sink << "\n"
942 << " newNameHint: " << problem.newNameHint << "\n";
943 });
944
945 // If the source and sink are in the same block, just wire them up.
946 if (sink.getParentBlock() == source.getParentBlock()) {
947 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
948 sink.getParentBlock());
949 if (failed(connect(source, sink, builder)))
950 return failure();
951 continue;
952 }
953 // If both are in the same module but not same block, U-turn.
954 // We may not be able to handle this, but that is checked below while
955 // connecting.
956 if (sourceModule == sinkModule) {
957 LLVM_DEBUG(llvm::dbgs()
958 << " LCA: " << sourceModule.getModuleName() << "\n");
959 moduleModifications[sourceModule].connectionMap[index] = source;
960 moduleModifications[sourceModule].uturns.push_back({index, sink});
961 continue;
962 }
963
964 // Otherwise, get instance paths for source/sink, and compute LCA.
965 auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
966 auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
967
968 if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
969 auto diag =
970 mlir::emitError(source.getLoc())
971 << "This source is involved with a Wiring Problem where the source "
972 "or the sink are multiply instantiated and this is not supported.";
973 diag.attachNote(sink.getLoc()) << "The sink is here.";
974 return failure();
975 }
976
977 FModuleOp lca =
978 cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
979 auto sources = sourcePaths[0];
980 auto sinks = sinkPaths[0];
981 while (!sources.empty() && !sinks.empty()) {
982 if (sources.top() != sinks.top())
983 break;
984 auto newLCA = cast<InstanceOp>(*sources.top());
985 lca = cast<FModuleOp>(newLCA.getReferencedModule(instanceGraph));
986 sources = sources.dropFront();
987 sinks = sinks.dropFront();
988 }
989
990 LLVM_DEBUG({
991 llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
992 << " sourcePath: " << sourcePaths[0] << "\n"
993 << " sinkPaths: " << sinkPaths[0] << "\n";
994 });
995
996 // Pre-populate the connectionMap of the module with the source and sink.
997 moduleModifications[sourceModule].connectionMap[index] = source;
998 moduleModifications[sinkModule].connectionMap[index] = sink;
999
1000 // Record port types that should be added to each module along the LCA path.
1001 Type sourceType, sinkType;
1002 auto useRefTypes =
1003 !noRefTypePorts &&
1004 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
1005 if (useRefTypes) {
1006 // Use RefType ports if possible
1007 RefType refType = TypeSwitch<Type, RefType>(source.getType())
1008 .Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
1009 return RefType::get(base.getPassiveType());
1010 })
1011 .Case<RefType>([](RefType ref) { return ref; });
1012 sourceType = refType;
1013 sinkType = refType.getType();
1014 } else {
1015 // Use specified port types.
1016 sourceType = source.getType();
1017 sinkType = sink.getType();
1018
1019 // Types must be connectable, which means FIRRTLType's.
1020 auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
1021 auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
1022 if (!sourceFType)
1023 return emitError(source.getLoc())
1024 << "Wiring Problem source type \"" << sourceType
1025 << "\" must be a FIRRTL type";
1026 if (!sinkFType)
1027 return emitError(sink.getLoc())
1028 << "Wiring Problem sink type \"" << sinkType
1029 << "\" must be a FIRRTL type";
1030
1031 // Otherwise they must be identical or FIRRTL type-equivalent
1032 // (connectable).
1033 if (sourceFType != sinkFType &&
1034 !areTypesEquivalent(sinkFType, sourceFType)) {
1035 // Support tapping mixed alignment -> passive , emulate probe behavior.
1036 if (auto sourceBaseType = dyn_cast<FIRRTLBaseType>(sourceFType);
1037 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer &&
1038 sourceBaseType &&
1039 areTypesEquivalent(sinkFType, sourceBaseType.getPassiveType())) {
1040 // Change "sourceType" to the passive version that's type-equivalent,
1041 // this will be used for wiring on the "up" side.
1042 // This relies on `emitConnect` supporting connecting to the passive
1043 // version from the original source.
1044 sourceType = sourceBaseType.getPassiveType();
1045 } else {
1046 auto diag = mlir::emitError(source.getLoc())
1047 << "Wiring Problem source type " << sourceType
1048 << " does not match sink type " << sinkType;
1049 diag.attachNote(sink.getLoc()) << "The sink is here.";
1050 return failure();
1051 }
1052 }
1053 }
1054 // If wiring using references, check that the sink value we connect to is
1055 // passive.
1056 if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
1057 sinkFType && type_isa<RefType>(sourceType) &&
1058 !getBaseType(sinkFType).isPassive())
1059 return emitError(sink.getLoc())
1060 << "Wiring Problem sink type \"" << sink.getType()
1061 << "\" must be passive (no flips) when using references";
1062
1063 // Record module modifications related to adding ports to modules.
1064 auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
1065 Direction dir) -> LogicalResult {
1066 StringRef name, instName;
1067 for (auto instNode : llvm::reverse(insts)) {
1068 auto inst = cast<InstanceOp>(*instNode);
1069 auto mod = inst.getReferencedModule<FModuleOp>(instanceGraph);
1070 if (mod.isPublic()) {
1071 if (!allowAddingPortsOnPublic) {
1072 auto diag = emitError(
1073 mod.getLoc(), "cannot wire port through this public module");
1074 diag.attachNote(source.getLoc()) << "source here";
1075 diag.attachNote(sink.getLoc()) << "sink here";
1076 return diag;
1077 }
1078 ++numPublicPortsWired;
1079 }
1080 if (name.empty()) {
1081 if (problem.newNameHint.empty())
1082 name = state.getNamespace(mod).newName(
1084 getFieldRefFromValue(val, /*lookThroughCasts=*/true),
1085 /*nameSafe=*/true)
1086 .first +
1087 "__bore");
1088 else
1089 name = state.getNamespace(mod).newName(problem.newNameHint);
1090 } else {
1091 assert(!instName.empty());
1092 name = state.getNamespace(mod).newName(instName + "_" + name);
1093 }
1094 moduleModifications[mod].portsToAdd.push_back(
1095 {index, {StringAttr::get(context, name), tpe, dir}});
1096 instName = inst.getInstanceName();
1097 }
1098 return success();
1099 };
1100
1101 // Record the addition of ports.
1102 if (failed(addPorts(sources, source, sourceType, Direction::Out)) ||
1103 failed(addPorts(sinks, sink, sinkType, Direction::In)))
1104 return failure();
1105 }
1106
1107 // Iterate over modules from leaves to roots, applying ModuleModifications to
1108 // each module.
1109 LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
1110 for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
1111 auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
1112 // Skip external modules and modules that have no modifications.
1113 if (!fmodule || !moduleModifications.count(fmodule))
1114 continue;
1115
1116 auto modifications = moduleModifications[fmodule];
1117 LLVM_DEBUG({
1118 llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
1119 llvm::dbgs() << " ports:\n";
1120 for (auto [index, port] : modifications.portsToAdd) {
1121 llvm::dbgs() << " - name: " << port.getName() << "\n"
1122 << " id: " << index << "\n"
1123 << " type: " << port.type << "\n"
1124 << " direction: "
1125 << (port.direction == Direction::In ? "in" : "out")
1126 << "\n";
1127 }
1128 });
1129
1130 // Add ports to the module after all other existing ports.
1131 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
1132 SmallVector<unsigned> problemIndices;
1133 for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
1134 // Create the port.
1135 newPorts.push_back({fmodule.getNumPorts(), portInfo});
1136 problemIndices.push_back(problemIdx);
1137 }
1138 auto originalNumPorts = fmodule.getNumPorts();
1139 auto portIdx = fmodule.getNumPorts();
1140 fmodule.insertPorts(newPorts);
1141
1142 auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
1143 fmodule.getBodyBlock());
1144
1145 // Connect each port to the value stored in the connectionMap for this
1146 // wiring problem index.
1147 for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
1148 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1149 assert(src && "there did not exist a driver for the port");
1150 Value dest = fmodule.getArgument(portIdx++);
1151 if (failed(connect(src, dest, builder)))
1152 return failure();
1153 }
1154
1155 // If a U-turn exists, this is an LCA and we need a U-turn connection. These
1156 // are the last connections made for this module.
1157 for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
1158 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1159 assert(src && "there did not exist a connection for the u-turn");
1160 if (failed(connect(src, dest, builder)))
1161 return failure();
1162 }
1163
1164 // Update the connectionMap of all modules for which we created a port.
1165 for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
1166 InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
1167 auto enclosingModule = useInst->getParentOfType<FModuleOp>();
1168 auto clonedInst = useInst.cloneWithInsertedPortsAndReplaceUses(newPorts);
1169 state.instancePathCache.replaceInstance(useInst, clonedInst);
1170 useInst->erase();
1171 // Record information in the moduleModifications strucutre for the module
1172 // _where this is instantiated_. This is done so that when that module is
1173 // visited later, there will be information available for it to find ports
1174 // it needs to wire up. If there is already an existing connection, then
1175 // this is a U-turn.
1176 for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
1177 auto &modifications = moduleModifications[enclosingModule];
1178 auto newPort = clonedInst.getResult(newPortIdx + originalNumPorts);
1179 if (modifications.connectionMap.count(problemIdx)) {
1180 modifications.uturns.push_back({problemIdx, newPort});
1181 continue;
1182 }
1183 modifications.connectionMap[problemIdx] = newPort;
1184 }
1185 }
1186 }
1187
1188 // Delete unused WireOps created by producers of WiringProblems.
1189 for (auto *op : opsToErase)
1190 op->erase();
1191
1192 return success();
1193}
1194
1195// This is the main entrypoint for the lowering pass.
1196void LowerAnnotationsPass::runOnOperation() {
1198
1199 CircuitOp circuit = getOperation();
1200 SymbolTable modules(circuit);
1201
1202 // Grab the annotations from a non-standard attribute called "rawAnnotations".
1203 // This is a temporary location for all annotations that are earmarked for
1204 // processing by this pass as we migrate annotations from being handled by
1205 // FIRAnnotations/FIRParser into this pass. While we do this, this pass is
1206 // not supposed to touch _other_ annotations to enable this pass to be run
1207 // after FIRAnnotations/FIRParser.
1208 auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
1209 if (!annotations)
1210 return;
1211 circuit->removeAttr(rawAnnotations);
1212
1213 // Populate the worklist in reverse order. This has the effect of causing
1214 // annotations to be processed in the order in which they appear in the
1215 // original JSON.
1216 for (auto anno : llvm::reverse(annotations.getValue()))
1217 worklistAttrs.push_back(cast<DictionaryAttr>(anno));
1218
1219 size_t numFailures = 0;
1220 size_t numAdded = 0;
1221 auto addToWorklist = [&](DictionaryAttr anno) {
1222 ++numAdded;
1223 worklistAttrs.push_back(anno);
1224 };
1225 InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
1226 ApplyState state{circuit, modules, addToWorklist, instancePathCache,
1227 noRefTypePorts};
1228 LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
1229 while (!worklistAttrs.empty()) {
1230 auto attr = worklistAttrs.pop_back_val();
1231 if (applyAnnotation(attr, state).failed())
1232 ++numFailures;
1233 }
1234
1235 if (failed(legacyToWiringProblems(state)))
1236 ++numFailures;
1237
1238 if (failed(solveWiringProblems(state)))
1239 ++numFailures;
1240
1241 // Update statistics
1242 numRawAnnotations += annotations.size();
1243 numAddedAnnos += numAdded;
1244 numAnnos += numAdded + annotations.size();
1245 numReusedHierPathOps += state.numReusedHierPaths;
1246
1247 if (numFailures)
1248 signalPassFailure();
1249}
assert(baseType &&"element must be base type")
static LogicalResult applyOutputDirAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
static LogicalResult convertToExcludeFromFullResetAnnotation(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Convert from IgnoreFullAsyncResetAnnotation to ExcludeFromFullResetAnnotation.
static LogicalResult applyModulePrefixAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
static void addAnnotation(AnnoTarget ref, unsigned fieldIdx, ArrayRef< NamedAttribute > anno)
Apply a new annotation to a resolved target.
static ArrayAttr replaceArrayAttrElement(ArrayAttr array, size_t elem, Attribute newVal)
Update an ArrayAttribute by replacing one entry.
static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Convert from FullAsyncResetAnnotation to FullResetAnnotation.
static ArrayAttr appendArrayAttr(ArrayAttr array, Attribute a)
Construct the annotation array with a new thing appended.
static LogicalResult applyBodyTypeLoweringAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
static std::optional< AnnoPathValue > stdResolveImpl(StringRef rawPath, ApplyState &state)
Implementation of standard resolution.
static LogicalResult applyDUTAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
LogicalResult drop(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Just drop the annotation.
static LogicalResult applyLoadMemoryAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Update a memory op with attributes about memory file loading.
static ArrayAttr getAnnotationsFrom(Operation *op)
Get annotations or an empty set of annotations.
static LogicalResult applyConventionAnno(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
static LogicalResult applyAttributeAnnotation(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
static FlatSymbolRefAttr scatterNonLocalPath(const AnnoPathValue &target, ApplyState &state)
Scatter breadcrumb annotations corresponding to non-local annotations along the instance path.
static const AnnoRecord * getAnnotationHandler(StringRef annoStr, bool ignoreAnnotationUnknown)
Lookup a record for a given annotation class.
static std::optional< Convention > parseConvention(llvm::StringRef str)
static std::optional< AnnoPathValue > noResolve(DictionaryAttr anno, ApplyState &state)
Always resolve to the circuit, ignoring the annotation.
static FlatSymbolRefAttr buildNLA(const AnnoPathValue &target, ApplyState &state)
Make an anchor for a non-local annotation.
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
StringRef getClass() const
Return the 'class' that this annotation is representing.
FIRRTLBaseType getPassiveType()
Return this type with any flip types recursively removed from itself.
bool isPassive() const
Return true if this is a "passive" type - one that contains no "flip" types recursively within itself...
An instance path composed of a series of instances.
connect(destination, source)
Definition support.py:39
constexpr const char * excludeFromFullResetAnnoClass
Annotation that marks a module as not belonging to any reset domain.
constexpr const char * injectDUTHierarchyAnnoClass
constexpr const char * extractCoverageAnnoClass
constexpr const char * elaborationArtefactsDirectoryAnnoClass
StringRef getAnnotationAttrName()
Return the name of the attribute used for annotations on FIRRTL ops.
Direction
This represents the direction of a single port.
Definition FIRRTLEnums.h:27
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
constexpr const char * extractGrandCentralClass
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
std::optional< AnnoPathValue > stdResolve(DictionaryAttr anno, ApplyState &state)
===-------------------------------------------------------------------—===// Standard Utility Resolve...
constexpr const char * fullResetAnnoClass
Annotation that marks a reset (port or wire) and domain.
constexpr const char * sitestBlackBoxAnnoClass
constexpr const char * convertMemToRegOfVecAnnoClass
constexpr const char * dontObfuscateModuleAnnoClass
constexpr const char * metadataDirectoryAttrName
constexpr const char * extractBlackBoxAnnoClass
constexpr const char * fullAsyncResetAnnoClass
Annotation that marks a reset (port or wire) and domain.
constexpr const char * testBenchDirAnnoClass
constexpr const char * sitestBlackBoxLibrariesAnnoClass
constexpr const char * sitestTestHarnessBlackBoxAnnoClass
constexpr const char * outputDirAnnoClass
constexpr const char * augmentedGroundTypeClass
constexpr const char * traceAnnoClass
constexpr const char * mustDedupAnnoClass
Flow foldFlow(Value val, Flow accumulatedFlow=Flow::Source)
Compute the flow for a Value, val, as determined by the FIRRTL specification.
constexpr const char * loadMemoryFromFileAnnoClass
constexpr const char * dutAnnoClass
constexpr const char * rawAnnotations
constexpr const char * extractSeqMemsAnnoClass
constexpr const char * attributeAnnoClass
bool areTypesEquivalent(FIRRTLType destType, FIRRTLType srcType, bool destOuterTypeIsConst=false, bool srcOuterTypeIsConst=false, bool requireSameWidths=false)
Returns whether the two types are equivalent.
std::optional< AnnoPathValue > resolveEntities(TokenAnnoTarget path, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Convert a parsed target string to a resolved target structure.
constexpr const char * wiringSinkAnnoClass
constexpr const char * loadMemoryFromFileInlineAnnoClass
constexpr const char * forceNameAnnoClass
constexpr const char * memTapClass
constexpr const char * noDedupAnnoClass
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc)
constexpr const char * conventionAnnoClass
constexpr const char * dedupGroupAnnoClass
constexpr const char * viewAnnoClass
constexpr const char * serializedViewAnnoClass
constexpr const char * enumDefAnnoClass
constexpr const char * enumVecAnnoClass
std::string canonicalizeTarget(StringRef target)
Return an input target string in canonical form.
constexpr const char * wiringSourceAnnoClass
LogicalResult applyGCTMemTaps(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
constexpr const char * traceNameAnnoClass
constexpr const char * enumComponentAnnoClass
constexpr const char * extractAssertAnnoClass
static LogicalResult applyWithoutTarget(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
An applier which puts the annotation on the target and drops the 'target' field from the annotation.
constexpr const char * testHarnessPathAnnoClass
constexpr const char * verifBlackBoxAnnoClass
constexpr const char * addSeqMemPortAnnoClass
LogicalResult applyWiring(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Consume SourceAnnotation and SinkAnnotation, storing into state.
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 * blackBoxPathAnnoClass
constexpr const char * addSeqMemPortsFileAnnoClass
constexpr const char * retimeModulesFileAnnoClass
constexpr const char * retimeModuleAnnoClass
constexpr const char * runFIRRTLTransformAnnoClass
constexpr const char * companionAnnoClass
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
constexpr const char * blackBoxTargetDirAnnoClass
constexpr const char * ignoreFullAsyncResetAnnoClass
Annotation that marks a module as not belonging to any reset domain.
LogicalResult registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord, const std::function< void(llvm::Twine)> &errorHandler={})
Register external annotation records.
constexpr const char * modulePrefixAnnoClass
constexpr const char * blackBoxInlineAnnoClass
StringRef getPortAnnotationAttrName()
Return the name of the attribute used for port annotations on FIRRTL ops.
constexpr const char * decodeTableAnnotation
constexpr const char * extractClockGatesAnnoClass
constexpr const char * inlineAnnoClass
LogicalResult applyGCTView(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
constexpr const char * flattenAnnoClass
std::optional< TokenAnnoTarget > tokenizePath(StringRef origTarget)
Parse a FIRRTL annotation path into its constituent parts.
LogicalResult applyWithoutTargetImpl(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state, bool allowNonLocal)
===-------------------------------------------------------------------—===// Standard Utility Applier...
constexpr const char * extractAssumeAnnoClass
constexpr const char * grandCentralHierarchyFileAnnoClass
std::optional< AnnoPathValue > tryResolve(DictionaryAttr anno, ApplyState &state)
Resolves with target, if it exists. If not, resolves to the circuit.
constexpr const char * typeLoweringAnnoClass
constexpr const char * dontTouchAnnoClass
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
constexpr const char * testHarnessHierAnnoClass
static llvm::StringMap< AnnoRecord > annotationRecords
constexpr const char * moduleHierAnnoClass
static AnnoRecord NoTargetAnnotation
Resolution and application of a "firrtl.annotations.NoTargetAnnotation".
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
SmallVector< InstanceOp > instances
===-------------------------------------------------------------------—===// LowerAnnotations ===----...
An annotation target is used to keep track of something that is targeted by an Annotation.
FModuleLike getModule() const
Get the parent module of the target.
State threaded through functions for resolving and applying annotations.
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
hw::InnerSymbolNamespace & getNamespace(FModuleLike module)
FlatSymbolRefAttr getRefFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const
A data structure that caches and provides paths to module instances in the IR.
ArrayRef< InstancePath > getAbsolutePaths(ModuleOpInterface op)
void replaceInstance(InstanceOpInterface oldOp, InstanceOpInterface newOp)
Replace an InstanceOp. This is required to keep the cache updated.
InstanceGraph & instanceGraph
The instance graph of the IR.