CIRCT 21.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 "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>}},
633 {stdResolve, applyWithoutTarget<true, MemOp, CombMemOp>}},
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 void runOnOperation() override;
725 LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
726 LogicalResult legacyToWiringProblems(ApplyState &state);
727 LogicalResult solveWiringProblems(ApplyState &state);
728
729 using LowerFIRRTLAnnotationsBase::allowAddingPortsOnPublic;
730 using LowerFIRRTLAnnotationsBase::ignoreAnnotationClassless;
731 using LowerFIRRTLAnnotationsBase::ignoreAnnotationUnknown;
732 using LowerFIRRTLAnnotationsBase::noRefTypePorts;
733 SmallVector<DictionaryAttr> worklistAttrs;
734};
735} // end anonymous namespace
736
737LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
738 ApplyState &state) {
739 LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
740
741 // Lookup the class
742 StringRef annoClassVal;
743 if (auto annoClass = anno.getNamed("class"))
744 annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
745 else if (ignoreAnnotationClassless)
746 annoClassVal = "circt.missing";
747 else
748 return mlir::emitError(state.circuit.getLoc())
749 << "Annotation without a class: " << anno;
750
751 // See if we handle the class
752 auto *record = getAnnotationHandler(annoClassVal, false);
753 if (!record) {
754 ++numUnhandled;
755 if (!ignoreAnnotationUnknown)
756 return mlir::emitError(state.circuit.getLoc())
757 << "Unhandled annotation: " << anno;
758
759 // Try again, requesting the fallback handler.
760 record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
761 assert(record);
762 }
763
764 // Try to apply the annotation
765 auto target = record->resolver(anno, state);
766 if (!target)
767 return mlir::emitError(state.circuit.getLoc())
768 << "Unable to resolve target of annotation: " << anno;
769 if (record->applier(*target, anno, state).failed())
770 return mlir::emitError(state.circuit.getLoc())
771 << "Unable to apply annotation: " << anno;
772 return success();
773}
774
775/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
776/// using the pin attribute as newNameHint
777LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
778 for (const auto &[name, problem] : state.legacyWiringProblems) {
779 if (!problem.source)
780 return mlir::emitError(state.circuit.getLoc())
781 << "Unable to resolve source for pin: " << name;
782
783 if (problem.sinks.empty())
784 return mlir::emitError(state.circuit.getLoc())
785 << "Unable to resolve sink(s) for pin: " << name;
786
787 for (const auto &sink : problem.sinks) {
788 state.wiringProblems.push_back(
789 {problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
790 }
791 }
792 return success();
793}
794
795/// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
796/// Wiring Problem is a mapping from a source to a sink that can be connected
797/// via a base Type or RefType as requested. This uses a two-step approach.
798/// First, all Wiring Problems are analyzed to compute pending modifications to
799/// modules. Second, modules are visited from leaves to roots to apply module
800/// modifications. Module modifications include addings ports and connecting
801/// things up.
802LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
803 // Utility function to extract the defining module from a value which may be
804 // either a BlockArgument or an Operation result.
805 auto getModule = [](Value value) {
806 if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
807 return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
808 return value.getDefiningOp()->getParentOfType<FModuleLike>();
809 };
810
811 // Utility function to determine where to insert connection operations.
812 auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
813 // Check for easy case: both are in the same block.
814 if (src.getParentBlock() == dest.getParentBlock())
815 return src.getParentBlock();
816
817 // If connecting across blocks, figure out where to connect.
818 (void)getModule;
819 assert(getModule(src) == getModule(dest));
820 // Helper to determine if 'a' is available at 'b's block.
821 auto safelyDoms = [&](Value a, Value b) {
822 if (isa<BlockArgument>(a))
823 return true;
824 if (isa<BlockArgument>(b))
825 return false;
826 // Handle cases where 'b' is in child op after 'a'.
827 auto *ancestor =
828 a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
829 return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
830 };
831 if (safelyDoms(src, dest))
832 return dest.getParentBlock();
833 if (safelyDoms(dest, src))
834 return src.getParentBlock();
835 return {};
836 };
837
838 auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
839 auto op =
840 dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
841 if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
842 op.getResultTypes()[0] == op.getOperandTypes()[0])
843 return op;
844 return {};
845 };
846
847 // Utility function to connect a destination to a source. Always use a
848 // ConnectOp as the widths may be uninferred.
849 SmallVector<Operation *> opsToErase;
850 auto connect = [&](Value src, Value dest,
851 ImplicitLocOpBuilder &builder) -> LogicalResult {
852 // Strip away noop unrealized_conversion_cast's, used as placeholders.
853 // In the future, these should be created/managed as part of creating WP's.
854 if (auto op = getNoopCast(dest)) {
855 dest = op.getOperand(0);
856 opsToErase.push_back(op);
857 std::swap(src, dest);
858 } else if (auto op = getNoopCast(src)) {
859 src = op.getOperand(0);
860 opsToErase.push_back(op);
861 }
862
863 if (foldFlow(dest) == Flow::Source)
864 std::swap(src, dest);
865
866 // Figure out where to insert operations.
867 auto *insertBlock = findInsertionBlock(src, dest);
868 if (!insertBlock)
869 return emitError(src.getLoc())
870 .append("This value is involved with a Wiring Problem where the "
871 "destination is in the same module but neither dominates the "
872 "other, which is not supported.")
873 .attachNote(dest.getLoc())
874 .append("The destination is here.");
875
876 // Insert at end, past invalidation in same block.
877 builder.setInsertionPointToEnd(insertBlock);
878
879 // Create RefSend/RefResolve if necessary.
880 if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
881 if (type_isa<RefType>(dest.getType()))
882 src = builder.create<RefSendOp>(src);
883 else
884 src = builder.create<RefResolveOp>(src);
885 }
886
887 // If the sink is a wire with no users, then convert this to a node.
888 // This is done to convert the undriven wires created for GCView's
889 // into the NodeOp's they're required to be in GrandCentral.cpp.
890 if (auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
891 destOp && dest.getUses().empty()) {
892 // Only perform this if the type is suitable (passive).
893 if (auto baseType = dyn_cast<FIRRTLBaseType>(src.getType());
894 baseType && baseType.isPassive()) {
895 // Note that the wire is replaced with the source type
896 // regardless, continue this behavior.
897 builder.create<NodeOp>(src, destOp.getName())
898 .setAnnotationsAttr(destOp.getAnnotations());
899 opsToErase.push_back(destOp);
900 return success();
901 }
902 }
903
904 // Otherwise, just connect to the source.
905 emitConnect(builder, dest, src);
906
907 return success();
908 };
909
910 auto &instanceGraph = state.instancePathCache.instanceGraph;
911 auto *context = state.circuit.getContext();
912
913 // Examine all discovered Wiring Problems to determine modifications that need
914 // to be made per-module.
915 LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
916 DenseMap<FModuleLike, ModuleModifications> moduleModifications;
917 DenseSet<Value> visitedSinks;
918 for (auto e : llvm::enumerate(state.wiringProblems)) {
919 auto index = e.index();
920 auto problem = e.value();
921 // This is a unique index that is assigned to this specific wiring problem
922 // and is used as a key during wiring to know which Values (ports, sources,
923 // or sinks) should be connected.
924 auto source = problem.source;
925 auto sink = problem.sink;
926
927 // Check that no WiringProblems are trying to use the same sink. This
928 // should never happen.
929 if (!visitedSinks.insert(sink).second) {
930 auto diag = mlir::emitError(source.getLoc())
931 << "This sink is involved with a Wiring Problem which is "
932 "targeted by a source used by another Wiring Problem. "
933 "(This is both illegal and should be impossible.)";
934 diag.attachNote(source.getLoc()) << "The source is here";
935 return failure();
936 }
937 FModuleLike sourceModule = getModule(source);
938 FModuleLike sinkModule = getModule(sink);
939 if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
940 auto diag = mlir::emitError(source.getLoc())
941 << "This source is involved with a Wiring Problem which "
942 "includes an External Module port and External Module "
943 "ports anre not supported.";
944 diag.attachNote(sink.getLoc()) << "The sink is here.";
945 return failure();
946 }
947
948 LLVM_DEBUG({
949 llvm::dbgs() << " - index: " << index << "\n"
950 << " source:\n"
951 << " module: " << sourceModule.getModuleName() << "\n"
952 << " value: " << source << "\n"
953 << " sink:\n"
954 << " module: " << sinkModule.getModuleName() << "\n"
955 << " value: " << sink << "\n"
956 << " newNameHint: " << problem.newNameHint << "\n";
957 });
958
959 // If the source and sink are in the same block, just wire them up.
960 if (sink.getParentBlock() == source.getParentBlock()) {
961 auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
962 sink.getParentBlock());
963 if (failed(connect(source, sink, builder)))
964 return failure();
965 continue;
966 }
967 // If both are in the same module but not same block, U-turn.
968 // We may not be able to handle this, but that is checked below while
969 // connecting.
970 if (sourceModule == sinkModule) {
971 LLVM_DEBUG(llvm::dbgs()
972 << " LCA: " << sourceModule.getModuleName() << "\n");
973 moduleModifications[sourceModule].connectionMap[index] = source;
974 moduleModifications[sourceModule].uturns.push_back({index, sink});
975 continue;
976 }
977
978 // Otherwise, get instance paths for source/sink, and compute LCA.
979 auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
980 auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
981
982 if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
983 auto diag =
984 mlir::emitError(source.getLoc())
985 << "This source is involved with a Wiring Problem where the source "
986 "or the sink are multiply instantiated and this is not supported.";
987 diag.attachNote(sink.getLoc()) << "The sink is here.";
988 return failure();
989 }
990
991 FModuleOp lca =
992 cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
993 auto sources = sourcePaths[0];
994 auto sinks = sinkPaths[0];
995 while (!sources.empty() && !sinks.empty()) {
996 if (sources.top() != sinks.top())
997 break;
998 auto newLCA = cast<InstanceOp>(*sources.top());
999 lca = cast<FModuleOp>(newLCA.getReferencedModule(instanceGraph));
1000 sources = sources.dropFront();
1001 sinks = sinks.dropFront();
1002 }
1003
1004 LLVM_DEBUG({
1005 llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
1006 << " sourcePath: " << sourcePaths[0] << "\n"
1007 << " sinkPaths: " << sinkPaths[0] << "\n";
1008 });
1009
1010 // Pre-populate the connectionMap of the module with the source and sink.
1011 moduleModifications[sourceModule].connectionMap[index] = source;
1012 moduleModifications[sinkModule].connectionMap[index] = sink;
1013
1014 // Record port types that should be added to each module along the LCA path.
1015 Type sourceType, sinkType;
1016 auto useRefTypes =
1017 !noRefTypePorts &&
1018 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
1019 if (useRefTypes) {
1020 // Use RefType ports if possible
1021 RefType refType = TypeSwitch<Type, RefType>(source.getType())
1022 .Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
1023 return RefType::get(base.getPassiveType());
1024 })
1025 .Case<RefType>([](RefType ref) { return ref; });
1026 sourceType = refType;
1027 sinkType = refType.getType();
1028 } else {
1029 // Use specified port types.
1030 sourceType = source.getType();
1031 sinkType = sink.getType();
1032
1033 // Types must be connectable, which means FIRRTLType's.
1034 auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
1035 auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
1036 if (!sourceFType)
1037 return emitError(source.getLoc())
1038 << "Wiring Problem source type \"" << sourceType
1039 << "\" must be a FIRRTL type";
1040 if (!sinkFType)
1041 return emitError(sink.getLoc())
1042 << "Wiring Problem sink type \"" << sinkType
1043 << "\" must be a FIRRTL type";
1044
1045 // Otherwise they must be identical or FIRRTL type-equivalent
1046 // (connectable).
1047 if (sourceFType != sinkFType &&
1048 !areTypesEquivalent(sinkFType, sourceFType)) {
1049 // Support tapping mixed alignment -> passive , emulate probe behavior.
1050 if (auto sourceBaseType = dyn_cast<FIRRTLBaseType>(sourceFType);
1051 problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer &&
1052 sourceBaseType &&
1053 areTypesEquivalent(sinkFType, sourceBaseType.getPassiveType())) {
1054 // Change "sourceType" to the passive version that's type-equivalent,
1055 // this will be used for wiring on the "up" side.
1056 // This relies on `emitConnect` supporting connecting to the passive
1057 // version from the original source.
1058 sourceType = sourceBaseType.getPassiveType();
1059 } else {
1060 auto diag = mlir::emitError(source.getLoc())
1061 << "Wiring Problem source type " << sourceType
1062 << " does not match sink type " << sinkType;
1063 diag.attachNote(sink.getLoc()) << "The sink is here.";
1064 return failure();
1065 }
1066 }
1067 }
1068 // If wiring using references, check that the sink value we connect to is
1069 // passive.
1070 if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
1071 sinkFType && type_isa<RefType>(sourceType) &&
1072 !getBaseType(sinkFType).isPassive())
1073 return emitError(sink.getLoc())
1074 << "Wiring Problem sink type \"" << sink.getType()
1075 << "\" must be passive (no flips) when using references";
1076
1077 // Record module modifications related to adding ports to modules.
1078 auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
1079 Direction dir) -> LogicalResult {
1080 StringRef name, instName;
1081 for (auto instNode : llvm::reverse(insts)) {
1082 auto inst = cast<InstanceOp>(*instNode);
1083 auto mod = inst.getReferencedModule<FModuleOp>(instanceGraph);
1084 if (mod.isPublic()) {
1085 if (!allowAddingPortsOnPublic) {
1086 auto diag = emitError(
1087 mod.getLoc(), "cannot wire port through this public module");
1088 diag.attachNote(source.getLoc()) << "source here";
1089 diag.attachNote(sink.getLoc()) << "sink here";
1090 return diag;
1091 }
1092 ++numPublicPortsWired;
1093 }
1094 if (name.empty()) {
1095 if (problem.newNameHint.empty())
1096 name = state.getNamespace(mod).newName(
1098 getFieldRefFromValue(val, /*lookThroughCasts=*/true),
1099 /*nameSafe=*/true)
1100 .first +
1101 "__bore");
1102 else
1103 name = state.getNamespace(mod).newName(problem.newNameHint);
1104 } else {
1105 assert(!instName.empty());
1106 name = state.getNamespace(mod).newName(instName + "_" + name);
1107 }
1108 moduleModifications[mod].portsToAdd.push_back(
1109 {index, {StringAttr::get(context, name), tpe, dir}});
1110 instName = inst.getInstanceName();
1111 }
1112 return success();
1113 };
1114
1115 // Record the addition of ports.
1116 if (failed(addPorts(sources, source, sourceType, Direction::Out)) ||
1117 failed(addPorts(sinks, sink, sinkType, Direction::In)))
1118 return failure();
1119 }
1120
1121 // Iterate over modules from leaves to roots, applying ModuleModifications to
1122 // each module.
1123 LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
1124 for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
1125 auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
1126 // Skip external modules and modules that have no modifications.
1127 if (!fmodule || !moduleModifications.count(fmodule))
1128 continue;
1129
1130 auto modifications = moduleModifications[fmodule];
1131 LLVM_DEBUG({
1132 llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
1133 llvm::dbgs() << " ports:\n";
1134 for (auto [index, port] : modifications.portsToAdd) {
1135 llvm::dbgs() << " - name: " << port.getName() << "\n"
1136 << " id: " << index << "\n"
1137 << " type: " << port.type << "\n"
1138 << " direction: "
1139 << (port.direction == Direction::In ? "in" : "out")
1140 << "\n";
1141 }
1142 });
1143
1144 // Add ports to the module after all other existing ports.
1145 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
1146 SmallVector<unsigned> problemIndices;
1147 for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
1148 // Create the port.
1149 newPorts.push_back({fmodule.getNumPorts(), portInfo});
1150 problemIndices.push_back(problemIdx);
1151 }
1152 auto originalNumPorts = fmodule.getNumPorts();
1153 auto portIdx = fmodule.getNumPorts();
1154 fmodule.insertPorts(newPorts);
1155
1156 auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
1157 fmodule.getBodyBlock());
1158
1159 // Connect each port to the value stored in the connectionMap for this
1160 // wiring problem index.
1161 for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
1162 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1163 assert(src && "there did not exist a driver for the port");
1164 Value dest = fmodule.getArgument(portIdx++);
1165 if (failed(connect(src, dest, builder)))
1166 return failure();
1167 }
1168
1169 // If a U-turn exists, this is an LCA and we need a U-turn connection. These
1170 // are the last connections made for this module.
1171 for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
1172 Value src = moduleModifications[fmodule].connectionMap[problemIdx];
1173 assert(src && "there did not exist a connection for the u-turn");
1174 if (failed(connect(src, dest, builder)))
1175 return failure();
1176 }
1177
1178 // Update the connectionMap of all modules for which we created a port.
1179 for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
1180 InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
1181 auto enclosingModule = useInst->getParentOfType<FModuleOp>();
1182 auto clonedInst = useInst.cloneAndInsertPorts(newPorts);
1183 state.instancePathCache.replaceInstance(useInst, clonedInst);
1184 // When RAUW-ing, ignore the new ports that we added when replacing (they
1185 // cannot have uses).
1186 useInst->replaceAllUsesWith(
1187 clonedInst.getResults().drop_back(newPorts.size()));
1188 useInst->erase();
1189 // Record information in the moduleModifications strucutre for the module
1190 // _where this is instantiated_. This is done so that when that module is
1191 // visited later, there will be information available for it to find ports
1192 // it needs to wire up. If there is already an existing connection, then
1193 // this is a U-turn.
1194 for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
1195 auto &modifications = moduleModifications[enclosingModule];
1196 auto newPort = clonedInst.getResult(newPortIdx + originalNumPorts);
1197 if (modifications.connectionMap.count(problemIdx)) {
1198 modifications.uturns.push_back({problemIdx, newPort});
1199 continue;
1200 }
1201 modifications.connectionMap[problemIdx] = newPort;
1202 }
1203 }
1204 }
1205
1206 // Delete unused WireOps created by producers of WiringProblems.
1207 for (auto *op : opsToErase)
1208 op->erase();
1209
1210 return success();
1211}
1212
1213// This is the main entrypoint for the lowering pass.
1214void LowerAnnotationsPass::runOnOperation() {
1215 CircuitOp circuit = getOperation();
1216 SymbolTable modules(circuit);
1217
1218 LLVM_DEBUG(debugPassHeader(this) << "\n");
1219
1220 // Grab the annotations from a non-standard attribute called "rawAnnotations".
1221 // This is a temporary location for all annotations that are earmarked for
1222 // processing by this pass as we migrate annotations from being handled by
1223 // FIRAnnotations/FIRParser into this pass. While we do this, this pass is
1224 // not supposed to touch _other_ annotations to enable this pass to be run
1225 // after FIRAnnotations/FIRParser.
1226 auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
1227 if (!annotations)
1228 return;
1229 circuit->removeAttr(rawAnnotations);
1230
1231 // Populate the worklist in reverse order. This has the effect of causing
1232 // annotations to be processed in the order in which they appear in the
1233 // original JSON.
1234 for (auto anno : llvm::reverse(annotations.getValue()))
1235 worklistAttrs.push_back(cast<DictionaryAttr>(anno));
1236
1237 size_t numFailures = 0;
1238 size_t numAdded = 0;
1239 auto addToWorklist = [&](DictionaryAttr anno) {
1240 ++numAdded;
1241 worklistAttrs.push_back(anno);
1242 };
1243 InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
1244 ApplyState state{circuit, modules, addToWorklist, instancePathCache,
1245 noRefTypePorts};
1246 LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
1247 while (!worklistAttrs.empty()) {
1248 auto attr = worklistAttrs.pop_back_val();
1249 if (applyAnnotation(attr, state).failed())
1250 ++numFailures;
1251 }
1252
1253 if (failed(legacyToWiringProblems(state)))
1254 ++numFailures;
1255
1256 if (failed(solveWiringProblems(state)))
1257 ++numFailures;
1258
1259 // Update statistics
1260 numRawAnnotations += annotations.size();
1261 numAddedAnnos += numAdded;
1262 numAnnos += numAdded + annotations.size();
1263 numReusedHierPathOps += state.numReusedHierPaths;
1264
1265 if (numFailures)
1266 signalPassFailure();
1267}
1268
1269/// This is the pass constructor.
1271 bool ignoreAnnotationUnknown, bool ignoreAnnotationClassless,
1272 bool noRefTypePorts, bool allowAddingPortsOnPublic) {
1273 auto pass = std::make_unique<LowerAnnotationsPass>();
1274 pass->ignoreAnnotationUnknown = ignoreAnnotationUnknown;
1275 pass->ignoreAnnotationClassless = ignoreAnnotationClassless;
1276 pass->noRefTypePorts = noRefTypePorts;
1277 pass->allowAddingPortsOnPublic = allowAddingPortsOnPublic;
1278 return pass;
1279}
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 * excludeMemToRegAnnoClass
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
std::unique_ptr< mlir::Pass > createLowerFIRRTLAnnotationsPass(bool ignoreUnhandledAnnotations=false, bool ignoreClasslessAnnotations=false, bool noRefTypePorts=false, bool allowAddingPortsOnPublic=false)
This is the pass constructor.
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 * 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 absolute 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.