CIRCT 23.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 << bodyTypeLoweringAnnoClass;
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 Annotations (manually maintained for testing infrastructure)
595 {"circt.test", {stdResolve, applyWithoutTarget<true>}},
596 {"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
597 {"circt.testNT", {noResolve, applyWithoutTarget<>}},
598 {"circt.missing", {tryResolve, applyWithoutTarget<true>}},
599
600// Auto-generated annotation records from FIRRTLAnnotations.td
601#define GET_ANNOTATION_RECORD_LIST
602#include "circt/Dialect/FIRRTL/FIRRTLAnnotationRecords.h.inc"
603}};
604
605LogicalResult
606registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord,
607 const std::function<void(llvm::Twine)> &errorHandler) {
608
609 if (annotationRecords.insert({annoClass, annoRecord}).second)
610 return LogicalResult::success();
611 if (errorHandler)
612 errorHandler("annotation record '" + annoClass + "' is registered twice\n");
613 return LogicalResult::failure();
614}
615
616} // namespace circt::firrtl
617
618/// Lookup a record for a given annotation class. Optionally, returns the
619/// record for "circuit.missing" if the record doesn't exist.
620static const AnnoRecord *getAnnotationHandler(StringRef annoStr,
621 bool ignoreAnnotationUnknown) {
622 auto ii = annotationRecords.find(annoStr);
623 if (ii != annotationRecords.end())
624 return &ii->second;
625 if (ignoreAnnotationUnknown)
626 return &annotationRecords.find("circt.missing")->second;
627 return nullptr;
628}
629
630//===----------------------------------------------------------------------===//
631// Pass Infrastructure
632//===----------------------------------------------------------------------===//
633
634namespace {
635struct LowerAnnotationsPass
636 : public circt::firrtl::impl::LowerFIRRTLAnnotationsBase<
637 LowerAnnotationsPass> {
638 using Base::Base;
639
640 void runOnOperation() override;
641 LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
642 LogicalResult legacyToWiringProblems(ApplyState &state);
643 LogicalResult solveWiringProblems(ApplyState &state);
644
645 SmallVector<DictionaryAttr> worklistAttrs;
646};
647} // end anonymous namespace
648
649LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
650 ApplyState &state) {
651 LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
652
653 // Lookup the class
654 StringRef annoClassVal;
655 if (auto annoClass = anno.getNamed("class"))
656 annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
657 else if (ignoreAnnotationClassless)
658 annoClassVal = "circt.missing";
659 else
660 return mlir::emitError(state.circuit.getLoc())
661 << "Annotation without a class: " << anno;
662
663 // See if we handle the class
664 auto *record = getAnnotationHandler(annoClassVal, false);
665 if (!record) {
666 ++numUnhandled;
667 if (!ignoreAnnotationUnknown)
668 return mlir::emitError(state.circuit.getLoc())
669 << "Unhandled annotation: " << anno;
670
671 // Try again, requesting the fallback handler.
672 record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
673 assert(record);
674 }
675
676 // Try to apply the annotation
677 auto target = record->resolver(anno, state);
678 if (!target)
679 return mlir::emitError(state.circuit.getLoc())
680 << "Unable to resolve target of annotation: " << anno;
681 if (record->applier(*target, anno, state).failed())
682 return mlir::emitError(state.circuit.getLoc())
683 << "Unable to apply annotation: " << anno;
684 return success();
685}
686
687/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
688/// using the pin attribute as newNameHint
689LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
690 for (const auto &[name, problem] : state.legacyWiringProblems) {
691 if (!problem.source)
692 return mlir::emitError(state.circuit.getLoc())
693 << "Unable to resolve source for pin: " << name;
694
695 if (problem.sinks.empty())
696 return mlir::emitError(state.circuit.getLoc())
697 << "Unable to resolve sink(s) for pin: " << name;
698
699 for (const auto &sink : problem.sinks) {
700 state.wiringProblems.push_back(
701 {problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
702 }
703 }
704 return success();
705}
706
707/// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
708/// Wiring Problem is a mapping from a source to a sink that can be connected
709/// via a base Type or RefType as requested. This uses a two-step approach.
710/// First, all Wiring Problems are analyzed to compute pending modifications to
711/// modules. Second, modules are visited from leaves to roots to apply module
712/// modifications. Module modifications include addings ports and connecting
713/// things up.
714LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
715 // Utility function to extract the defining module from a value which may be
716 // either a BlockArgument or an Operation result.
717 auto getModule = [](Value value) {
718 if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
719 return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
720 return value.getDefiningOp()->getParentOfType<FModuleLike>();
721 };
722
723 // Utility function to determine where to insert connection operations.
724 auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
725 // Check for easy case: both are in the same block.
726 if (src.getParentBlock() == dest.getParentBlock())
727 return src.getParentBlock();
728
729 // If connecting across blocks, figure out where to connect.
730 (void)getModule;
731 assert(getModule(src) == getModule(dest));
732 // Helper to determine if 'a' is available at 'b's block.
733 auto safelyDoms = [&](Value a, Value b) {
734 if (isa<BlockArgument>(a))
735 return true;
736 if (isa<BlockArgument>(b))
737 return false;
738 // Handle cases where 'b' is in child op after 'a'.
739 auto *ancestor =
740 a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
741 return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
742 };
743 if (safelyDoms(src, dest))
744 return dest.getParentBlock();
745 if (safelyDoms(dest, src))
746 return src.getParentBlock();
747 return {};
748 };
749
750 auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
751 auto op =
752 dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
753 if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
754 op.getResultTypes()[0] == op.getOperandTypes()[0])
755 return op;
756 return {};
757 };
758
759 // Utility function to connect a destination to a source. Always use a
760 // ConnectOp as the widths may be uninferred.
761 SmallVector<Operation *> opsToErase;
762 auto connect = [&](Value src, Value dest,
763 ImplicitLocOpBuilder &builder) -> LogicalResult {
764 // Strip away noop unrealized_conversion_cast's, used as placeholders.
765 // In the future, these should be created/managed as part of creating WP's.
766 if (auto op = getNoopCast(dest)) {
767 dest = op.getOperand(0);
768 opsToErase.push_back(op);
769 std::swap(src, dest);
770 } else if (auto op = getNoopCast(src)) {
771 src = op.getOperand(0);
772 opsToErase.push_back(op);
773 }
774
775 if (foldFlow(dest) == Flow::Source)
776 std::swap(src, dest);
777
778 // Figure out where to insert operations.
779 auto *insertBlock = findInsertionBlock(src, dest);
780 if (!insertBlock)
781 return emitError(src.getLoc())
782 .append("This value is involved with a Wiring Problem where the "
783 "destination is in the same module but neither dominates the "
784 "other, which is not supported.")
785 .attachNote(dest.getLoc())
786 .append("The destination is here.");
787
788 // Insert at end, past invalidation in same block.
789 builder.setInsertionPointToEnd(insertBlock);
790
791 // Create RefSend/RefResolve if necessary.
792 if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
793 if (type_isa<RefType>(dest.getType()))
794 src = RefSendOp::create(builder, src);
795 else
796 src = RefResolveOp::create(builder, src);
797 }
798
799 // If the sink is a wire with no users, then convert this to a node.
800 // This is done to convert the undriven wires created for GCView's
801 // into the NodeOp's they're required to be in GrandCentral.cpp.
802 if (auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
803 destOp && dest.getUses().empty()) {
804 // Only perform this if the type is suitable (passive).
805 if (auto baseType = dyn_cast<FIRRTLBaseType>(src.getType());
806 baseType && baseType.isPassive()) {
807 // Note that the wire is replaced with the source type
808 // regardless, continue this behavior.
809 NodeOp::create(builder, src, destOp.getName())
810 .setAnnotationsAttr(destOp.getAnnotations());
811 opsToErase.push_back(destOp);
812 return success();
813 }
814 }
815
816 // Otherwise, just connect to the source.
817 emitConnect(builder, dest, src);
818
819 return success();
820 };
821
822 auto &instanceGraph = state.instancePathCache.instanceGraph;
823 auto *context = state.circuit.getContext();
824
825 // Examine all discovered Wiring Problems to determine modifications that need
826 // to be made per-module.
827 LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
828 DenseMap<FModuleLike, ModuleModifications> moduleModifications;
829 DenseSet<Value> visitedSinks;
830 for (auto e : llvm::enumerate(state.wiringProblems)) {
831 auto index = e.index();
832 auto problem = e.value();
833 // This is a unique index that is assigned to this specific wiring problem
834 // and is used as a key during wiring to know which Values (ports, sources,
835 // or sinks) should be connected.
836 auto source = problem.source;
837 auto sink = problem.sink;
838
839 // Check that no WiringProblems are trying to use the same sink. This
840 // should never happen.
841 if (!visitedSinks.insert(sink).second) {
842 auto diag = mlir::emitError(source.getLoc())
843 << "This sink is involved with a Wiring Problem which is "
844 "targeted by a source used by another Wiring Problem. "
845 "(This is both illegal and should be impossible.)";
846 diag.attachNote(source.getLoc()) << "The source is here";
847 return failure();
848 }
849 FModuleLike sourceModule = getModule(source);
850 FModuleLike sinkModule = getModule(sink);
851 if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
852 auto diag = mlir::emitError(source.getLoc())
853 << "This source is involved with a Wiring Problem which "
854 "includes an External Module port and External Module "
855 "ports anre not supported.";
856 diag.attachNote(sink.getLoc()) << "The sink is here.";
857 return failure();
858 }
859
860 LLVM_DEBUG({
861 llvm::dbgs() << " - index: " << index << "\n"
862 << " source:\n"
863 << " module: " << sourceModule.getModuleName() << "\n"
864 << " value: " << source << "\n"
865 << " sink:\n"
866 << " module: " << sinkModule.getModuleName() << "\n"
867 << " value: " << sink << "\n"
868 << " newNameHint: " << problem.newNameHint << "\n";
869 });
870
871 // If the source and sink are in the same block, just wire them up.
872 if (sink.getParentBlock() == source.getParentBlock()) {
873 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
874 sink.getParentBlock());
875 if (failed(connect(source, sink, builder)))
876 return failure();
877 continue;
878 }
879 // If both are in the same module but not same block, U-turn.
880 // We may not be able to handle this, but that is checked below while
881 // connecting.
882 if (sourceModule == sinkModule) {
883 LLVM_DEBUG(llvm::dbgs()
884 << " LCA: " << sourceModule.getModuleName() << "\n");
885 moduleModifications[sourceModule].connectionMap[index] = source;
886 moduleModifications[sourceModule].uturns.push_back({index, sink});
887 continue;
888 }
889
890 // Otherwise, get instance paths for source/sink, and compute LCA.
891 auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
892 auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
893
894 if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
895 auto diag =
896 mlir::emitError(source.getLoc())
897 << "This source is involved with a Wiring Problem where the source "
898 "or the sink are multiply instantiated and this is not supported.";
899 diag.attachNote(sink.getLoc()) << "The sink is here.";
900 return failure();
901 }
902
903 FModuleOp lca =
904 cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
905 auto sources = sourcePaths[0];
906 auto sinks = sinkPaths[0];
907 while (!sources.empty() && !sinks.empty()) {
908 if (sources.top() != sinks.top())
909 break;
910 auto newLCA = cast<InstanceOp>(*sources.top());
911 lca = cast<FModuleOp>(newLCA.getReferencedModule(instanceGraph));
912 sources = sources.dropFront();
913 sinks = sinks.dropFront();
914 }
915
916 LLVM_DEBUG({
917 llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
918 << " sourcePath: " << sourcePaths[0] << "\n"
919 << " sinkPaths: " << sinkPaths[0] << "\n";
920 });
921
922 // Pre-populate the connectionMap of the module with the source and sink.
923 moduleModifications[sourceModule].connectionMap[index] = source;
924 moduleModifications[sinkModule].connectionMap[index] = sink;
925
926 // Record port types that should be added to each module along the LCA path.
927 Type sourceType, sinkType;
928 auto useRefTypes =
929 !noRefTypePorts &&
930 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
931 if (useRefTypes) {
932 // Use RefType ports if possible
933 RefType refType = TypeSwitch<Type, RefType>(source.getType())
934 .Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
935 return RefType::get(base.getPassiveType());
936 })
937 .Case<RefType>([](RefType ref) { return ref; });
938 sourceType = refType;
939 sinkType = refType.getType();
940 } else {
941 // Use specified port types.
942 sourceType = source.getType();
943 sinkType = sink.getType();
944
945 // Types must be connectable, which means FIRRTLType's.
946 auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
947 auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
948 if (!sourceFType)
949 return emitError(source.getLoc())
950 << "Wiring Problem source type \"" << sourceType
951 << "\" must be a FIRRTL type";
952 if (!sinkFType)
953 return emitError(sink.getLoc())
954 << "Wiring Problem sink type \"" << sinkType
955 << "\" must be a FIRRTL type";
956
957 // Otherwise they must be identical or FIRRTL type-equivalent
958 // (connectable).
959 if (sourceFType != sinkFType &&
960 !areTypesEquivalent(sinkFType, sourceFType)) {
961 // Support tapping mixed alignment -> passive , emulate probe behavior.
962 if (auto sourceBaseType = dyn_cast<FIRRTLBaseType>(sourceFType);
963 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer &&
964 sourceBaseType &&
965 areTypesEquivalent(sinkFType, sourceBaseType.getPassiveType())) {
966 // Change "sourceType" to the passive version that's type-equivalent,
967 // this will be used for wiring on the "up" side.
968 // This relies on `emitConnect` supporting connecting to the passive
969 // version from the original source.
970 sourceType = sourceBaseType.getPassiveType();
971 } else {
972 auto diag = mlir::emitError(source.getLoc())
973 << "Wiring Problem source type " << sourceType
974 << " does not match sink type " << sinkType;
975 diag.attachNote(sink.getLoc()) << "The sink is here.";
976 return failure();
977 }
978 }
979 }
980 // If wiring using references, check that the sink value we connect to is
981 // passive.
982 if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
983 sinkFType && type_isa<RefType>(sourceType) &&
984 !getBaseType(sinkFType).isPassive())
985 return emitError(sink.getLoc())
986 << "Wiring Problem sink type \"" << sink.getType()
987 << "\" must be passive (no flips) when using references";
988
989 // Record module modifications related to adding ports to modules.
990 auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
991 Direction dir) -> LogicalResult {
992 StringRef name, instName;
993 for (auto instNode : llvm::reverse(insts)) {
994 auto inst = cast<InstanceOp>(*instNode);
995 auto mod = inst.getReferencedModule<FModuleOp>(instanceGraph);
996 if (mod.isPublic()) {
997 auto diag = emitError(mod.getLoc(),
998 "cannot wire port through this public module");
999 diag.attachNote(source.getLoc()) << "source here";
1000 diag.attachNote(sink.getLoc()) << "sink here";
1001 return diag;
1002 }
1003 if (name.empty()) {
1004 if (problem.newNameHint.empty())
1005 name = state.getNamespace(mod).newName(
1007 getFieldRefFromValue(val, /*lookThroughCasts=*/true),
1008 /*nameSafe=*/true)
1009 .first +
1010 "__bore");
1011 else
1012 name = state.getNamespace(mod).newName(problem.newNameHint);
1013 } else {
1014 assert(!instName.empty());
1015 name = state.getNamespace(mod).newName(instName + "_" + name);
1016 }
1017 moduleModifications[mod].portsToAdd.push_back(
1018 {index, {StringAttr::get(context, name), tpe, dir}});
1019 instName = inst.getInstanceName();
1020 }
1021 return success();
1022 };
1023
1024 // Record the addition of ports.
1025 if (failed(addPorts(sources, source, sourceType, Direction::Out)) ||
1026 failed(addPorts(sinks, sink, sinkType, Direction::In)))
1027 return failure();
1028 }
1029
1030 // Iterate over modules from leaves to roots, applying ModuleModifications to
1031 // each module.
1032 LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
1033 for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
1034 auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
1035 // Skip external modules and modules that have no modifications.
1036 if (!fmodule || !moduleModifications.count(fmodule))
1037 continue;
1038
1039 auto modifications = moduleModifications[fmodule];
1040 LLVM_DEBUG({
1041 llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
1042 llvm::dbgs() << " ports:\n";
1043 for (auto [index, port] : modifications.portsToAdd) {
1044 llvm::dbgs() << " - name: " << port.getName() << "\n"
1045 << " id: " << index << "\n"
1046 << " type: " << port.type << "\n"
1047 << " direction: "
1048 << (port.direction == Direction::In ? "in" : "out")
1049 << "\n";
1050 }
1051 });
1052
1053 // Add ports to the module after all other existing ports.
1054 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
1055 SmallVector<unsigned> problemIndices;
1056 for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
1057 // Create the port.
1058 newPorts.push_back({fmodule.getNumPorts(), portInfo});
1059 problemIndices.push_back(problemIdx);
1060 }
1061 auto originalNumPorts = fmodule.getNumPorts();
1062 auto portIdx = fmodule.getNumPorts();
1063 fmodule.insertPorts(newPorts);
1064
1065 auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
1066 fmodule.getBodyBlock());
1067
1068 // Connect each port to the value stored in the connectionMap for this
1069 // wiring problem index.
1070 for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
1071 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1072 assert(src && "there did not exist a driver for the port");
1073 Value dest = fmodule.getArgument(portIdx++);
1074 if (failed(connect(src, dest, builder)))
1075 return failure();
1076 }
1077
1078 // If a U-turn exists, this is an LCA and we need a U-turn connection. These
1079 // are the last connections made for this module.
1080 for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
1081 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1082 assert(src && "there did not exist a connection for the u-turn");
1083 if (failed(connect(src, dest, builder)))
1084 return failure();
1085 }
1086
1087 // Update the connectionMap of all modules for which we created a port.
1088 for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
1089 InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
1090 auto enclosingModule = useInst->getParentOfType<FModuleOp>();
1091 auto clonedInst = useInst.cloneWithInsertedPortsAndReplaceUses(newPorts);
1092 state.instancePathCache.replaceInstance(useInst, clonedInst);
1093 useInst->erase();
1094 // Record information in the moduleModifications strucutre for the module
1095 // _where this is instantiated_. This is done so that when that module is
1096 // visited later, there will be information available for it to find ports
1097 // it needs to wire up. If there is already an existing connection, then
1098 // this is a U-turn.
1099 for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
1100 auto &modifications = moduleModifications[enclosingModule];
1101 auto newPort = clonedInst.getResult(newPortIdx + originalNumPorts);
1102 if (modifications.connectionMap.count(problemIdx)) {
1103 modifications.uturns.push_back({problemIdx, newPort});
1104 continue;
1105 }
1106 modifications.connectionMap[problemIdx] = newPort;
1107 }
1108 }
1109 }
1110
1111 // Delete unused WireOps created by producers of WiringProblems.
1112 for (auto *op : opsToErase)
1113 op->erase();
1114
1115 return success();
1116}
1117
1118// This is the main entrypoint for the lowering pass.
1119void LowerAnnotationsPass::runOnOperation() {
1121
1122 CircuitOp circuit = getOperation();
1123 SymbolTable modules(circuit);
1124
1125 // Grab the annotations from a non-standard attribute called "rawAnnotations".
1126 // This is a temporary location for all annotations that are earmarked for
1127 // processing by this pass as we migrate annotations from being handled by
1128 // FIRAnnotations/FIRParser into this pass. While we do this, this pass is
1129 // not supposed to touch _other_ annotations to enable this pass to be run
1130 // after FIRAnnotations/FIRParser.
1131 auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
1132 if (!annotations)
1133 return;
1134 circuit->removeAttr(rawAnnotations);
1135
1136 // Populate the worklist in reverse order. This has the effect of causing
1137 // annotations to be processed in the order in which they appear in the
1138 // original JSON.
1139 for (auto anno : llvm::reverse(annotations.getValue()))
1140 worklistAttrs.push_back(cast<DictionaryAttr>(anno));
1141
1142 size_t numFailures = 0;
1143 size_t numAdded = 0;
1144 auto addToWorklist = [&](DictionaryAttr anno) {
1145 ++numAdded;
1146 worklistAttrs.push_back(anno);
1147 };
1148 InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
1149 ApplyState state{circuit, modules, addToWorklist, instancePathCache,
1150 noRefTypePorts};
1151 LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
1152 while (!worklistAttrs.empty()) {
1153 auto attr = worklistAttrs.pop_back_val();
1154 if (applyAnnotation(attr, state).failed())
1155 ++numFailures;
1156 }
1157
1158 if (failed(legacyToWiringProblems(state)))
1159 ++numFailures;
1160
1161 if (failed(solveWiringProblems(state)))
1162 ++numFailures;
1163
1164 // Update statistics
1165 numRawAnnotations += annotations.size();
1166 numAddedAnnos += numAdded;
1167 numAnnos += numAdded + annotations.size();
1168 numReusedHierPathOps += state.numReusedHierPaths;
1169
1170 if (numFailures)
1171 signalPassFailure();
1172}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
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
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.
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
std::optional< AnnoPathValue > stdResolve(DictionaryAttr anno, ApplyState &state)
===-------------------------------------------------------------------—===// Standard Utility Resolve...
Flow foldFlow(Value val, Flow accumulatedFlow=Flow::Source)
Compute the flow for a Value, val, as determined by the FIRRTL specification.
constexpr const char * rawAnnotations
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.
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc)
std::string canonicalizeTarget(StringRef target)
Return an input target string in canonical form.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
LogicalResult registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord, const std::function< void(llvm::Twine)> &errorHandler={})
Register external annotation records.
StringRef getPortAnnotationAttrName()
Return the name of the attribute used for port annotations on FIRRTL ops.
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...
std::optional< AnnoPathValue > tryResolve(DictionaryAttr anno, ApplyState &state)
Resolves with target, if it exists. If not, resolves to the circuit.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
static llvm::StringMap< AnnoRecord > annotationRecords
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.