CIRCT 24.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 mlir::SymbolTable::setSymbolVisibility(moduleOp.getOperation(),
256 mlir::SymbolTable::Visibility::Public);
257 SmallVector<NamedAttribute> newAnnoAttrs;
258 for (auto &na : anno)
259 if (na.getName().getValue() != "target")
260 newAnnoAttrs.push_back(na);
261 addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
262 return success();
263}
264
265// Like symbolizeConvention, but disallows the internal convention.
266static std::optional<Convention> parseConvention(llvm::StringRef str) {
267 return ::llvm::StringSwitch<::std::optional<Convention>>(str)
268 .Case("scalarized", Convention::Scalarized)
269 .Default(std::nullopt);
270}
271
272static LogicalResult applyConventionAnno(const AnnoPathValue &target,
273 DictionaryAttr anno,
274 ApplyState &state) {
275 auto *op = target.ref.getOp();
276 auto loc = op->getLoc();
277 auto error = [&]() {
278 auto diag = mlir::emitError(loc);
279 diag << "circuit.ConventionAnnotation ";
280 return diag;
281 };
282
283 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
284 if (!opTarget)
285 return error() << "must target a module object";
286
287 if (!target.isLocal())
288 return error() << "must be local";
289
290 auto conventionStrAttr =
291 tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
292 if (!conventionStrAttr)
293 return failure();
294
295 auto conventionStr = conventionStrAttr.getValue();
296 auto conventionOpt = parseConvention(conventionStr);
297 if (!conventionOpt)
298 return error() << "unknown convention " << conventionStr;
299
300 auto convention = *conventionOpt;
301
302 if (auto moduleOp = dyn_cast<FModuleOp>(op)) {
303 moduleOp.setConvention(convention);
304 return success();
305 }
306
307 if (auto extModuleOp = dyn_cast<FExtModuleOp>(op)) {
308 extModuleOp.setConvention(convention);
309 return success();
310 }
311
312 return error() << "can only target to a module or extmodule";
313}
314
315static LogicalResult applyBodyTypeLoweringAnno(const AnnoPathValue &target,
316 DictionaryAttr anno,
317 ApplyState &state) {
318 auto *op = target.ref.getOp();
319 auto loc = op->getLoc();
320 auto error = [&]() {
321 auto diag = mlir::emitError(loc);
322 diag << bodyTypeLoweringAnnoClass;
323 return diag;
324 };
325
326 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
327 if (!opTarget)
328 return error() << "must target a module object";
329
330 if (!target.isLocal())
331 return error() << "must be local";
332
333 auto moduleOp = dyn_cast<FModuleOp>(op);
334
335 if (!moduleOp)
336 return error() << "can only target to a module";
337
338 auto conventionStrAttr =
339 tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
340
341 if (!conventionStrAttr)
342 return failure();
343
344 auto conventionStr = conventionStrAttr.getValue();
345 auto conventionOpt = parseConvention(conventionStr);
346 if (!conventionOpt)
347 return error() << "unknown convention " << conventionStr;
348
349 auto convention = *conventionOpt;
350
351 if (convention == Convention::Internal)
352 // Convention is internal by default so there is nothing to change
353 return success();
354
355 auto conventionAttr = ConventionAttr::get(op->getContext(), convention);
356
357 // `includeHierarchy` only valid in BodyTypeLowering.
358 bool includeHierarchy = false;
359 if (auto includeHierarchyAttr = tryGetAs<BoolAttr>(
360 anno, anno, "includeHierarchy", loc, conventionAnnoClass))
361 includeHierarchy = includeHierarchyAttr.getValue();
362
363 if (includeHierarchy) {
364 // If includeHierarchy is true, update the convention for all modules in
365 // the hierarchy.
366 for (auto *node :
367 llvm::post_order(state.instancePathCache.instanceGraph[moduleOp])) {
368 if (!node)
369 continue;
370 if (auto fmodule = dyn_cast<FModuleOp>(*node->getModule()))
371 fmodule->setAttr("body_type_lowering", conventionAttr);
372 }
373 } else {
374 // Update the convention.
375 moduleOp->setAttr("body_type_lowering", conventionAttr);
376 }
377
378 return success();
379}
380
381static LogicalResult applyModulePrefixAnno(const AnnoPathValue &target,
382 DictionaryAttr anno,
383 ApplyState &state) {
384 auto *op = target.ref.getOp();
385 auto loc = op->getLoc();
386 auto error = [&]() {
387 auto diag = mlir::emitError(loc);
388 diag << modulePrefixAnnoClass << " ";
389 return diag;
390 };
391
392 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
393 if (!opTarget)
394 return error() << "must target an operation";
395
396 if (!isa<SeqMemOp, CombMemOp, MemOp>(opTarget.getOp()))
397 return error() << "must target a memory operation";
398
399 if (!target.isLocal())
400 return error() << "must be local";
401
402 auto prefixStrAttr =
403 tryGetAs<StringAttr>(anno, anno, "prefix", loc, modulePrefixAnnoClass);
404 if (!prefixStrAttr)
405 return failure();
406
407 if (auto mem = dyn_cast<SeqMemOp>(op))
408 mem.setPrefixAttr(prefixStrAttr);
409 else if (auto mem = dyn_cast<CombMemOp>(op))
410 mem.setPrefixAttr(prefixStrAttr);
411 else if (auto mem = dyn_cast<MemOp>(op))
412 mem.setPrefixAttr(prefixStrAttr);
413
414 return success();
415}
416
417static LogicalResult applyAttributeAnnotation(const AnnoPathValue &target,
418 DictionaryAttr anno,
419 ApplyState &state) {
420 auto *op = target.ref.getOp();
421
422 auto error = [&]() {
423 auto diag = mlir::emitError(op->getLoc());
424 diag << anno.getAs<StringAttr>("class").getValue() << " ";
425 return diag;
426 };
427
428 if (!isa<OpAnnoTarget>(target.ref))
429 return error()
430 << "must target an operation. Currently ports are not supported";
431
432 if (!target.isLocal())
433 return error() << "must be local";
434
435 if (!isa<FModuleOp, WireOp, NodeOp, RegOp, RegResetOp>(op))
436 return error()
437 << "unhandled operation. The target must be a module, wire, node or "
438 "register";
439
440 auto name = anno.getAs<StringAttr>("description");
441 auto svAttr = sv::SVAttributeAttr::get(name.getContext(), name);
442 sv::addSVAttributes(op, {svAttr});
443 return success();
444}
445
446/// Update a memory op with attributes about memory file loading.
447template <bool isInline>
448static LogicalResult applyLoadMemoryAnno(const AnnoPathValue &target,
449 DictionaryAttr anno,
450 ApplyState &state) {
451 if (!target.isLocal()) {
452 mlir::emitError(state.circuit.getLoc())
453 << "has a " << anno.get("class")
454 << " annotation which is non-local, but this annotation is not allowed "
455 "to be non-local";
456 return failure();
457 }
458
459 auto *op = target.ref.getOp();
460
461 if (!target.isOpOfType<MemOp, CombMemOp, SeqMemOp>()) {
462 mlir::emitError(op->getLoc())
463 << "can only apply a load memory annotation to a memory";
464 return failure();
465 }
466
467 // The two annotations have different case usage in "filename".
468 StringAttr filename = tryGetAs<StringAttr>(
469 anno, anno, isInline ? "filename" : "fileName", op->getLoc(),
470 anno.getAs<StringAttr>("class").getValue());
471 if (!filename)
472 return failure();
473
474 auto hexOrBinary =
475 tryGetAs<StringAttr>(anno, anno, "hexOrBinary", op->getLoc(),
476 anno.getAs<StringAttr>("class").getValue());
477 if (!hexOrBinary)
478 return failure();
479
480 auto hexOrBinaryValue = hexOrBinary.getValue();
481 if (hexOrBinaryValue != "h" && hexOrBinaryValue != "b") {
482 auto diag = mlir::emitError(op->getLoc())
483 << "has memory initialization annotation with invalid format, "
484 "'hexOrBinary' field must be either 'h' or 'b'";
485 diag.attachNote() << "the full annotation is: " << anno;
486 return failure();
487 }
488
489 op->setAttr("init", MemoryInitAttr::get(op->getContext(), filename,
490 hexOrBinaryValue == "b", isInline));
491
492 return success();
493}
494
495static LogicalResult applyOutputDirAnno(const AnnoPathValue &target,
496 DictionaryAttr anno,
497 ApplyState &state) {
498 auto *op = target.ref.getOp();
499 auto *context = op->getContext();
500 auto loc = op->getLoc();
501
502 auto error = [&]() {
503 return mlir::emitError(loc) << outputDirAnnoClass << " ";
504 };
505
506 auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
507 if (!opTarget)
508 return error() << "must target a module";
509 if (!target.isLocal())
510 return error() << "must be local";
511
512 auto moduleOp = dyn_cast<FModuleOp>(op);
513 if (!moduleOp)
514 return error() << "must target a module";
515 if (!moduleOp.isPublic())
516 return error() << "must target a public module";
517 if (moduleOp->hasAttr("output_file"))
518 return error() << "target already has an output file";
519
520 auto dirname =
521 tryGetAs<StringAttr>(anno, anno, "dirname", loc, outputDirAnnoClass);
522 if (!dirname)
523 return failure();
524 if (dirname.empty())
525 return error() << "dirname must not be empty";
526
527 auto outputFile =
528 hw::OutputFileAttr::getAsDirectory(context, dirname.getValue());
529
530 moduleOp->setAttr("output_file", outputFile);
531 return success();
532}
533
534/// Convert from FullAsyncResetAnnotation to FullResetAnnotation
535static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target,
536 DictionaryAttr anno,
537 ApplyState &state) {
538 auto *op = target.ref.getOp();
539 auto *context = op->getContext();
540
541 mlir::emitWarning(op->getLoc())
542 << "'" << fullAsyncResetAnnoClass << "' is deprecated, use '"
543 << fullResetAnnoClass << "' instead";
544
545 NamedAttrList newAnno(anno.getValue());
546 newAnno.set("class", StringAttr::get(context, fullResetAnnoClass));
547 newAnno.append("resetType", StringAttr::get(context, "async"));
548
549 DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
550
551 return applyWithoutTarget<false>(target, newDictionary, state);
552}
553
554/// Convert from IgnoreFullAsyncResetAnnotation to
555/// ExcludeFromFullResetAnnotation
557 const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state) {
558 auto *op = target.ref.getOp();
559 auto *context = op->getContext();
560
561 mlir::emitWarning(op->getLoc())
562 << "'" << ignoreFullAsyncResetAnnoClass << "' is deprecated, use '"
563 << excludeFromFullResetAnnoClass << "' instead";
564
565 NamedAttrList newAnno(anno.getValue());
566 newAnno.set("class", StringAttr::get(context, excludeFromFullResetAnnoClass));
567
568 DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
569
570 return applyWithoutTarget<true, FModuleOp>(target, newDictionary, state);
571}
572
573//===----------------------------------------------------------------------===//
574// Driving table
575//===----------------------------------------------------------------------===//
576
577namespace circt::firrtl {
578/// Resolution and application of a "firrtl.annotations.NoTargetAnnotation".
579/// This should be used for any Annotation which does not apply to anything in
580/// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically,
581/// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or
582/// its passes, e.g., to set the output directory or to turn on a pass.
583/// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set
584/// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to
585/// cause the SFC to schedule a specified pass. Instead of leaving these
586/// floating or attaching them to the top-level MLIR module (which is a purer
587/// interpretation of "no target"), we choose to attach them to the Circuit even
588/// they do not "apply" to the Circuit. This gives later passes a common place,
589/// the Circuit, to search for these control Annotations.
591 applyWithoutTarget<false, CircuitOp>};
592
593static llvm::StringMap<AnnoRecord> annotationRecords{{
594
595 // Testing Annotations (manually maintained for testing infrastructure)
596 {"circt.test", {stdResolve, applyWithoutTarget<true>}},
597 {"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
598 {"circt.testNT", {noResolve, applyWithoutTarget<>}},
599 {"circt.missing", {tryResolve, applyWithoutTarget<true>}},
600
601// Auto-generated annotation records from FIRRTLAnnotations.td
602#define GET_ANNOTATION_RECORD_LIST
603#include "circt/Dialect/FIRRTL/FIRRTLAnnotationRecords.h.inc"
604}};
605
606LogicalResult
607registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord,
608 const std::function<void(llvm::Twine)> &errorHandler) {
609
610 if (annotationRecords.insert({annoClass, annoRecord}).second)
611 return LogicalResult::success();
612 if (errorHandler)
613 errorHandler("annotation record '" + annoClass + "' is registered twice\n");
614 return LogicalResult::failure();
615}
616
617} // namespace circt::firrtl
618
619/// Lookup a record for a given annotation class. Optionally, returns the
620/// record for "circuit.missing" if the record doesn't exist.
621static const AnnoRecord *getAnnotationHandler(StringRef annoStr,
622 bool ignoreAnnotationUnknown) {
623 auto ii = annotationRecords.find(annoStr);
624 if (ii != annotationRecords.end())
625 return &ii->second;
626 if (ignoreAnnotationUnknown)
627 return &annotationRecords.find("circt.missing")->second;
628 return nullptr;
629}
630
631//===----------------------------------------------------------------------===//
632// Pass Infrastructure
633//===----------------------------------------------------------------------===//
634
635namespace {
636struct LowerAnnotationsPass
637 : public circt::firrtl::impl::LowerFIRRTLAnnotationsBase<
638 LowerAnnotationsPass> {
639 using Base::Base;
640
641 void runOnOperation() override;
642 LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
643 LogicalResult legacyToWiringProblems(ApplyState &state);
644 LogicalResult solveWiringProblems(ApplyState &state);
645
646 SmallVector<DictionaryAttr> worklistAttrs;
647};
648} // end anonymous namespace
649
650LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
651 ApplyState &state) {
652 LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
653
654 // Lookup the class
655 StringRef annoClassVal;
656 if (auto annoClass = anno.getNamed("class"))
657 annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
658 else if (ignoreAnnotationClassless)
659 annoClassVal = "circt.missing";
660 else
661 return mlir::emitError(state.circuit.getLoc())
662 << "Annotation without a class: " << anno;
663
664 // See if we handle the class
665 auto *record = getAnnotationHandler(annoClassVal, false);
666 if (!record) {
667 ++numUnhandled;
668 if (!ignoreAnnotationUnknown)
669 return mlir::emitError(state.circuit.getLoc())
670 << "Unhandled annotation: " << anno;
671
672 // Try again, requesting the fallback handler.
673 record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
674 assert(record);
675 }
676
677 // Try to apply the annotation
678 auto target = record->resolver(anno, state);
679 if (!target)
680 return mlir::emitError(state.circuit.getLoc())
681 << "Unable to resolve target of annotation: " << anno;
682 if (record->applier(*target, anno, state).failed())
683 return mlir::emitError(state.circuit.getLoc())
684 << "Unable to apply annotation: " << anno;
685 return success();
686}
687
688/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
689/// using the pin attribute as newNameHint
690LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
691 for (const auto &[name, problem] : state.legacyWiringProblems) {
692 if (!problem.source)
693 return mlir::emitError(state.circuit.getLoc())
694 << "Unable to resolve source for pin: " << name;
695
696 if (problem.sinks.empty())
697 return mlir::emitError(state.circuit.getLoc())
698 << "Unable to resolve sink(s) for pin: " << name;
699
700 for (const auto &sink : problem.sinks) {
701 state.wiringProblems.push_back(
702 {problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
703 }
704 }
705 return success();
706}
707
708/// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
709/// Wiring Problem is a mapping from a source to a sink that can be connected
710/// via a base Type or RefType as requested. This uses a two-step approach.
711/// First, all Wiring Problems are analyzed to compute pending modifications to
712/// modules. Second, modules are visited from leaves to roots to apply module
713/// modifications. Module modifications include addings ports and connecting
714/// things up.
715LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
716 // Utility function to extract the defining module from a value which may be
717 // either a BlockArgument or an Operation result.
718 auto getModule = [](Value value) {
719 if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
720 return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
721 return value.getDefiningOp()->getParentOfType<FModuleLike>();
722 };
723
724 // Utility function to determine where to insert connection operations.
725 auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
726 // Check for easy case: both are in the same block.
727 if (src.getParentBlock() == dest.getParentBlock())
728 return src.getParentBlock();
729
730 // If connecting across blocks, figure out where to connect.
731 (void)getModule;
732 assert(getModule(src) == getModule(dest));
733 // Helper to determine if 'a' is available at 'b's block.
734 auto safelyDoms = [&](Value a, Value b) {
735 if (isa<BlockArgument>(a))
736 return true;
737 if (isa<BlockArgument>(b))
738 return false;
739 // Handle cases where 'b' is in child op after 'a'.
740 auto *ancestor =
741 a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
742 return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
743 };
744 if (safelyDoms(src, dest))
745 return dest.getParentBlock();
746 if (safelyDoms(dest, src))
747 return src.getParentBlock();
748 return {};
749 };
750
751 auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
752 auto op =
753 dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
754 if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
755 op.getResultTypes()[0] == op.getOperandTypes()[0])
756 return op;
757 return {};
758 };
759
760 // Utility function to connect a destination to a source. Always use a
761 // ConnectOp as the widths may be uninferred.
762 SmallVector<Operation *> opsToErase;
763 auto connect = [&](Value src, Value dest,
764 ImplicitLocOpBuilder &builder) -> LogicalResult {
765 // Strip away noop unrealized_conversion_cast's, used as placeholders.
766 // In the future, these should be created/managed as part of creating WP's.
767 if (auto op = getNoopCast(dest)) {
768 dest = op.getOperand(0);
769 opsToErase.push_back(op);
770 std::swap(src, dest);
771 } else if (auto op = getNoopCast(src)) {
772 src = op.getOperand(0);
773 opsToErase.push_back(op);
774 }
775
776 if (foldFlow(dest) == Flow::Source)
777 std::swap(src, dest);
778
779 // Figure out where to insert operations.
780 auto *insertBlock = findInsertionBlock(src, dest);
781 if (!insertBlock)
782 return emitError(src.getLoc())
783 .append("This value is involved with a Wiring Problem where the "
784 "destination is in the same module but neither dominates the "
785 "other, which is not supported.")
786 .attachNote(dest.getLoc())
787 .append("The destination is here.");
788
789 // Insert at end, past invalidation in same block.
790 builder.setInsertionPointToEnd(insertBlock);
791
792 // Create RefSend/RefResolve if necessary.
793 if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
794 if (type_isa<RefType>(dest.getType()))
795 src = RefSendOp::create(builder, src);
796 else
797 src = RefResolveOp::create(builder, src);
798 }
799
800 // If the sink is a wire with no users, then convert this to a node.
801 // This is done to convert the undriven wires created for GCView's
802 // into the NodeOp's they're required to be in GrandCentral.cpp.
803 if (auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
804 destOp && dest.getUses().empty()) {
805 // Only perform this if the type is suitable (passive).
806 if (auto baseType = dyn_cast<FIRRTLBaseType>(src.getType());
807 baseType && baseType.isPassive()) {
808 // Note that the wire is replaced with the source type
809 // regardless, continue this behavior.
810 NodeOp::create(builder, src, destOp.getName())
811 .setAnnotationsAttr(destOp.getAnnotations());
812 opsToErase.push_back(destOp);
813 return success();
814 }
815 }
816
817 // Otherwise, just connect to the source.
818 emitConnect(builder, dest, src);
819
820 return success();
821 };
822
823 auto &instanceGraph = state.instancePathCache.instanceGraph;
824 auto *context = state.circuit.getContext();
825
826 // Examine all discovered Wiring Problems to determine modifications that need
827 // to be made per-module.
828 LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
829 DenseMap<FModuleLike, ModuleModifications> moduleModifications;
830 DenseSet<Value> visitedSinks;
831 for (auto e : llvm::enumerate(state.wiringProblems)) {
832 auto index = e.index();
833 auto problem = e.value();
834 // This is a unique index that is assigned to this specific wiring problem
835 // and is used as a key during wiring to know which Values (ports, sources,
836 // or sinks) should be connected.
837 auto source = problem.source;
838 auto sink = problem.sink;
839
840 // Check that no WiringProblems are trying to use the same sink. This
841 // should never happen.
842 if (!visitedSinks.insert(sink).second) {
843 auto diag = mlir::emitError(source.getLoc())
844 << "This sink is involved with a Wiring Problem which is "
845 "targeted by a source used by another Wiring Problem. "
846 "(This is both illegal and should be impossible.)";
847 diag.attachNote(source.getLoc()) << "The source is here";
848 return failure();
849 }
850 FModuleLike sourceModule = getModule(source);
851 FModuleLike sinkModule = getModule(sink);
852 if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
853 auto diag = mlir::emitError(source.getLoc())
854 << "This source is involved with a Wiring Problem which "
855 "includes an External Module port and External Module "
856 "ports anre not supported.";
857 diag.attachNote(sink.getLoc()) << "The sink is here.";
858 return failure();
859 }
860
861 LLVM_DEBUG({
862 llvm::dbgs() << " - index: " << index << "\n"
863 << " source:\n"
864 << " module: " << sourceModule.getModuleName() << "\n"
865 << " value: " << source << "\n"
866 << " sink:\n"
867 << " module: " << sinkModule.getModuleName() << "\n"
868 << " value: " << sink << "\n"
869 << " newNameHint: " << problem.newNameHint << "\n";
870 });
871
872 // If the source and sink are in the same block, just wire them up.
873 if (sink.getParentBlock() == source.getParentBlock()) {
874 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
875 sink.getParentBlock());
876 if (failed(connect(source, sink, builder)))
877 return failure();
878 continue;
879 }
880 // If both are in the same module but not same block, U-turn.
881 // We may not be able to handle this, but that is checked below while
882 // connecting.
883 if (sourceModule == sinkModule) {
884 LLVM_DEBUG(llvm::dbgs()
885 << " LCA: " << sourceModule.getModuleName() << "\n");
886 moduleModifications[sourceModule].connectionMap[index] = source;
887 moduleModifications[sourceModule].uturns.push_back({index, sink});
888 continue;
889 }
890
891 // Otherwise, get instance paths for source/sink, and compute LCA.
892 auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
893 auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
894
895 if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
896 auto diag =
897 mlir::emitError(source.getLoc())
898 << "This source is involved with a Wiring Problem where the source "
899 "or the sink are multiply instantiated and this is not supported.";
900 diag.attachNote(sink.getLoc()) << "The sink is here.";
901 return failure();
902 }
903
904 FModuleOp lca =
905 cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
906 auto sources = sourcePaths[0];
907 auto sinks = sinkPaths[0];
908 while (!sources.empty() && !sinks.empty()) {
909 if (sources.top() != sinks.top())
910 break;
911 auto newLCA = cast<InstanceOp>(*sources.top());
912 lca = cast<FModuleOp>(newLCA.getReferencedModule(instanceGraph));
913 sources = sources.dropFront();
914 sinks = sinks.dropFront();
915 }
916
917 LLVM_DEBUG({
918 llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
919 << " sourcePath: " << sourcePaths[0] << "\n"
920 << " sinkPaths: " << sinkPaths[0] << "\n";
921 });
922
923 // Pre-populate the connectionMap of the module with the source and sink.
924 moduleModifications[sourceModule].connectionMap[index] = source;
925 moduleModifications[sinkModule].connectionMap[index] = sink;
926
927 // Record port types that should be added to each module along the LCA path.
928 Type sourceType, sinkType;
929 auto useRefTypes =
930 !noRefTypePorts &&
931 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
932 if (useRefTypes) {
933 // Use RefType ports if possible
934 RefType refType = TypeSwitch<Type, RefType>(source.getType())
935 .Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
936 return RefType::get(base.getPassiveType());
937 })
938 .Case<RefType>([](RefType ref) { return ref; });
939 sourceType = refType;
940 sinkType = refType.getType();
941 } else {
942 // Use specified port types.
943 sourceType = source.getType();
944 sinkType = sink.getType();
945
946 // Types must be connectable, which means FIRRTLType's.
947 auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
948 auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
949 if (!sourceFType)
950 return emitError(source.getLoc())
951 << "Wiring Problem source type \"" << sourceType
952 << "\" must be a FIRRTL type";
953 if (!sinkFType)
954 return emitError(sink.getLoc())
955 << "Wiring Problem sink type \"" << sinkType
956 << "\" must be a FIRRTL type";
957
958 // Otherwise they must be identical or FIRRTL type-equivalent
959 // (connectable).
960 if (sourceFType != sinkFType &&
961 !areTypesEquivalent(sinkFType, sourceFType)) {
962 // Support tapping mixed alignment -> passive , emulate probe behavior.
963 if (auto sourceBaseType = dyn_cast<FIRRTLBaseType>(sourceFType);
964 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer &&
965 sourceBaseType &&
966 areTypesEquivalent(sinkFType, sourceBaseType.getPassiveType())) {
967 // Change "sourceType" to the passive version that's type-equivalent,
968 // this will be used for wiring on the "up" side.
969 // This relies on `emitConnect` supporting connecting to the passive
970 // version from the original source.
971 sourceType = sourceBaseType.getPassiveType();
972 } else {
973 auto diag = mlir::emitError(source.getLoc())
974 << "Wiring Problem source type " << sourceType
975 << " does not match sink type " << sinkType;
976 diag.attachNote(sink.getLoc()) << "The sink is here.";
977 return failure();
978 }
979 }
980 }
981 // If wiring using references, check that the sink value we connect to is
982 // passive.
983 if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
984 sinkFType && type_isa<RefType>(sourceType) &&
985 !getBaseType(sinkFType).isPassive())
986 return emitError(sink.getLoc())
987 << "Wiring Problem sink type \"" << sink.getType()
988 << "\" must be passive (no flips) when using references";
989
990 // Record module modifications related to adding ports to modules.
991 auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
992 Direction dir) -> LogicalResult {
993 StringRef name, instName;
994 for (auto instNode : llvm::reverse(insts)) {
995 auto inst = cast<InstanceOp>(*instNode);
996 auto mod = inst.getReferencedModule<FModuleOp>(instanceGraph);
997 if (mod.isPublic()) {
998 auto diag = emitError(mod.getLoc(),
999 "cannot wire port through this public module");
1000 diag.attachNote(source.getLoc()) << "source here";
1001 diag.attachNote(sink.getLoc()) << "sink here";
1002 return diag;
1003 }
1004 if (name.empty()) {
1005 if (problem.newNameHint.empty())
1006 name = state.getNamespace(mod).newName(
1008 getFieldRefFromValue(val, /*lookThroughCasts=*/true),
1009 /*nameSafe=*/true)
1010 .first +
1011 "__bore");
1012 else
1013 name = state.getNamespace(mod).newName(problem.newNameHint);
1014 } else {
1015 assert(!instName.empty());
1016 name = state.getNamespace(mod).newName(instName + "_" + name);
1017 }
1018 moduleModifications[mod].portsToAdd.push_back(
1019 {index, {StringAttr::get(context, name), tpe, dir}});
1020 instName = inst.getInstanceName();
1021 }
1022 return success();
1023 };
1024
1025 // Record the addition of ports.
1026 if (failed(addPorts(sources, source, sourceType, Direction::Out)) ||
1027 failed(addPorts(sinks, sink, sinkType, Direction::In)))
1028 return failure();
1029 }
1030
1031 // Iterate over modules from leaves to roots, applying ModuleModifications to
1032 // each module.
1033 LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
1034 for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
1035 auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
1036 // Skip external modules and modules that have no modifications.
1037 if (!fmodule || !moduleModifications.count(fmodule))
1038 continue;
1039
1040 auto modifications = moduleModifications[fmodule];
1041 LLVM_DEBUG({
1042 llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
1043 llvm::dbgs() << " ports:\n";
1044 for (auto [index, port] : modifications.portsToAdd) {
1045 llvm::dbgs() << " - name: " << port.getName() << "\n"
1046 << " id: " << index << "\n"
1047 << " type: " << port.type << "\n"
1048 << " direction: "
1049 << (port.direction == Direction::In ? "in" : "out")
1050 << "\n";
1051 }
1052 });
1053
1054 // Add ports to the module after all other existing ports.
1055 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
1056 SmallVector<unsigned> problemIndices;
1057 for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
1058 // Create the port.
1059 newPorts.push_back({fmodule.getNumPorts(), portInfo});
1060 problemIndices.push_back(problemIdx);
1061 }
1062 auto originalNumPorts = fmodule.getNumPorts();
1063 auto portIdx = fmodule.getNumPorts();
1064 fmodule.insertPorts(newPorts);
1065
1066 auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
1067 fmodule.getBodyBlock());
1068
1069 // Connect each port to the value stored in the connectionMap for this
1070 // wiring problem index.
1071 for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
1072 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1073 assert(src && "there did not exist a driver for the port");
1074 Value dest = fmodule.getArgument(portIdx++);
1075 if (failed(connect(src, dest, builder)))
1076 return failure();
1077 }
1078
1079 // If a U-turn exists, this is an LCA and we need a U-turn connection. These
1080 // are the last connections made for this module.
1081 for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
1082 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1083 assert(src && "there did not exist a connection for the u-turn");
1084 if (failed(connect(src, dest, builder)))
1085 return failure();
1086 }
1087
1088 // Update the connectionMap of all modules for which we created a port.
1089 for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
1090 InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
1091 auto enclosingModule = useInst->getParentOfType<FModuleOp>();
1092 auto clonedInst = useInst.cloneWithInsertedPortsAndReplaceUses(newPorts);
1093 state.instancePathCache.replaceInstance(useInst, clonedInst);
1094 useInst->erase();
1095 // Record information in the moduleModifications strucutre for the module
1096 // _where this is instantiated_. This is done so that when that module is
1097 // visited later, there will be information available for it to find ports
1098 // it needs to wire up. If there is already an existing connection, then
1099 // this is a U-turn.
1100 for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
1101 auto &modifications = moduleModifications[enclosingModule];
1102 auto newPort = clonedInst->getResult(newPortIdx + originalNumPorts);
1103 if (modifications.connectionMap.count(problemIdx)) {
1104 modifications.uturns.push_back({problemIdx, newPort});
1105 continue;
1106 }
1107 modifications.connectionMap[problemIdx] = newPort;
1108 }
1109 }
1110 }
1111
1112 // Delete unused WireOps created by producers of WiringProblems.
1113 for (auto *op : opsToErase)
1114 op->erase();
1115
1116 return success();
1117}
1118
1119// This is the main entrypoint for the lowering pass.
1120void LowerAnnotationsPass::runOnOperation() {
1122
1123 CircuitOp circuit = getOperation();
1124 SymbolTable modules(circuit);
1125
1126 // Grab the annotations from a non-standard attribute called "rawAnnotations".
1127 // This is a temporary location for all annotations that are earmarked for
1128 // processing by this pass as we migrate annotations from being handled by
1129 // FIRAnnotations/FIRParser into this pass. While we do this, this pass is
1130 // not supposed to touch _other_ annotations to enable this pass to be run
1131 // after FIRAnnotations/FIRParser.
1132 auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
1133 if (!annotations)
1134 return;
1135 circuit->removeAttr(rawAnnotations);
1136
1137 // Populate the worklist in reverse order. This has the effect of causing
1138 // annotations to be processed in the order in which they appear in the
1139 // original JSON.
1140 for (auto anno : llvm::reverse(annotations.getValue()))
1141 worklistAttrs.push_back(cast<DictionaryAttr>(anno));
1142
1143 size_t numFailures = 0;
1144 size_t numAdded = 0;
1145 auto addToWorklist = [&](DictionaryAttr anno) {
1146 ++numAdded;
1147 worklistAttrs.push_back(anno);
1148 };
1149 InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
1150 ApplyState state{circuit, modules, addToWorklist, instancePathCache,
1151 noRefTypePorts};
1152 LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
1153 while (!worklistAttrs.empty()) {
1154 auto attr = worklistAttrs.pop_back_val();
1155 if (applyAnnotation(attr, state).failed())
1156 ++numFailures;
1157 }
1158
1159 if (failed(legacyToWiringProblems(state)))
1160 ++numFailures;
1161
1162 if (failed(solveWiringProblems(state)))
1163 ++numFailures;
1164
1165 // Update statistics
1166 numRawAnnotations += annotations.size();
1167 numAddedAnnos += numAdded;
1168 numAnnos += numAdded + annotations.size();
1169 numReusedHierPathOps += state.numReusedHierPaths;
1170
1171 if (numFailures)
1172 signalPassFailure();
1173}
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:86
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.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs, bool warnOnTruncation=false)
Emit a connect between two values.
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.
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.