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