CIRCT  18.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 
15 #include "PassDetails.h"
29 #include "circt/Dialect/HW/HWOps.h"
31 #include "mlir/IR/Diagnostics.h"
32 #include "llvm/ADT/APSInt.h"
33 #include "llvm/ADT/PostOrderIterator.h"
34 #include "llvm/ADT/StringExtras.h"
35 #include "llvm/Support/Debug.h"
36 
37 #define DEBUG_TYPE "lower-annos"
38 
39 using namespace circt;
40 using namespace firrtl;
41 using namespace chirrtl;
42 
43 /// Get annotations or an empty set of annotations.
44 static ArrayAttr getAnnotationsFrom(Operation *op) {
45  if (auto annots = op->getAttrOfType<ArrayAttr>(getAnnotationAttrName()))
46  return annots;
47  return ArrayAttr::get(op->getContext(), {});
48 }
49 
50 /// Construct the annotation array with a new thing appended.
51 static ArrayAttr appendArrayAttr(ArrayAttr array, Attribute a) {
52  if (!array)
53  return ArrayAttr::get(a.getContext(), ArrayRef<Attribute>{a});
54  SmallVector<Attribute> old(array.begin(), array.end());
55  old.push_back(a);
56  return ArrayAttr::get(a.getContext(), old);
57 }
58 
59 /// Update an ArrayAttribute by replacing one entry.
60 static ArrayAttr replaceArrayAttrElement(ArrayAttr array, size_t elem,
61  Attribute newVal) {
62  SmallVector<Attribute> old(array.begin(), array.end());
63  old[elem] = newVal;
64  return ArrayAttr::get(array.getContext(), old);
65 }
66 
67 /// Apply a new annotation to a resolved target. This handles ports,
68 /// aggregates, modules, wires, etc.
69 static void addAnnotation(AnnoTarget ref, unsigned fieldIdx,
70  ArrayRef<NamedAttribute> anno) {
71  auto *context = ref.getOp()->getContext();
72  DictionaryAttr annotation;
73  if (fieldIdx) {
74  SmallVector<NamedAttribute> annoField(anno.begin(), anno.end());
75  annoField.emplace_back(
76  StringAttr::get(context, "circt.fieldID"),
77  IntegerAttr::get(IntegerType::get(context, 32, IntegerType::Signless),
78  fieldIdx));
79  annotation = DictionaryAttr::get(context, annoField);
80  } else {
81  annotation = DictionaryAttr::get(context, anno);
82  }
83 
84  if (ref.isa<OpAnnoTarget>()) {
85  auto newAnno = appendArrayAttr(getAnnotationsFrom(ref.getOp()), annotation);
86  ref.getOp()->setAttr(getAnnotationAttrName(), newAnno);
87  return;
88  }
89 
90  auto portRef = ref.cast<PortAnnoTarget>();
91  auto portAnnoRaw = ref.getOp()->getAttr(getPortAnnotationAttrName());
92  ArrayAttr portAnno = portAnnoRaw.dyn_cast_or_null<ArrayAttr>();
93  if (!portAnno || portAnno.size() != getNumPorts(ref.getOp())) {
94  SmallVector<Attribute> emptyPortAttr(
95  getNumPorts(ref.getOp()),
96  ArrayAttr::get(ref.getOp()->getContext(), {}));
97  portAnno = ArrayAttr::get(ref.getOp()->getContext(), emptyPortAttr);
98  }
99  portAnno = replaceArrayAttrElement(
100  portAnno, portRef.getPortNo(),
101  appendArrayAttr(dyn_cast<ArrayAttr>(portAnno[portRef.getPortNo()]),
102  annotation));
103  ref.getOp()->setAttr("portAnnotations", portAnno);
104 }
105 
106 /// Make an anchor for a non-local annotation. Use the expanded path to build
107 /// the module and name list in the anchor.
108 static FlatSymbolRefAttr buildNLA(const AnnoPathValue &target,
109  ApplyState &state) {
110  OpBuilder b(state.circuit.getBodyRegion());
111  SmallVector<Attribute> insts;
112  for (auto inst : target.instances) {
113  insts.push_back(OpAnnoTarget(inst).getNLAReference(
114  state.getNamespace(inst->getParentOfType<FModuleLike>())));
115  }
116 
117  insts.push_back(
118  FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr()));
119 
120  auto instAttr = ArrayAttr::get(state.circuit.getContext(), insts);
121  return state.hierPathCache.getRefFor(instAttr);
122 }
123 
124 /// Scatter breadcrumb annotations corresponding to non-local annotations
125 /// along the instance path. Returns symbol name used to anchor annotations to
126 /// path.
127 // FIXME: uniq annotation chain links
128 static FlatSymbolRefAttr scatterNonLocalPath(const AnnoPathValue &target,
129  ApplyState &state) {
130 
131  FlatSymbolRefAttr sym = buildNLA(target, state);
132  return sym;
133 }
134 
135 //===----------------------------------------------------------------------===//
136 // Standard Utility Resolvers
137 //===----------------------------------------------------------------------===//
138 
139 /// Always resolve to the circuit, ignoring the annotation.
140 static std::optional<AnnoPathValue> noResolve(DictionaryAttr anno,
141  ApplyState &state) {
142  return AnnoPathValue(state.circuit);
143 }
144 
145 /// Implementation of standard resolution. First parses the target path, then
146 /// resolves it.
147 static std::optional<AnnoPathValue> stdResolveImpl(StringRef rawPath,
148  ApplyState &state) {
149  auto pathStr = canonicalizeTarget(rawPath);
150  StringRef path{pathStr};
151 
152  auto tokens = tokenizePath(path);
153  if (!tokens) {
154  mlir::emitError(state.circuit.getLoc())
155  << "Cannot tokenize annotation path " << rawPath;
156  return {};
157  }
158 
159  return resolveEntities(*tokens, state.circuit, state.symTbl,
160  state.targetCaches);
161 }
162 
163 /// (SFC) FIRRTL SingleTargetAnnotation resolver. Uses the 'target' field of
164 /// the annotation with standard parsing to resolve the path. This requires
165 /// 'target' to exist and be normalized (per docs/FIRRTLAnnotations.md).
166 std::optional<AnnoPathValue> circt::firrtl::stdResolve(DictionaryAttr anno,
167  ApplyState &state) {
168  auto target = anno.getNamed("target");
169  if (!target) {
170  mlir::emitError(state.circuit.getLoc())
171  << "No target field in annotation " << anno;
172  return {};
173  }
174  if (!isa<StringAttr>(target->getValue())) {
175  mlir::emitError(state.circuit.getLoc())
176  << "Target field in annotation doesn't contain string " << anno;
177  return {};
178  }
179  return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(), state);
180 }
181 
182 /// Resolves with target, if it exists. If not, resolves to the circuit.
183 std::optional<AnnoPathValue> circt::firrtl::tryResolve(DictionaryAttr anno,
184  ApplyState &state) {
185  auto target = anno.getNamed("target");
186  if (target)
187  return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(),
188  state);
189  return AnnoPathValue(state.circuit);
190 }
191 
192 //===----------------------------------------------------------------------===//
193 // Standard Utility Appliers
194 //===----------------------------------------------------------------------===//
195 
196 /// An applier which puts the annotation on the target and drops the 'target'
197 /// field from the annotation. Optionally handles non-local annotations.
199 
200  DictionaryAttr anno,
201  ApplyState &state,
202  bool allowNonLocal) {
203  if (!allowNonLocal && !target.isLocal()) {
204  Annotation annotation(anno);
205  auto diag = mlir::emitError(target.ref.getOp()->getLoc())
206  << "is targeted by a non-local annotation \""
207  << annotation.getClass() << "\" with target "
208  << annotation.getMember("target")
209  << ", but this annotation cannot be non-local";
210  diag.attachNote() << "see current annotation: " << anno << "\n";
211  return failure();
212  }
213  SmallVector<NamedAttribute> newAnnoAttrs;
214  for (auto &na : anno) {
215  if (na.getName().getValue() != "target") {
216  newAnnoAttrs.push_back(na);
217  } else if (!target.isLocal()) {
218  auto sym = scatterNonLocalPath(target, state);
219  newAnnoAttrs.push_back(
220  {StringAttr::get(anno.getContext(), "circt.nonlocal"), sym});
221  }
222  }
223  addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
224  return success();
225 }
226 
227 /// Just drop the annotation. This is intended for Annotations which are known,
228 /// but can be safely ignored.
229 LogicalResult drop(const AnnoPathValue &target, DictionaryAttr anno,
230  ApplyState &state) {
231  return success();
232 }
233 //===----------------------------------------------------------------------===//
234 // Customized Appliers
235 //===----------------------------------------------------------------------===//
236 
237 static LogicalResult applyDUTAnno(const AnnoPathValue &target,
238  DictionaryAttr anno, ApplyState &state) {
239  auto *op = target.ref.getOp();
240  auto loc = op->getLoc();
241 
242  if (!target.isLocal())
243  return mlir::emitError(loc) << "must be local";
244 
245  if (!target.ref.isa<OpAnnoTarget>() || !isa<FModuleOp>(op))
246  return mlir::emitError(loc) << "can only target to a module";
247 
248  auto moduleOp = cast<FModuleOp>(op);
249 
250  // DUT has public visibility.
251  moduleOp.setPublic();
252  SmallVector<NamedAttribute> newAnnoAttrs;
253  for (auto &na : anno)
254  if (na.getName().getValue() != "target")
255  newAnnoAttrs.push_back(na);
256  addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
257  return success();
258 }
259 
260 // Like symbolizeConvention, but disallows the internal convention.
261 static std::optional<Convention> parseConvention(llvm::StringRef str) {
262  return ::llvm::StringSwitch<::std::optional<Convention>>(str)
263  .Case("scalarized", Convention::Scalarized)
264  .Default(std::nullopt);
265 }
266 
267 static LogicalResult applyConventionAnno(const AnnoPathValue &target,
268  DictionaryAttr anno,
269  ApplyState &state) {
270  auto *op = target.ref.getOp();
271  auto loc = op->getLoc();
272  auto error = [&]() {
273  auto diag = mlir::emitError(loc);
274  diag << "circuit.ConventionAnnotation ";
275  return diag;
276  };
277 
278  auto opTarget = target.ref.dyn_cast<OpAnnoTarget>();
279  if (!opTarget)
280  return error() << "must target a module object";
281 
282  if (!target.isLocal())
283  return error() << "must be local";
284 
285  auto conventionStrAttr =
286  tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
287  if (!conventionStrAttr)
288  return failure();
289 
290  auto conventionStr = conventionStrAttr.getValue();
291  auto conventionOpt = parseConvention(conventionStr);
292  if (!conventionOpt)
293  return error() << "unknown convention " << conventionStr;
294 
295  auto convention = *conventionOpt;
296 
297  if (auto moduleOp = dyn_cast<FModuleOp>(op)) {
298  moduleOp.setConvention(convention);
299  return success();
300  }
301 
302  if (auto extModuleOp = dyn_cast<FExtModuleOp>(op)) {
303  extModuleOp.setConvention(convention);
304  return success();
305  }
306 
307  return error() << "can only target to a module or extmodule";
308 }
309 
310 static LogicalResult applyAttributeAnnotation(const AnnoPathValue &target,
311  DictionaryAttr anno,
312  ApplyState &state) {
313  auto *op = target.ref.getOp();
314 
315  auto error = [&]() {
316  auto diag = mlir::emitError(op->getLoc());
317  diag << anno.getAs<StringAttr>("class").getValue() << " ";
318  return diag;
319  };
320 
321  if (!target.ref.isa<OpAnnoTarget>())
322  return error()
323  << "must target an operation. Currently ports are not supported";
324 
325  if (!target.isLocal())
326  return error() << "must be local";
327 
328  if (!isa<FModuleOp, WireOp, NodeOp, RegOp, RegResetOp>(op))
329  return error()
330  << "unhandled operation. The target must be a module, wire, node or "
331  "register";
332 
333  auto name = anno.getAs<StringAttr>("description");
334  auto svAttr = sv::SVAttributeAttr::get(name.getContext(), name);
335  sv::addSVAttributes(op, {svAttr});
336  return success();
337 }
338 
339 /// Update a memory op with attributes about memory file loading.
340 template <bool isInline>
341 static LogicalResult applyLoadMemoryAnno(const AnnoPathValue &target,
342  DictionaryAttr anno,
343  ApplyState &state) {
344  if (!target.isLocal()) {
345  mlir::emitError(state.circuit.getLoc())
346  << "has a " << anno.get("class")
347  << " annotation which is non-local, but this annotation is not allowed "
348  "to be non-local";
349  return failure();
350  }
351 
352  auto *op = target.ref.getOp();
353 
354  if (!target.isOpOfType<MemOp, CombMemOp, SeqMemOp>()) {
355  mlir::emitError(op->getLoc())
356  << "can only apply a load memory annotation to a memory";
357  return failure();
358  }
359 
360  // The two annotations have different case usage in "filename".
361  StringAttr filename = tryGetAs<StringAttr>(
362  anno, anno, isInline ? "filename" : "fileName", op->getLoc(),
363  anno.getAs<StringAttr>("class").getValue());
364  if (!filename)
365  return failure();
366 
367  auto hexOrBinary =
368  tryGetAs<StringAttr>(anno, anno, "hexOrBinary", op->getLoc(),
369  anno.getAs<StringAttr>("class").getValue());
370  if (!hexOrBinary)
371  return failure();
372 
373  auto hexOrBinaryValue = hexOrBinary.getValue();
374  if (hexOrBinaryValue != "h" && hexOrBinaryValue != "b") {
375  auto diag = mlir::emitError(op->getLoc())
376  << "has memory initialization annotation with invalid format, "
377  "'hexOrBinary' field must be either 'h' or 'b'";
378  diag.attachNote() << "the full annotation is: " << anno;
379  return failure();
380  }
381 
382  op->setAttr("init", MemoryInitAttr::get(op->getContext(), filename,
383  hexOrBinaryValue == "b", isInline));
384 
385  return success();
386 }
387 
388 //===----------------------------------------------------------------------===//
389 // Driving table
390 //===----------------------------------------------------------------------===//
391 
392 namespace circt::firrtl {
393 /// Resolution and application of a "firrtl.annotations.NoTargetAnnotation".
394 /// This should be used for any Annotation which does not apply to anything in
395 /// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically,
396 /// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or
397 /// its passes, e.g., to set the output directory or to turn on a pass.
398 /// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set
399 /// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to
400 /// cause the SFC to schedule a specified pass. Instead of leaving these
401 /// floating or attaching them to the top-level MLIR module (which is a purer
402 /// interpretation of "no target"), we choose to attach them to the Circuit even
403 /// they do not "apply" to the Circuit. This gives later passes a common place,
404 /// the Circuit, to search for these control Annotations.
406  applyWithoutTarget<false, CircuitOp>};
407 
408 static llvm::StringMap<AnnoRecord> annotationRecords{{
409 
410  // Testing Annotation
411  {"circt.test", {stdResolve, applyWithoutTarget<true>}},
412  {"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
413  {"circt.testNT", {noResolve, applyWithoutTarget<>}},
414  {"circt.missing", {tryResolve, applyWithoutTarget<true>}},
415  {"circt.Intrinsic", {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
416  // Grand Central Views/Interfaces Annotations
421  {companionAnnoClass, {stdResolve, applyWithoutTarget<>}},
422  {augmentedGroundTypeClass, {stdResolve, applyWithoutTarget<true>}},
423  // Grand Central Data Tap Annotations
425  {dataTapsBlackboxClass, {stdResolve, applyWithoutTarget<true>}},
426  {referenceKeySourceClass, {stdResolve, applyWithoutTarget<true>}},
427  {referenceKeyPortClass, {stdResolve, applyWithoutTarget<true>}},
428  {internalKeySourceClass, {stdResolve, applyWithoutTarget<true>}},
429  {internalKeyPortClass, {stdResolve, applyWithoutTarget<true>}},
430  {deletedKeyClass, {stdResolve, applyWithoutTarget<true>}},
431  {literalKeyClass, {stdResolve, applyWithoutTarget<true>}},
432  // Grand Central Mem Tap Annotations
434  {memTapSourceClass, {stdResolve, applyWithoutTarget<true>}},
435  {memTapPortClass, {stdResolve, applyWithoutTarget<true>}},
436  {memTapBlackboxClass, {stdResolve, applyWithoutTarget<true>}},
437  // OMIR Annotations
439  {omirTrackerAnnoClass, {stdResolve, applyWithoutTarget<true>}},
441  // Miscellaneous Annotations
444  {stdResolve, applyWithoutTarget<true, true, WireOp, NodeOp, RegOp,
445  RegResetOp, InstanceOp, MemOp, CombMemOp,
446  MemoryPortOp, SeqMemOp>}},
448  {stdResolve,
449  applyWithoutTarget<true, FModuleOp, FExtModuleOp, InstanceOp>}},
455  {stdResolve, applyWithoutTarget<true, MemOp, CombMemOp>}},
461  {stdResolve, applyWithoutTarget<true, FModuleOp, FExtModuleOp>}},
462  {flattenAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
463  {inlineAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
465  {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
467  {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
469  {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
471  {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
473  {stdResolve, applyWithoutTarget<false, FModuleOp>}},
475  {stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
480  {stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
492  {dftTestModeEnableAnnoClass, {stdResolve, applyWithoutTarget<true>}},
493  {dftClockDividerBypassAnnoClass, {stdResolve, applyWithoutTarget<true>}},
499  {extractBlackBoxAnnoClass, {stdResolve, applyWithoutTarget<false>}},
500  {fullAsyncResetAnnoClass, {stdResolve, applyWithoutTarget<true>}},
502  {stdResolve, applyWithoutTarget<true, FModuleOp>}},
506  {traceAnnoClass, {stdResolve, applyWithoutTarget<true>}},
507  {loadMemoryFromFileAnnoClass, {stdResolve, applyLoadMemoryAnno<false>}},
509  {stdResolve, applyLoadMemoryAnno<true>}},
513 
514 LogicalResult
515 registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord,
516  const std::function<void(llvm::Twine)> &errorHandler) {
517 
518  if (annotationRecords.insert({annoClass, annoRecord}).second)
519  return LogicalResult::success();
520  if (errorHandler)
521  errorHandler("annotation record '" + annoClass + "' is registered twice\n");
522  return LogicalResult::failure();
523 }
524 
525 } // namespace circt::firrtl
526 
527 /// Lookup a record for a given annotation class. Optionally, returns the
528 /// record for "circuit.missing" if the record doesn't exist.
529 static const AnnoRecord *getAnnotationHandler(StringRef annoStr,
530  bool ignoreAnnotationUnknown) {
531  auto ii = annotationRecords.find(annoStr);
532  if (ii != annotationRecords.end())
533  return &ii->second;
534  if (ignoreAnnotationUnknown)
535  return &annotationRecords.find("circt.missing")->second;
536  return nullptr;
537 }
538 
539 //===----------------------------------------------------------------------===//
540 // Pass Infrastructure
541 //===----------------------------------------------------------------------===//
542 
543 namespace {
544 struct LowerAnnotationsPass
545  : public LowerFIRRTLAnnotationsBase<LowerAnnotationsPass> {
546  void runOnOperation() override;
547  LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
548  LogicalResult legacyToWiringProblems(ApplyState &state);
549  LogicalResult solveWiringProblems(ApplyState &state);
550 
551  using LowerFIRRTLAnnotationsBase::ignoreAnnotationClassless;
552  using LowerFIRRTLAnnotationsBase::ignoreAnnotationUnknown;
553  using LowerFIRRTLAnnotationsBase::noRefTypePorts;
554  SmallVector<DictionaryAttr> worklistAttrs;
555 };
556 } // end anonymous namespace
557 
558 LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
559  ApplyState &state) {
560  LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
561 
562  // Lookup the class
563  StringRef annoClassVal;
564  if (auto annoClass = anno.getNamed("class"))
565  annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
566  else if (ignoreAnnotationClassless)
567  annoClassVal = "circt.missing";
568  else
569  return mlir::emitError(state.circuit.getLoc())
570  << "Annotation without a class: " << anno;
571 
572  // See if we handle the class
573  auto *record = getAnnotationHandler(annoClassVal, false);
574  if (!record) {
575  ++numUnhandled;
576  if (!ignoreAnnotationUnknown)
577  return mlir::emitError(state.circuit.getLoc())
578  << "Unhandled annotation: " << anno;
579 
580  // Try again, requesting the fallback handler.
581  record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
582  assert(record);
583  }
584 
585  // Try to apply the annotation
586  auto target = record->resolver(anno, state);
587  if (!target)
588  return mlir::emitError(state.circuit.getLoc())
589  << "Unable to resolve target of annotation: " << anno;
590  if (record->applier(*target, anno, state).failed())
591  return mlir::emitError(state.circuit.getLoc())
592  << "Unable to apply annotation: " << anno;
593  return success();
594 }
595 
596 /// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
597 /// using the pin attribute as newNameHint
598 LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
599  for (const auto &[name, problem] : state.legacyWiringProblems) {
600  if (!problem.source)
601  return mlir::emitError(state.circuit.getLoc())
602  << "Unable to resolve source for pin: " << name;
603 
604  if (problem.sinks.empty())
605  return mlir::emitError(state.circuit.getLoc())
606  << "Unable to resolve sink(s) for pin: " << name;
607 
608  for (const auto &sink : problem.sinks) {
609  state.wiringProblems.push_back(
610  {problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
611  }
612  }
613  return success();
614 }
615 
616 /// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
617 /// Wiring Problem is a mapping from a source to a sink that can be connected
618 /// via a base Type or RefType as requested. This uses a two-step approach.
619 /// First, all Wiring Problems are analyzed to compute pending modifications to
620 /// modules. Second, modules are visited from leaves to roots to apply module
621 /// modifications. Module modifications include addings ports and connecting
622 /// things up.
623 LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
624  // Utility function to extract the defining module from a value which may be
625  // either a BlockArgument or an Operation result.
626  auto getModule = [](Value value) {
627  if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
628  return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
629  return value.getDefiningOp()->getParentOfType<FModuleLike>();
630  };
631 
632  // Utility function to determine where to insert connection operations.
633  auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
634  // Check for easy case: both are in the same block.
635  if (src.getParentBlock() == dest.getParentBlock())
636  return src.getParentBlock();
637 
638  // If connecting across blocks, figure out where to connect.
639  (void)getModule;
640  assert(getModule(src) == getModule(dest));
641  // Helper to determine if 'a' is available at 'b's block.
642  auto safelyDoms = [&](Value a, Value b) {
643  if (isa<BlockArgument>(a))
644  return true;
645  if (isa<BlockArgument>(b))
646  return false;
647  // Handle cases where 'b' is in child op after 'a'.
648  auto *ancestor =
649  a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
650  return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
651  };
652  if (safelyDoms(src, dest))
653  return dest.getParentBlock();
654  if (safelyDoms(dest, src))
655  return src.getParentBlock();
656  return {};
657  };
658 
659  auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
660  auto op =
661  dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
662  if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
663  op.getResultTypes()[0] == op.getOperandTypes()[0])
664  return op;
665  return {};
666  };
667 
668  // Utility function to connect a destination to a source. Always use a
669  // ConnectOp as the widths may be uninferred.
670  SmallVector<Operation *> opsToErase;
671  auto connect = [&](Value src, Value dest,
672  ImplicitLocOpBuilder &builder) -> LogicalResult {
673  // Strip away noop unrealized_conversion_cast's, used as placeholders.
674  // In the future, these should be created/managed as part of creating WP's.
675  if (auto op = getNoopCast(dest)) {
676  dest = op.getOperand(0);
677  opsToErase.push_back(op);
678  std::swap(src, dest);
679  } else if (auto op = getNoopCast(src)) {
680  src = op.getOperand(0);
681  opsToErase.push_back(op);
682  }
683 
684  if (foldFlow(dest) == Flow::Source)
685  std::swap(src, dest);
686 
687  // Figure out where to insert operations.
688  auto *insertBlock = findInsertionBlock(src, dest);
689  if (!insertBlock)
690  return emitError(src.getLoc())
691  .append("This value is involved with a Wiring Problem where the "
692  "destination is in the same module but neither dominates the "
693  "other, which is not supported.")
694  .attachNote(dest.getLoc())
695  .append("The destination is here.");
696 
697  // Insert at end, past invalidation in same block.
698  builder.setInsertionPointToEnd(insertBlock);
699 
700  // Create RefSend/RefResolve if necessary.
701  if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
702  if (type_isa<RefType>(dest.getType()))
703  src = builder.create<RefSendOp>(src);
704  else
705  src = builder.create<RefResolveOp>(src);
706  }
707 
708  // If the sink is a wire with no users, then convert this to a node.
709  auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
710  if (destOp && dest.getUses().empty()) {
711  builder.create<NodeOp>(src, destOp.getName())
712  .setAnnotationsAttr(destOp.getAnnotations());
713  opsToErase.push_back(destOp);
714  return success();
715  }
716 
717  // Otherwise, just connect to the source.
718  emitConnect(builder, dest, src);
719 
720  return success();
721  };
722 
723  auto &instanceGraph = state.instancePathCache.instanceGraph;
724  auto *context = state.circuit.getContext();
725 
726  // Examine all discovered Wiring Problems to determine modifications that need
727  // to be made per-module.
728  LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
729  DenseMap<FModuleLike, ModuleModifications> moduleModifications;
730  DenseSet<Value> visitedSinks;
731  for (auto e : llvm::enumerate(state.wiringProblems)) {
732  auto index = e.index();
733  auto problem = e.value();
734  // This is a unique index that is assigned to this specific wiring problem
735  // and is used as a key during wiring to know which Values (ports, sources,
736  // or sinks) should be connected.
737  auto source = problem.source;
738  auto sink = problem.sink;
739 
740  // Check that no WiringProblems are trying to use the same sink. This
741  // should never happen.
742  if (!visitedSinks.insert(sink).second) {
743  auto diag = mlir::emitError(source.getLoc())
744  << "This sink is involved with a Wiring Problem which is "
745  "targeted by a source used by another Wiring Problem. "
746  "(This is both illegal and should be impossible.)";
747  diag.attachNote(source.getLoc()) << "The source is here";
748  return failure();
749  }
750  FModuleLike sourceModule = getModule(source);
751  FModuleLike sinkModule = getModule(sink);
752  if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
753  auto diag = mlir::emitError(source.getLoc())
754  << "This source is involved with a Wiring Problem which "
755  "includes an External Module port and External Module "
756  "ports anre not supported.";
757  diag.attachNote(sink.getLoc()) << "The sink is here.";
758  return failure();
759  }
760 
761  LLVM_DEBUG({
762  llvm::dbgs() << " - index: " << index << "\n"
763  << " source:\n"
764  << " module: " << sourceModule.getModuleName() << "\n"
765  << " value: " << source << "\n"
766  << " sink:\n"
767  << " module: " << sinkModule.getModuleName() << "\n"
768  << " value: " << sink << "\n"
769  << " newNameHint: " << problem.newNameHint << "\n";
770  });
771 
772  // If the source and sink are in the same block, just wire them up.
773  if (sink.getParentBlock() == source.getParentBlock()) {
774  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
775  sink.getParentBlock());
776  if (failed(connect(source, sink, builder)))
777  return failure();
778  continue;
779  }
780  // If both are in the same module but not same block, U-turn.
781  // We may not be able to handle this, but that is checked below while
782  // connecting.
783  if (sourceModule == sinkModule) {
784  LLVM_DEBUG(llvm::dbgs()
785  << " LCA: " << sourceModule.getModuleName() << "\n");
786  moduleModifications[sourceModule].connectionMap[index] = source;
787  moduleModifications[sourceModule].uturns.push_back({index, sink});
788  continue;
789  }
790 
791  // Otherwise, get instance paths for source/sink, and compute LCA.
792  auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
793  auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
794 
795  if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
796  auto diag =
797  mlir::emitError(source.getLoc())
798  << "This source is involved with a Wiring Problem where the source "
799  "or the sink are multiply instantiated and this is not supported.";
800  diag.attachNote(sink.getLoc()) << "The sink is here.";
801  return failure();
802  }
803 
804  FModuleOp lca =
805  cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
806  auto sources = sourcePaths[0];
807  auto sinks = sinkPaths[0];
808  while (!sources.empty() && !sinks.empty()) {
809  if (sources.top() != sinks.top())
810  break;
811  auto newLCA = sources.top();
812  lca = cast<FModuleOp>(instanceGraph.getReferencedModule(newLCA));
813  sources = sources.dropFront();
814  sinks = sinks.dropFront();
815  }
816 
817  LLVM_DEBUG({
818  llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
819  << " sourcePaths:\n";
820  for (auto inst : sourcePaths[0])
821  llvm::dbgs() << " - " << inst.getInstanceName() << " of "
822  << inst.getReferencedModuleName() << "\n";
823  llvm::dbgs() << " sinkPaths:\n";
824  for (auto inst : sinkPaths[0])
825  llvm::dbgs() << " - " << inst.getInstanceName() << " of "
826  << inst.getReferencedModuleName() << "\n";
827  });
828 
829  // Pre-populate the connectionMap of the module with the source and sink.
830  moduleModifications[sourceModule].connectionMap[index] = source;
831  moduleModifications[sinkModule].connectionMap[index] = sink;
832 
833  // Record port types that should be added to each module along the LCA path.
834  Type sourceType, sinkType;
835  auto useRefTypes =
836  !noRefTypePorts &&
837  problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
838  if (useRefTypes) {
839  // Use RefType ports if possible
840  RefType refType = TypeSwitch<Type, RefType>(source.getType())
841  .Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
842  return RefType::get(base.getPassiveType());
843  })
844  .Case<RefType>([](RefType ref) { return ref; });
845  sourceType = refType;
846  sinkType = refType.getType();
847  } else {
848  // Use specified port types.
849  sourceType = source.getType();
850  sinkType = sink.getType();
851 
852  // Types must be connectable, which means FIRRTLType's.
853  auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
854  auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
855  if (!sourceFType)
856  return emitError(source.getLoc())
857  << "Wiring Problem source type \"" << sourceType
858  << "\" must be a FIRRTL type";
859  if (!sinkFType)
860  return emitError(sink.getLoc())
861  << "Wiring Problem sink type \"" << sinkType
862  << "\" must be a FIRRTL type";
863 
864  // Otherwise they must be identical or FIRRTL type-equivalent
865  // (connectable).
866  if (sourceFType != sinkFType &&
867  !areTypesEquivalent(sourceFType, sinkFType)) {
868  auto diag = mlir::emitError(source.getLoc())
869  << "Wiring Problem source type " << sourceType
870  << " does not match sink type " << sinkType;
871  diag.attachNote(sink.getLoc()) << "The sink is here.";
872  return failure();
873  }
874  }
875  // If wiring using references, check that the sink value we connect to is
876  // passive.
877  if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
878  sinkFType && type_isa<RefType>(sourceType) &&
879  !getBaseType(sinkFType).isPassive())
880  return emitError(sink.getLoc())
881  << "Wiring Problem sink type \"" << sink.getType()
882  << "\" must be passive (no flips) when using references";
883 
884  // Record module modifications related to adding ports to modules.
885  auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
886  Direction dir) {
887  StringRef name, instName;
888  for (auto inst : llvm::reverse(insts)) {
889  auto mod = instanceGraph.getReferencedModule<FModuleOp>(inst);
890  if (name.empty()) {
891  if (problem.newNameHint.empty())
892  name = state.getNamespace(mod).newName(
893  getFieldName(
894  getFieldRefFromValue(val, /*lookThroughCasts=*/true),
895  /*nameSafe=*/true)
896  .first +
897  "__bore");
898  else
899  name = state.getNamespace(mod).newName(problem.newNameHint);
900  } else {
901  assert(!instName.empty());
902  name = state.getNamespace(mod).newName(instName + "_" + name);
903  }
904  moduleModifications[mod].portsToAdd.push_back(
905  {index, {StringAttr::get(context, name), tpe, dir}});
906  instName = inst.getInstanceName();
907  }
908  };
909 
910  // Record the addition of ports.
911  addPorts(sources, source, sourceType, Direction::Out);
912  addPorts(sinks, sink, sinkType, Direction::In);
913  }
914 
915  // Iterate over modules from leaves to roots, applying ModuleModifications to
916  // each module.
917  LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
918  for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
919  auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
920  // Skip external modules and modules that have no modifications.
921  if (!fmodule || !moduleModifications.count(fmodule))
922  continue;
923 
924  auto modifications = moduleModifications[fmodule];
925  LLVM_DEBUG({
926  llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
927  llvm::dbgs() << " ports:\n";
928  for (auto [index, port] : modifications.portsToAdd) {
929  llvm::dbgs() << " - name: " << port.getName() << "\n"
930  << " id: " << index << "\n"
931  << " type: " << port.type << "\n"
932  << " direction: "
933  << (port.direction == Direction::In ? "in" : "out")
934  << "\n";
935  }
936  });
937 
938  // Add ports to the module after all other existing ports.
939  SmallVector<std::pair<unsigned, PortInfo>> newPorts;
940  SmallVector<unsigned> problemIndices;
941  for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
942  // Create the port.
943  newPorts.push_back({fmodule.getNumPorts(), portInfo});
944  problemIndices.push_back(problemIdx);
945  }
946  auto originalNumPorts = fmodule.getNumPorts();
947  auto portIdx = fmodule.getNumPorts();
948  fmodule.insertPorts(newPorts);
949 
950  auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
951  fmodule.getBodyBlock());
952 
953  // Connect each port to the value stored in the connectionMap for this
954  // wiring problem index.
955  for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
956  Value src = moduleModifications[fmodule].connectionMap[problemIdx];
957  assert(src && "there did not exist a driver for the port");
958  Value dest = fmodule.getArgument(portIdx++);
959  if (failed(connect(src, dest, builder)))
960  return failure();
961  }
962 
963  // If a U-turn exists, this is an LCA and we need a U-turn connection. These
964  // are the last connections made for this module.
965  for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
966  Value src = moduleModifications[fmodule].connectionMap[problemIdx];
967  assert(src && "there did not exist a connection for the u-turn");
968  if (failed(connect(src, dest, builder)))
969  return failure();
970  }
971 
972  // Update the connectionMap of all modules for which we created a port.
973  for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
974  InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
975  auto enclosingModule = useInst->getParentOfType<FModuleOp>();
976  auto clonedInst = useInst.cloneAndInsertPorts(newPorts);
977  state.instancePathCache.replaceInstance(useInst, clonedInst);
978  // When RAUW-ing, ignore the new ports that we added when replacing (they
979  // cannot have uses).
980  useInst->replaceAllUsesWith(
981  clonedInst.getResults().drop_back(newPorts.size()));
982  useInst->erase();
983  // Record information in the moduleModifications strucutre for the module
984  // _where this is instantiated_. This is done so that when that module is
985  // visited later, there will be information available for it to find ports
986  // it needs to wire up. If there is already an existing connection, then
987  // this is a U-turn.
988  for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
989  auto &modifications = moduleModifications[enclosingModule];
990  auto newPort = clonedInst.getResult(newPortIdx + originalNumPorts);
991  if (modifications.connectionMap.count(problemIdx)) {
992  modifications.uturns.push_back({problemIdx, newPort});
993  continue;
994  }
995  modifications.connectionMap[problemIdx] = newPort;
996  }
997  }
998  }
999 
1000  // Delete unused WireOps created by producers of WiringProblems.
1001  for (auto *op : opsToErase)
1002  op->erase();
1003 
1004  return success();
1005 }
1006 
1007 // This is the main entrypoint for the lowering pass.
1008 void LowerAnnotationsPass::runOnOperation() {
1009  CircuitOp circuit = getOperation();
1010  SymbolTable modules(circuit);
1011 
1012  LLVM_DEBUG(llvm::dbgs() << "===- Running LowerAnnotations Pass "
1013  "------------------------------------------===\n");
1014 
1015  // Grab the annotations from a non-standard attribute called "rawAnnotations".
1016  // This is a temporary location for all annotations that are earmarked for
1017  // processing by this pass as we migrate annotations from being handled by
1018  // FIRAnnotations/FIRParser into this pass. While we do this, this pass is
1019  // not supposed to touch _other_ annotations to enable this pass to be run
1020  // after FIRAnnotations/FIRParser.
1021  auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
1022  if (!annotations)
1023  return;
1024  circuit->removeAttr(rawAnnotations);
1025 
1026  // Populate the worklist in reverse order. This has the effect of causing
1027  // annotations to be processed in the order in which they appear in the
1028  // original JSON.
1029  for (auto anno : llvm::reverse(annotations.getValue()))
1030  worklistAttrs.push_back(cast<DictionaryAttr>(anno));
1031 
1032  size_t numFailures = 0;
1033  size_t numAdded = 0;
1034  auto addToWorklist = [&](DictionaryAttr anno) {
1035  ++numAdded;
1036  worklistAttrs.push_back(anno);
1037  };
1038  InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
1039  ApplyState state{circuit, modules, addToWorklist, instancePathCache,
1040  noRefTypePorts};
1041  LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
1042  while (!worklistAttrs.empty()) {
1043  auto attr = worklistAttrs.pop_back_val();
1044  if (applyAnnotation(attr, state).failed())
1045  ++numFailures;
1046  }
1047 
1048  if (failed(legacyToWiringProblems(state)))
1049  ++numFailures;
1050 
1051  if (failed(solveWiringProblems(state)))
1052  ++numFailures;
1053 
1054  // Update statistics
1055  numRawAnnotations += annotations.size();
1056  numAddedAnnos += numAdded;
1057  numAnnos += numAdded + annotations.size();
1058  numReusedHierPathOps += state.numReusedHierPaths;
1059 
1060  if (numFailures)
1061  signalPassFailure();
1062 }
1063 
1064 /// This is the pass constructor.
1065 std::unique_ptr<mlir::Pass>
1067  bool ignoreAnnotationClassless,
1068  bool noRefTypePorts) {
1069  auto pass = std::make_unique<LowerAnnotationsPass>();
1070  pass->ignoreAnnotationUnknown = ignoreAnnotationUnknown;
1071  pass->ignoreAnnotationClassless = ignoreAnnotationClassless;
1072  pass->noRefTypePorts = noRefTypePorts;
1073  return pass;
1074 }
lowerAnnotationsNoRefTypePorts FirtoolPreserveValuesMode value
Definition: Firtool.cpp:95
assert(baseType &&"element must be base type")
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 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 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.
Builder builder
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:63
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.
An instance path composed of a series of instances.
def connect(destination, source)
Definition: support.py:37
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
LogicalResult applyOMIR(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Main entry point to handle scattering of an OMIRAnnotation.
Definition: EmitOMIR.cpp:489
constexpr const char * injectDUTHierarchyAnnoClass
constexpr const char * dftTestModeEnableAnnoClass
constexpr const char * extractCoverageAnnoClass
constexpr const char * excludeMemToRegAnnoClass
constexpr const char * dftClockDividerBypassAnnoClass
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:217
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 * sitestBlackBoxAnnoClass
constexpr const char * convertMemToRegOfVecAnnoClass
constexpr const char * dontObfuscateModuleAnnoClass
constexpr const char * metadataDirectoryAttrName
constexpr const char * extractBlackBoxAnnoClass
constexpr const char * fullAsyncResetAnnoClass
Annotation that marks a reset (port or wire) and domain.
constexpr const char * testBenchDirAnnoClass
constexpr const char * sitestTestHarnessBlackBoxAnnoClass
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:180
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:271
constexpr const char * dataTapsClass
constexpr const char * conventionAnnoClass
constexpr const char * dedupGroupAnnoClass
constexpr const char * omirAnnoClass
constexpr const char * omirTrackerAnnoClass
constexpr const char * viewAnnoClass
constexpr const char * serializedViewAnnoClass
constexpr const char * enumDefAnnoClass
constexpr const char * enumVecAnnoClass
constexpr const char * omirFileAnnoClass
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 * subCircuitsTargetDirectoryAnnoClass
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 * prefixModulesAnnoClass
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 * subCircuitDirAnnotation
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.
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
constexpr const char * prefixInterfacesAnnoClass
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::unique_ptr< mlir::Pass > createLowerFIRRTLAnnotationsPass(bool ignoreUnhandledAnnotations=false, bool ignoreClasslessAnnotations=false, bool noRefTypePorts=false)
This is the pass constructor.
std::optional< AnnoPathValue > tryResolve(DictionaryAttr anno, ApplyState &state)
Resolves with target, if it exists. If not, resolves to the circuit.
constexpr const char * dontTouchAnnoClass
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:23
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.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
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.
This represents an annotation targeting a specific port of a module, memory, or instance.
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.