CIRCT  20.0.0git
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 
36 namespace circt {
37 namespace firrtl {
38 #define GEN_PASS_DEF_LOWERFIRRTLANNOTATIONS
39 #include "circt/Dialect/FIRRTL/Passes.h.inc"
40 } // namespace firrtl
41 } // namespace circt
42 
43 using namespace circt;
44 using namespace firrtl;
45 using namespace chirrtl;
46 
47 /// Get annotations or an empty set of annotations.
48 static 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.
55 static 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.
64 static 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.
73 static 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.
112 static 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
132 static 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.
144 static 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.
151 static 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).
170 std::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.
187 std::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.
233 LogicalResult drop(const AnnoPathValue &target, DictionaryAttr anno,
234  ApplyState &state) {
235  return success();
236 }
237 //===----------------------------------------------------------------------===//
238 // Customized Appliers
239 //===----------------------------------------------------------------------===//
240 
241 static 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.
265 static 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 
271 static 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 
314 static 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 
380 static 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 
416 static 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.
446 template <bool isInline>
447 static 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 
494 static 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
534 static 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 
576 namespace 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 
592 static 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 
691 LogicalResult
692 registerAnnotationRecord(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.
706 static 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 
720 namespace {
721 struct 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 
737 LogicalResult 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
777 LogicalResult 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.
802 LogicalResult 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(
1097  getFieldName(
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.
1214 void 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 const AnnoRecord * getAnnotationHandler(StringRef annoStr, bool ignoreAnnotationUnknown)
Lookup a record for a given annotation class.
static std::optional< AnnoPathValue > stdResolveImpl(StringRef rawPath, ApplyState &state)
Implementation of standard resolution.
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 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 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 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 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:85
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.
An instance path composed of a series of instances.
def connect(destination, source)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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.
Definition: FIRRTLUtils.h:222
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.
Definition: FIRRTLOps.cpp:212
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)
Definition: FIRRTLOps.cpp:301
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.
Definition: FIRRTLUtils.cpp:25
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".
unsigned addSVAttributes(mlir::Operation *op, llvm::ArrayRef< SVAttributeAttr > attrs)
Add a list of SV attributes to an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
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.
Operation * getOp() const
FModuleLike getModule() const
Get the parent module of the target.
State threaded through functions for resolving and applying annotations.
hw::InnerSymbolNamespace & getNamespace(FModuleLike module)
DenseMap< StringAttr, LegacyWiringProblem > legacyWiringProblems
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
FlatSymbolRefAttr getRefFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
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.