CIRCT  19.0.0git
GrandCentral.cpp
Go to the documentation of this file.
1 //===- GrandCentral.cpp - Ingest black box sources --------------*- 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 // Implement SiFive's Grand Central transform. Currently, this supports
9 // SystemVerilog Interface generation.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
23 #include "circt/Dialect/HW/HWOps.h"
25 #include "circt/Dialect/SV/SVOps.h"
26 #include "circt/Support/Debug.h"
27 #include "mlir/IR/ImplicitLocOpBuilder.h"
28 #include "llvm/ADT/DepthFirstIterator.h"
29 #include "llvm/ADT/TypeSwitch.h"
30 #include "llvm/Support/Debug.h"
31 #include "llvm/Support/YAMLTraits.h"
32 #include <variant>
33 
34 #define DEBUG_TYPE "gct"
35 
36 using namespace circt;
37 using namespace firrtl;
38 
39 //===----------------------------------------------------------------------===//
40 // Collateral for generating a YAML representation of a SystemVerilog interface
41 //===----------------------------------------------------------------------===//
42 
43 namespace {
44 
45 // These are used to provide hard-errors if a user tries to use the YAML
46 // infrastructure improperly. We only implement conversion to YAML and not
47 // conversion from YAML. The LLVM YAML infrastructure doesn't provide the
48 // ability to differentitate this and we don't need it for the purposes of
49 // Grand Central.
50 [[maybe_unused]] static std::string noDefault(StringRef clazz) {
51  return ("default '" + clazz +
52  "' construction is an intentionally *NOT* implemented "
53  "YAML feature (you should never be using this)")
54  .str();
55 }
56 
57 [[maybe_unused]] static std::string deNorm(StringRef clazz) {
58  return ("conversion from YAML to a '" + clazz +
59  "' is intentionally *NOT* implemented (you should not be "
60  "converting from YAML to an interface)")
61  .str();
62 }
63 
64 // This namespace provides YAML-related collateral that is specific to Grand
65 // Central and should not be placed in the `llvm::yaml` namespace.
66 namespace yaml {
67 
68 /// Context information necessary for YAML generation.
69 struct Context {
70  /// A symbol table consisting of _only_ the interfaces construted by the Grand
71  /// Central pass. This is not a symbol table because we do not have an
72  /// up-to-date symbol table that includes interfaces at the time the Grand
73  /// Central pass finishes. This structure is easier to build up and is only
74  /// the information we need.
75  DenseMap<Attribute, sv::InterfaceOp> &interfaceMap;
76 };
77 
78 /// A representation of an `sv::InterfaceSignalOp` that includes additional
79 /// description information.
80 ///
81 /// TODO: This could be removed if we add `firrtl.DocStringAnnotation` support
82 /// or if FIRRTL dialect included support for ops to specify "comment"
83 /// information.
84 struct DescribedSignal {
85  /// The comment associated with this signal.
86  StringAttr description;
87 
88  /// The signal.
89  sv::InterfaceSignalOp signal;
90 };
91 
92 /// This exist to work around the fact that no interface can be instantiated
93 /// inside another interface. This serves to represent an op like this for the
94 /// purposes of conversion to YAML.
95 ///
96 /// TODO: Fix this once we have a solution for #1464.
97 struct DescribedInstance {
98  StringAttr name;
99 
100  /// A comment associated with the interface instance.
101  StringAttr description;
102 
103  /// The dimensionality of the interface instantiation.
104  ArrayAttr dimensions;
105 
106  /// The symbol associated with the interface.
107  FlatSymbolRefAttr interface;
108 };
109 
110 } // namespace yaml
111 } // namespace
112 
113 // These macros tell the YAML infrastructure that these are types which can
114 // show up in vectors and provides implementations of how to serialize these.
115 // Each of these macros puts the resulting class into the `llvm::yaml` namespace
116 // (which is why these are outside the `llvm::yaml` namespace below).
117 LLVM_YAML_IS_SEQUENCE_VECTOR(::yaml::DescribedSignal)
118 LLVM_YAML_IS_SEQUENCE_VECTOR(::yaml::DescribedInstance)
119 LLVM_YAML_IS_SEQUENCE_VECTOR(sv::InterfaceOp)
120 
121 // This `llvm::yaml` namespace contains implementations of classes that enable
122 // conversion from an `sv::InterfaceOp` to a YAML representation of that
123 // interface using [LLVM's YAML I/O library](https://llvm.org/docs/YamlIO.html).
124 namespace llvm {
125 namespace yaml {
126 
127 using namespace ::yaml;
128 
129 /// Convert newlines and comments to remove the comments. This produces better
130 /// looking YAML output. E.g., this will convert the following:
131 ///
132 /// // foo
133 /// // bar
134 ///
135 /// Into the following:
136 ///
137 /// foo
138 /// bar
139 std::string static stripComment(StringRef str) {
140  std::string descriptionString;
141  llvm::raw_string_ostream stream(descriptionString);
142  SmallVector<StringRef> splits;
143  str.split(splits, "\n");
144  llvm::interleave(
145  splits,
146  [&](auto substr) {
147  substr.consume_front("//");
148  stream << substr.drop_while([](auto c) { return c == ' '; });
149  },
150  [&]() { stream << "\n"; });
151  return descriptionString;
152 }
153 
154 /// Conversion from a `DescribedSignal` to YAML. This is
155 /// implemented using YAML normalization to first convert this to an internal
156 /// `Field` structure which has a one-to-one mapping to the YAML represntation.
157 template <>
158 struct MappingContextTraits<DescribedSignal, Context> {
159  /// A one-to-one representation with a YAML representation of a signal/field.
160  struct Field {
161  /// The name of the field.
162  StringRef name;
163 
164  /// An optional, textual description of what the field is.
165  std::optional<std::string> description;
166 
167  /// The dimensions of the field.
168  SmallVector<unsigned, 2> dimensions;
169 
170  /// The width of the underlying type.
171  unsigned width;
172 
173  /// Construct a `Field` from a `DescribedSignal` (an `sv::InterfaceSignalOp`
174  /// with an optional description).
175  Field(IO &io, DescribedSignal &op)
176  : name(op.signal.getSymNameAttr().getValue()) {
177 
178  // Convert the description from a `StringAttr` (which may be null) to an
179  // `optional<StringRef>`. This aligns exactly with the YAML
180  // representation.
181  if (op.description)
182  description = stripComment(op.description.getValue());
183 
184  // Unwrap the type of the field into an array of dimensions and a width.
185  // By example, this is going from the following hardware type:
186  //
187  // !hw.uarray<1xuarray<2xuarray<3xi8>>>
188  //
189  // To the following representation:
190  //
191  // dimensions: [ 3, 2, 1 ]
192  // width: 8
193  //
194  // Note that the above is equivalenet to the following Verilog
195  // specification.
196  //
197  // wire [7:0] foo [2:0][1:0][0:0]
198  //
199  // Do this by repeatedly unwrapping unpacked array types until you get to
200  // the underlying type. The dimensions need to be reversed as this
201  // unwrapping happens in reverse order of the final representation.
202  auto tpe = op.signal.getType();
203  while (auto vector = tpe.dyn_cast<hw::UnpackedArrayType>()) {
204  dimensions.push_back(vector.getNumElements());
205  tpe = vector.getElementType();
206  }
207  dimensions = SmallVector<unsigned>(llvm::reverse(dimensions));
208 
209  // The final non-array type must be an integer. Leave this as an assert
210  // with a blind cast because we generated this type in this pass (and we
211  // therefore cannot fail this cast).
212  assert(isa<IntegerType>(tpe));
213  width = type_cast<IntegerType>(tpe).getWidth();
214  }
215 
216  /// A no-argument constructor is necessary to work with LLVM's YAML library.
217  Field(IO &io) { llvm_unreachable(noDefault("Field").c_str()); }
218 
219  /// This cannot be denomralized back to an interface op.
220  DescribedSignal denormalize(IO &) {
221  llvm_unreachable(deNorm("DescribedSignal").c_str());
222  }
223  };
224 
225  static void mapping(IO &io, DescribedSignal &op, Context &ctx) {
226  MappingNormalization<Field, DescribedSignal> keys(io, op);
227  io.mapRequired("name", keys->name);
228  io.mapOptional("description", keys->description);
229  io.mapRequired("dimensions", keys->dimensions);
230  io.mapRequired("width", keys->width);
231  }
232 };
233 
234 /// Conversion from a `DescribedInstance` to YAML. This is implemented using
235 /// YAML normalization to first convert the `DescribedInstance` to an internal
236 /// `Instance` struct which has a one-to-one representation with the final YAML
237 /// representation.
238 template <>
239 struct MappingContextTraits<DescribedInstance, Context> {
240  /// A YAML-serializable representation of an interface instantiation.
241  struct Instance {
242  /// The name of the interface.
243  StringRef name;
244 
245  /// An optional textual description of the interface.
246  std::optional<std::string> description = std::nullopt;
247 
248  /// An array describing the dimnensionality of the interface.
249  SmallVector<int64_t, 2> dimensions;
250 
251  /// The underlying interface.
252  FlatSymbolRefAttr interface;
253 
254  Instance(IO &io, DescribedInstance &op)
255  : name(op.name.getValue()), interface(op.interface) {
256 
257  // Convert the description from a `StringAttr` (which may be null) to an
258  // `optional<StringRef>`. This aligns exactly with the YAML
259  // representation.
260  if (op.description)
261  description = stripComment(op.description.getValue());
262 
263  for (auto &d : op.dimensions) {
264  auto dimension = dyn_cast<IntegerAttr>(d);
265  dimensions.push_back(dimension.getInt());
266  }
267  }
268 
269  Instance(IO &io) { llvm_unreachable(noDefault("Instance").c_str()); }
270 
271  DescribedInstance denormalize(IO &) {
272  llvm_unreachable(deNorm("DescribedInstance").c_str());
273  }
274  };
275 
276  static void mapping(IO &io, DescribedInstance &op, Context &ctx) {
277  MappingNormalization<Instance, DescribedInstance> keys(io, op);
278  io.mapRequired("name", keys->name);
279  io.mapOptional("description", keys->description);
280  io.mapRequired("dimensions", keys->dimensions);
281  io.mapRequired("interface", ctx.interfaceMap[keys->interface], ctx);
282  }
283 };
284 
285 /// Conversion from an `sv::InterfaceOp` to YAML. This is implemented using
286 /// YAML normalization to first convert the interface to an internal `Interface`
287 /// which reformats the Grand Central-generated interface into the YAML format.
288 template <>
289 struct MappingContextTraits<sv::InterfaceOp, Context> {
290  /// A YAML-serializable representation of an interface. This consists of
291  /// fields (vector or ground types) and nested interfaces.
292  struct Interface {
293  /// The name of the interface.
294  StringRef name;
295 
296  /// All ground or vectors that make up the interface.
297  std::vector<DescribedSignal> fields;
298 
299  /// Instantiations of _other_ interfaces.
300  std::vector<DescribedInstance> instances;
301 
302  /// Construct an `Interface` from an `sv::InterfaceOp`. This is tuned to
303  /// "parse" the structure of an interface that the Grand Central pass
304  /// generates. The structure of `Field`s and `Instance`s is documented
305  /// below.
306  ///
307  /// A field will look like the following. The verbatim description is
308  /// optional:
309  ///
310  /// sv.verbatim "// <description>" {
311  /// firrtl.grandcentral.yaml.type = "description",
312  /// symbols = []}
313  /// sv.interface.signal @<name> : <type>
314  ///
315  /// An interface instanctiation will look like the following. The verbatim
316  /// description is optional.
317  ///
318  /// sv.verbatim "// <description>" {
319  /// firrtl.grandcentral.type = "description",
320  /// symbols = []}
321  /// sv.verbatim "<name> <symbol>();" {
322  /// firrtl.grandcentral.yaml.name = "<name>",
323  /// firrtl.grandcentral.yaml.dimensions = [<first dimension>, ...],
324  /// firrtl.grandcentral.yaml.symbol = @<symbol>,
325  /// firrtl.grandcentral.yaml.type = "instance",
326  /// symbols = []}
327  ///
328  Interface(IO &io, sv::InterfaceOp &op) : name(op.getName()) {
329  // A mutable store of the description. This occurs in the op _before_ the
330  // field or instance, so we need someplace to put it until we use it.
331  StringAttr description = {};
332 
333  for (auto &op : op.getBodyBlock()->getOperations()) {
334  TypeSwitch<Operation *>(&op)
335  // A verbatim op is either a description or an interface
336  // instantiation.
337  .Case<sv::VerbatimOp>([&](sv::VerbatimOp op) {
338  auto tpe = op->getAttrOfType<StringAttr>(
339  "firrtl.grandcentral.yaml.type");
340 
341  // This is a descripton. Update the mutable description and
342  // continue;
343  if (tpe.getValue() == "description") {
344  description = op.getFormatStringAttr();
345  return;
346  }
347 
348  // This is an unsupported construct. Just drop it.
349  if (tpe.getValue() == "unsupported") {
350  description = {};
351  return;
352  }
353 
354  // This is an instance of another interface. Add the symbol to
355  // the vector of instances.
356  auto name = op->getAttrOfType<StringAttr>(
357  "firrtl.grandcentral.yaml.name");
358  auto dimensions = op->getAttrOfType<ArrayAttr>(
359  "firrtl.grandcentral.yaml.dimensions");
360  auto symbol = op->getAttrOfType<FlatSymbolRefAttr>(
361  "firrtl.grandcentral.yaml.symbol");
362  instances.push_back(
363  DescribedInstance({name, description, dimensions, symbol}));
364  description = {};
365  })
366  // An interface signal op is a field.
367  .Case<sv::InterfaceSignalOp>([&](sv::InterfaceSignalOp op) {
368  fields.push_back(DescribedSignal({description, op}));
369  description = {};
370  });
371  }
372  }
373 
374  /// A no-argument constructor is necessary to work with LLVM's YAML library.
375  Interface(IO &io) { llvm_unreachable(noDefault("Interface").c_str()); }
376 
377  /// This cannot be denomralized back to an interface op.
378  sv::InterfaceOp denormalize(IO &) {
379  llvm_unreachable(deNorm("sv::InterfaceOp").c_str());
380  }
381  };
382 
383  static void mapping(IO &io, sv::InterfaceOp &op, Context &ctx) {
384  MappingNormalization<Interface, sv::InterfaceOp> keys(io, op);
385  io.mapRequired("name", keys->name);
386  io.mapRequired("fields", keys->fields, ctx);
387  io.mapRequired("instances", keys->instances, ctx);
388  }
389 };
390 
391 } // namespace yaml
392 } // namespace llvm
393 
394 //===----------------------------------------------------------------------===//
395 // Pass Implementation
396 //===----------------------------------------------------------------------===//
397 
398 namespace {
399 
400 /// A helper to build verbatim strings with symbol placeholders. Provides a
401 /// mechanism to snapshot the current string and symbols and restore back to
402 /// this state after modifications. These snapshots are particularly useful when
403 /// the string is assembled through hierarchical traversal of some sort, which
404 /// populates the string with a prefix common to all children of a hierarchy
405 /// (like the interface field traversal in the `GrandCentralPass`).
406 ///
407 /// The intended use is as follows:
408 ///
409 /// void baz(VerbatimBuilder &v) {
410 /// foo(v.snapshot().append("bar"));
411 /// }
412 ///
413 /// The function `baz` takes a snapshot of the current verbatim text `v`, adds
414 /// "bar" to it and calls `foo` with that appended verbatim text. After the call
415 /// to `foo` returns, any changes made by `foo` as well as the "bar" are dropped
416 /// from the verbatim text `v`, as the temporary snapshot goes out of scope.
417 struct VerbatimBuilder {
418  struct Base {
419  SmallString<128> string;
420  SmallVector<Attribute> symbols;
421  VerbatimBuilder builder() { return VerbatimBuilder(*this); }
422  operator VerbatimBuilder() { return builder(); }
423  };
424 
425  /// Constructing a builder will snapshot the `Base` which holds the actual
426  /// string and symbols.
427  VerbatimBuilder(Base &base)
428  : base(base), stringBaseSize(base.string.size()),
429  symbolsBaseSize(base.symbols.size()) {}
430 
431  /// Destroying a builder will reset the `Base` to the original string and
432  /// symbols.
433  ~VerbatimBuilder() {
434  base.string.resize(stringBaseSize);
435  base.symbols.resize(symbolsBaseSize);
436  }
437 
438  // Disallow copying.
439  VerbatimBuilder(const VerbatimBuilder &) = delete;
440  VerbatimBuilder &operator=(const VerbatimBuilder &) = delete;
441 
442  /// Take a snapshot of the current string and symbols. This returns a new
443  /// `VerbatimBuilder` that will reset to the current state of the string once
444  /// destroyed.
445  VerbatimBuilder snapshot() { return VerbatimBuilder(base); }
446 
447  /// Get the current string.
448  StringRef getString() const { return base.string; }
449  /// Get the current symbols;
450  ArrayRef<Attribute> getSymbols() const { return base.symbols; }
451 
452  /// Append to the string.
453  VerbatimBuilder &append(char c) {
454  base.string.push_back(c);
455  return *this;
456  }
457 
458  /// Append to the string.
459  VerbatimBuilder &append(const Twine &twine) {
460  twine.toVector(base.string);
461  return *this;
462  }
463 
464  /// Append a placeholder and symbol to the string.
465  VerbatimBuilder &append(Attribute symbol) {
466  unsigned id = base.symbols.size();
467  base.symbols.push_back(symbol);
468  append("{{" + Twine(id) + "}}");
469  return *this;
470  }
471 
472  VerbatimBuilder &operator+=(char c) { return append(c); }
473  VerbatimBuilder &operator+=(const Twine &twine) { return append(twine); }
474  VerbatimBuilder &operator+=(Attribute symbol) { return append(symbol); }
475 
476 private:
477  Base &base;
478  size_t stringBaseSize;
479  size_t symbolsBaseSize;
480 };
481 
482 /// A wrapper around a string that is used to encode a type which cannot be
483 /// represented by an mlir::Type for some reason. This is currently used to
484 /// represent either an interface, a n-dimensional vector of interfaces, or a
485 /// tombstone for an actually unsupported type (e.g., an AugmentedBooleanType).
486 struct VerbatimType {
487  /// The textual representation of the type.
488  std::string str;
489 
490  /// True if this is a type which must be "instatiated" and requires a trailing
491  /// "()".
492  bool instantiation;
493 
494  /// A vector storing the width of each dimension of the type.
495  SmallVector<int32_t, 4> dimensions = {};
496 
497  /// Serialize this type to a string.
498  std::string toStr(StringRef name) {
499  SmallString<64> stringType(str);
500  stringType.append(" ");
501  stringType.append(name);
502  for (auto d : llvm::reverse(dimensions)) {
503  stringType.append("[");
504  stringType.append(Twine(d).str());
505  stringType.append("]");
506  }
507  if (instantiation)
508  stringType.append("()");
509  stringType.append(";");
510  return std::string(stringType);
511  }
512 };
513 
514 /// A sum type representing either a type encoded as a string (VerbatimType)
515 /// or an actual mlir::Type.
516 typedef std::variant<VerbatimType, Type> TypeSum;
517 
518 /// Stores the information content of an ExtractGrandCentralAnnotation.
519 struct ExtractionInfo {
520  /// The directory where Grand Central generated collateral (modules,
521  /// interfaces, etc.) will be written.
522  StringAttr directory = {};
523 
524  /// The name of the file where any binds will be written. This will be placed
525  /// in the same output area as normal compilation output, e.g., output
526  /// Verilog. This has no relation to the `directory` member.
527  StringAttr bindFilename = {};
528 };
529 
530 /// Stores information about the companion module of a GrandCentral view.
531 struct CompanionInfo {
532  StringRef name;
533 
534  FModuleOp companion;
535  bool isNonlocal;
536 };
537 
538 /// Stores a reference to a ground type and an optional NLA associated with
539 /// that field.
540 struct FieldAndNLA {
541  FieldRef field;
542  FlatSymbolRefAttr nlaSym;
543 };
544 
545 /// Stores the arguments required to construct the verbatim xmr assignment.
546 struct VerbatimXMRbuilder {
547  Value val;
548  StringAttr str;
549  ArrayAttr syms;
550  FModuleOp companionMod;
551  VerbatimXMRbuilder(Value val, StringAttr str, ArrayAttr syms,
552  FModuleOp companionMod)
553  : val(val), str(str), syms(syms), companionMod(companionMod) {}
554 };
555 
556 /// Stores the arguments required to construct the InterfaceOps and
557 /// InterfaceSignalOps.
558 struct InterfaceElemsBuilder {
559  StringAttr iFaceName;
560  IntegerAttr id;
561  struct Properties {
562  StringAttr description;
563  StringAttr elemName;
564  TypeSum elemType;
565  Properties(StringAttr des, StringAttr name, TypeSum &elemType)
566  : description(des), elemName(name), elemType(elemType) {}
567  };
568  SmallVector<Properties> elementsList;
569  InterfaceElemsBuilder(StringAttr iFaceName, IntegerAttr id)
570  : iFaceName(iFaceName), id(id) {}
571 };
572 
573 /// Generate SystemVerilog interfaces from Grand Central annotations. This pass
574 /// roughly works in the following three phases:
575 ///
576 /// 1. Extraction information is determined.
577 ///
578 /// 2. The circuit is walked to find all scattered annotations related to Grand
579 /// Central interfaces. These are: (a) the companion module and (b) all
580 /// leaves that are to be connected to the interface.
581 ///
582 /// 3. The circuit-level Grand Central annotation is walked to both generate and
583 /// instantiate interfaces and to generate the "mappings" file that produces
584 /// cross-module references (XMRs) to drive the interface.
585 struct GrandCentralPass : public GrandCentralBase<GrandCentralPass> {
586  using GrandCentralBase::companionMode;
587 
588  void runOnOperation() override;
589 
590 private:
591  /// Optionally build an AugmentedType from an attribute. Return none if the
592  /// attribute is not a dictionary or if it does not match any of the known
593  /// templates for AugmentedTypes.
594  std::optional<Attribute> fromAttr(Attribute attr);
595 
596  /// Mapping of ID to leaf ground type and an optional non-local annotation
597  /// associated with that ID.
598  DenseMap<Attribute, FieldAndNLA> leafMap;
599 
600  /// Mapping of ID to companion module.
601  DenseMap<Attribute, CompanionInfo> companionIDMap;
602 
603  /// An optional prefix applied to all interfaces in the design. This is set
604  /// based on a PrefixInterfacesAnnotation.
605  StringRef interfacePrefix;
606 
607  NLATable *nlaTable;
608 
609  /// The design-under-test (DUT) as determined by the presence of a
610  /// "sifive.enterprise.firrtl.MarkDUTAnnotation". This will be null if no DUT
611  /// was found.
612  FModuleOp dut;
613 
614  /// An optional directory for testbench-related files. This is null if no
615  /// "TestBenchDirAnnotation" is found.
616  StringAttr testbenchDir;
617 
618  /// Return a string containing the name of an interface. Apply correct
619  /// prefixing from the interfacePrefix and module-level prefix parameter.
620  std::string getInterfaceName(StringAttr prefix,
621  AugmentedBundleTypeAttr bundleType) {
622 
623  if (prefix)
624  return (prefix.getValue() + interfacePrefix +
625  bundleType.getDefName().getValue())
626  .str();
627  return (interfacePrefix + bundleType.getDefName().getValue()).str();
628  }
629 
630  /// Recursively examine an AugmentedType to populate the "mappings" file
631  /// (generate XMRs) for this interface. This does not build new interfaces.
632  bool traverseField(Attribute field, IntegerAttr id, VerbatimBuilder &path,
633  SmallVector<VerbatimXMRbuilder> &xmrElems,
634  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
635 
636  /// Recursively examine an AugmentedType to both build new interfaces and
637  /// populate a "mappings" file (generate XMRs) using `traverseField`. Return
638  /// the type of the field exmained.
639  std::optional<TypeSum>
640  computeField(Attribute field, IntegerAttr id, StringAttr prefix,
641  VerbatimBuilder &path, SmallVector<VerbatimXMRbuilder> &xmrElems,
642  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
643 
644  /// Recursively examine an AugmentedBundleType to both build new interfaces
645  /// and populate a "mappings" file (generate XMRs). Return none if the
646  /// interface is invalid.
647  std::optional<StringAttr>
648  traverseBundle(AugmentedBundleTypeAttr bundle, IntegerAttr id,
649  StringAttr prefix, VerbatimBuilder &path,
650  SmallVector<VerbatimXMRbuilder> &xmrElems,
651  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
652 
653  /// Return the module associated with this value.
654  igraph::ModuleOpInterface getEnclosingModule(Value value,
655  FlatSymbolRefAttr sym = {});
656 
657  /// Inforamtion about how the circuit should be extracted. This will be
658  /// non-empty if an extraction annotation is found.
659  std::optional<ExtractionInfo> maybeExtractInfo = std::nullopt;
660 
661  /// A filename describing where to put a YAML representation of the
662  /// interfaces generated by this pass.
663  std::optional<StringAttr> maybeHierarchyFileYAML = std::nullopt;
664 
665  StringAttr getOutputDirectory() {
666  if (maybeExtractInfo)
667  return maybeExtractInfo->directory;
668  return {};
669  }
670 
671  /// Store of an instance paths analysis. This is constructed inside
672  /// `runOnOperation`, to work around the deleted copy constructor of
673  /// `InstancePathCache`'s internal `BumpPtrAllocator`.
674  ///
675  /// TODO: Investigate a way to not use a pointer here like how `getNamespace`
676  /// works below.
677  InstancePathCache *instancePaths = nullptr;
678 
679  /// The namespace associated with the circuit. This is lazily constructed
680  /// using `getNamesapce`.
681  std::optional<CircuitNamespace> circuitNamespace;
682 
683  /// The module namespaces. These are lazily constructed by
684  /// `getModuleNamespace`.
685  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
686 
687  /// Return a reference to the circuit namespace. This will lazily construct a
688  /// namespace if one does not exist.
689  CircuitNamespace &getNamespace() {
690  if (!circuitNamespace)
691  circuitNamespace = CircuitNamespace(getOperation());
692  return *circuitNamespace;
693  }
694 
695  /// Get the cached namespace for a module.
696  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
697  return moduleNamespaces.try_emplace(module, module).first->second;
698  }
699 
700  /// A symbol table associated with the circuit. This is lazily constructed by
701  /// `getSymbolTable`.
702  std::optional<SymbolTable *> symbolTable;
703 
704  /// Return a reference to a circuit-level symbol table. Lazily construct one
705  /// if such a symbol table does not already exist.
706  SymbolTable &getSymbolTable() {
707  if (!symbolTable)
708  symbolTable = &getAnalysis<SymbolTable>();
709  return **symbolTable;
710  }
711 
712  // Utility that acts like emitOpError, but does _not_ include a note. The
713  // note in emitOpError includes the entire op which means the **ENTIRE**
714  // FIRRTL circuit. This doesn't communicate anything useful to the user
715  // other than flooding their terminal.
716  InFlightDiagnostic emitCircuitError(StringRef message = {}) {
717  return emitError(getOperation().getLoc(), "'firrtl.circuit' op " + message);
718  }
719 
720  // Insert comment delimiters ("// ") after newlines in the description string.
721  // This is necessary to prevent introducing invalid verbatim Verilog.
722  //
723  // TODO: Add a comment op and lower the description to that.
724  // TODO: Tracking issue: https://github.com/llvm/circt/issues/1677
725  std::string cleanupDescription(StringRef description) {
726  StringRef head;
727  SmallString<64> out;
728  do {
729  std::tie(head, description) = description.split("\n");
730  out.append(head);
731  if (!description.empty())
732  out.append("\n// ");
733  } while (!description.empty());
734  return std::string(out);
735  }
736 
737  /// A store of the YAML representation of interfaces.
738  DenseMap<Attribute, sv::InterfaceOp> interfaceMap;
739 
740  /// Emit the hierarchy yaml file.
741  void emitHierarchyYamlFile(SmallVectorImpl<sv::InterfaceOp> &intfs);
742 };
743 
744 } // namespace
745 
746 //===----------------------------------------------------------------------===//
747 // Code related to handling Grand Central View annotations
748 //===----------------------------------------------------------------------===//
749 
750 /// Recursively walk a sifive.enterprise.grandcentral.AugmentedType to extract
751 /// any annotations it may contain. This is going to generate two types of
752 /// annotations:
753 /// 1) Annotations necessary to build interfaces and store them at "~"
754 /// 2) Scattered annotations for how components bind to interfaces
755 static std::optional<DictionaryAttr>
756 parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType,
757  DictionaryAttr root, StringRef companion, StringAttr name,
758  StringAttr defName, std::optional<IntegerAttr> id,
759  std::optional<StringAttr> description, Twine clazz,
760  StringAttr companionAttr, Twine path = {}) {
761 
762  auto *context = state.circuit.getContext();
763  auto loc = state.circuit.getLoc();
764 
765  /// Optionally unpack a ReferenceTarget encoded as a DictionaryAttr. Return
766  /// either a pair containing the Target string (up to the reference) and an
767  /// array of components or none if the input is malformed. The input
768  /// DicionaryAttr encoding is a JSON object of a serialized ReferenceTarget
769  /// Scala class. By example, this is converting:
770  /// ~Foo|Foo>a.b[0]
771  /// To:
772  /// {"~Foo|Foo>a", {".b", "[0]"}}
773  /// The format of a ReferenceTarget object like:
774  /// circuit: String
775  /// module: String
776  /// path: Seq[(Instance, OfModule)]
777  /// ref: String
778  /// component: Seq[TargetToken]
779  auto refToTarget =
780  [&](DictionaryAttr refTarget) -> std::optional<std::string> {
781  auto circuitAttr =
782  tryGetAs<StringAttr>(refTarget, refTarget, "circuit", loc, clazz, path);
783  auto moduleAttr =
784  tryGetAs<StringAttr>(refTarget, refTarget, "module", loc, clazz, path);
785  auto pathAttr =
786  tryGetAs<ArrayAttr>(refTarget, refTarget, "path", loc, clazz, path);
787  auto componentAttr = tryGetAs<ArrayAttr>(refTarget, refTarget, "component",
788  loc, clazz, path);
789  if (!circuitAttr || !moduleAttr || !pathAttr || !componentAttr)
790  return {};
791 
792  // Parse non-local annotations.
793  SmallString<32> strpath;
794  for (auto p : pathAttr) {
795  auto dict = dyn_cast_or_null<DictionaryAttr>(p);
796  if (!dict) {
797  mlir::emitError(loc, "annotation '" + clazz +
798  " has invalid type (expected DictionaryAttr)");
799  return {};
800  }
801  auto instHolder =
802  tryGetAs<DictionaryAttr>(dict, dict, "_1", loc, clazz, path);
803  auto modHolder =
804  tryGetAs<DictionaryAttr>(dict, dict, "_2", loc, clazz, path);
805  if (!instHolder || !modHolder) {
806  mlir::emitError(loc, "annotation '" + clazz +
807  " has invalid type (expected DictionaryAttr)");
808  return {};
809  }
810  auto inst = tryGetAs<StringAttr>(instHolder, instHolder, "value", loc,
811  clazz, path);
812  auto mod =
813  tryGetAs<StringAttr>(modHolder, modHolder, "value", loc, clazz, path);
814  if (!inst || !mod) {
815  mlir::emitError(loc, "annotation '" + clazz +
816  " has invalid type (expected DictionaryAttr)");
817  return {};
818  }
819  strpath += "/" + inst.getValue().str() + ":" + mod.getValue().str();
820  }
821 
822  SmallVector<Attribute> componentAttrs;
823  SmallString<32> componentStr;
824  for (size_t i = 0, e = componentAttr.size(); i != e; ++i) {
825  auto cPath = (path + ".component[" + Twine(i) + "]").str();
826  auto component = componentAttr[i];
827  auto dict = dyn_cast_or_null<DictionaryAttr>(component);
828  if (!dict) {
829  mlir::emitError(loc, "annotation '" + clazz + "' with path '" + cPath +
830  " has invalid type (expected DictionaryAttr)");
831  return {};
832  }
833  auto classAttr =
834  tryGetAs<StringAttr>(dict, refTarget, "class", loc, clazz, cPath);
835  if (!classAttr)
836  return {};
837 
838  auto value = dict.get("value");
839 
840  // A subfield like "bar" in "~Foo|Foo>foo.bar".
841  if (auto field = dyn_cast<StringAttr>(value)) {
842  assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Field" &&
843  "A StringAttr target token must be found with a subfield target "
844  "token.");
845  componentStr.append((Twine(".") + field.getValue()).str());
846  continue;
847  }
848 
849  // A subindex like "42" in "~Foo|Foo>foo[42]".
850  if (auto index = dyn_cast<IntegerAttr>(value)) {
851  assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Index" &&
852  "An IntegerAttr target token must be found with a subindex "
853  "target token.");
854  componentStr.append(
855  (Twine("[") + Twine(index.getValue().getZExtValue()) + "]").str());
856  continue;
857  }
858 
859  mlir::emitError(loc,
860  "Annotation '" + clazz + "' with path '" + cPath +
861  ".value has unexpected type (should be StringAttr "
862  "for subfield or IntegerAttr for subindex).")
863  .attachNote()
864  << "The value received was: " << value << "\n";
865  return {};
866  }
867 
868  auto refAttr =
869  tryGetAs<StringAttr>(refTarget, refTarget, "ref", loc, clazz, path);
870 
871  return (Twine("~" + circuitAttr.getValue() + "|" + moduleAttr.getValue() +
872  strpath + ">" + refAttr.getValue()) +
873  componentStr)
874  .str();
875  };
876 
877  auto classAttr =
878  tryGetAs<StringAttr>(augmentedType, root, "class", loc, clazz, path);
879  if (!classAttr)
880  return std::nullopt;
881  StringRef classBase = classAttr.getValue();
882  if (!classBase.consume_front("sifive.enterprise.grandcentral.Augmented")) {
883  mlir::emitError(loc,
884  "the 'class' was expected to start with "
885  "'sifive.enterprise.grandCentral.Augmented*', but was '" +
886  classAttr.getValue() + "' (Did you misspell it?)")
887  .attachNote()
888  << "see annotation: " << augmentedType;
889  return std::nullopt;
890  }
891 
892  // An AugmentedBundleType looks like:
893  // "defName": String
894  // "elements": Seq[AugmentedField]
895  if (classBase == "BundleType") {
896  defName =
897  tryGetAs<StringAttr>(augmentedType, root, "defName", loc, clazz, path);
898  if (!defName)
899  return std::nullopt;
900 
901  // Each element is an AugmentedField with members:
902  // "name": String
903  // "description": Option[String]
904  // "tpe": AugmenetedType
905  SmallVector<Attribute> elements;
906  auto elementsAttr =
907  tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
908  if (!elementsAttr)
909  return std::nullopt;
910  for (size_t i = 0, e = elementsAttr.size(); i != e; ++i) {
911  auto field = dyn_cast_or_null<DictionaryAttr>(elementsAttr[i]);
912  if (!field) {
913  mlir::emitError(
914  loc,
915  "Annotation '" + Twine(clazz) + "' with path '.elements[" +
916  Twine(i) +
917  "]' contained an unexpected type (expected a DictionaryAttr).")
918  .attachNote()
919  << "The received element was: " << elementsAttr[i] << "\n";
920  return std::nullopt;
921  }
922  auto ePath = (path + ".elements[" + Twine(i) + "]").str();
923  auto name = tryGetAs<StringAttr>(field, root, "name", loc, clazz, ePath);
924  auto tpe =
925  tryGetAs<DictionaryAttr>(field, root, "tpe", loc, clazz, ePath);
926  std::optional<StringAttr> description;
927  if (auto maybeDescription = field.get("description"))
928  description = cast<StringAttr>(maybeDescription);
929  auto eltAttr = parseAugmentedType(
930  state, tpe, root, companion, name, defName, std::nullopt, description,
931  clazz, companionAttr, path + "_" + name.getValue());
932  if (!name || !tpe || !eltAttr)
933  return std::nullopt;
934 
935  // Collect information necessary to build a module with this view later.
936  // This includes the optional description and name.
937  NamedAttrList attrs;
938  if (auto maybeDescription = field.get("description"))
939  attrs.append("description", cast<StringAttr>(maybeDescription));
940  attrs.append("name", name);
941  attrs.append("tpe", tpe.getAs<StringAttr>("class"));
942  elements.push_back(*eltAttr);
943  }
944  // Add an annotation that stores information necessary to construct the
945  // module for the view. This needs the name of the module (defName) and the
946  // names of the components inside it.
947  NamedAttrList attrs;
948  attrs.append("class", classAttr);
949  attrs.append("defName", defName);
950  if (description)
951  attrs.append("description", *description);
952  attrs.append("elements", ArrayAttr::get(context, elements));
953  if (id)
954  attrs.append("id", *id);
955  attrs.append("name", name);
956  return DictionaryAttr::getWithSorted(context, attrs);
957  }
958 
959  // An AugmentedGroundType looks like:
960  // "ref": ReferenceTarget
961  // "tpe": GroundType
962  // The ReferenceTarget is not serialized to a string. The GroundType will
963  // either be an actual FIRRTL ground type or a GrandCentral uninferred type.
964  // This can be ignored for us.
965  if (classBase == "GroundType") {
966  auto maybeTarget = refToTarget(augmentedType.getAs<DictionaryAttr>("ref"));
967  if (!maybeTarget) {
968  mlir::emitError(loc, "Failed to parse ReferenceTarget").attachNote()
969  << "See the full Annotation here: " << root;
970  return std::nullopt;
971  }
972 
973  auto id = state.newID();
974 
975  auto target = *maybeTarget;
976 
977  NamedAttrList elementIface, elementScattered;
978 
979  // Populate the annotation for the interface element.
980  elementIface.append("class", classAttr);
981  if (description)
982  elementIface.append("description", *description);
983  elementIface.append("id", id);
984  elementIface.append("name", name);
985  // Populate an annotation that will be scattered onto the element.
986  elementScattered.append("class", classAttr);
987  elementScattered.append("id", id);
988  // If there are sub-targets, then add these.
989  auto targetAttr = StringAttr::get(context, target);
990  auto xmrSrcTarget = resolvePath(targetAttr.getValue(), state.circuit,
991  state.symTbl, state.targetCaches);
992  if (!xmrSrcTarget) {
993  mlir::emitError(loc, "Failed to resolve target ") << targetAttr;
994  return std::nullopt;
995  }
996 
997  // Determine the source for this Wiring Problem. The source is the value
998  // that will be eventually by read from, via cross-module reference, to
999  // drive this element of the SystemVerilog Interface.
1000  auto sourceRef = xmrSrcTarget->ref;
1001  ImplicitLocOpBuilder builder(sourceRef.getOp()->getLoc(), context);
1002  std::optional<Value> source =
1003  TypeSwitch<Operation *, std::optional<Value>>(sourceRef.getOp())
1004  // The target is an external module port. The source is the
1005  // instance port of this singly-instantiated external module.
1006  .Case<FExtModuleOp>([&](FExtModuleOp extMod)
1007  -> std::optional<Value> {
1008  auto portNo = sourceRef.getImpl().getPortNo();
1009  if (xmrSrcTarget->instances.empty()) {
1010  auto paths = state.instancePathCache.getAbsolutePaths(extMod);
1011  if (paths.size() > 1) {
1012  extMod.emitError(
1013  "cannot resolve a unique instance path from the "
1014  "external module '")
1015  << targetAttr << "'";
1016  return std::nullopt;
1017  }
1018  auto *it = xmrSrcTarget->instances.begin();
1019  for (auto inst : paths.back()) {
1020  xmrSrcTarget->instances.insert(it, cast<InstanceOp>(inst));
1021  ++it;
1022  }
1023  }
1024  auto lastInst = xmrSrcTarget->instances.pop_back_val();
1025  builder.setInsertionPointAfter(lastInst);
1026  return getValueByFieldID(builder, lastInst.getResult(portNo),
1027  xmrSrcTarget->fieldIdx);
1028  })
1029  // The target is a module port. The source is the port _inside_
1030  // that module.
1031  .Case<FModuleOp>([&](FModuleOp module) -> std::optional<Value> {
1032  builder.setInsertionPointToEnd(module.getBodyBlock());
1033  auto portNum = sourceRef.getImpl().getPortNo();
1034  return getValueByFieldID(builder, module.getArgument(portNum),
1035  xmrSrcTarget->fieldIdx);
1036  })
1037  // The target is something else.
1038  .Default([&](Operation *op) -> std::optional<Value> {
1039  auto module = cast<FModuleOp>(sourceRef.getModule());
1040  builder.setInsertionPointToEnd(module.getBodyBlock());
1041  auto is = dyn_cast<hw::InnerSymbolOpInterface>(op);
1042  // Resolve InnerSymbol references to their target result.
1043  if (is && is.getTargetResult())
1044  return getValueByFieldID(builder, is.getTargetResult(),
1045  xmrSrcTarget->fieldIdx);
1046  if (sourceRef.getOp()->getNumResults() != 1) {
1047  op->emitOpError()
1048  << "cannot be used as a target of the Grand Central View \""
1049  << defName.getValue()
1050  << "\" because it does not have exactly one result";
1051  return std::nullopt;
1052  }
1053  return getValueByFieldID(builder, sourceRef.getOp()->getResult(0),
1054  xmrSrcTarget->fieldIdx);
1055  });
1056 
1057  // Exit if there was an error in the source.
1058  if (!source)
1059  return std::nullopt;
1060 
1061  // Compute the sink of this Wiring Problem. The final sink will eventually
1062  // be a SystemVerilog Interface. However, this cannot exist until the
1063  // GrandCentral pass runs. Create an undriven WireOp and use that as the
1064  // sink. The WireOp will be driven later when the Wiring Problem is
1065  // resolved. Apply the scattered element annotation to this directly to save
1066  // having to reprocess this in LowerAnnotations.
1067  auto companionMod =
1068  cast<FModuleOp>(resolvePath(companionAttr.getValue(), state.circuit,
1069  state.symTbl, state.targetCaches)
1070  ->ref.getOp());
1071  builder.setInsertionPointToEnd(companionMod.getBodyBlock());
1072  auto sink = builder.create<WireOp>(source->getType(), name);
1073  state.targetCaches.insertOp(sink);
1074  AnnotationSet annotations(context);
1075  annotations.addAnnotations(
1076  {DictionaryAttr::getWithSorted(context, elementScattered)});
1077  annotations.applyToOperation(sink);
1078 
1079  // Append this new Wiring Problem to the ApplyState. The Wiring Problem
1080  // will be resolved to bore RefType ports before LowerAnnotations finishes.
1081  state.wiringProblems.push_back({*source, sink.getResult(),
1082  (path + "__bore").str(),
1084 
1085  return DictionaryAttr::getWithSorted(context, elementIface);
1086  }
1087 
1088  // An AugmentedVectorType looks like:
1089  // "elements": Seq[AugmentedType]
1090  if (classBase == "VectorType") {
1091  auto elementsAttr =
1092  tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
1093  if (!elementsAttr)
1094  return std::nullopt;
1095  SmallVector<Attribute> elements;
1096  for (auto [i, elt] : llvm::enumerate(elementsAttr)) {
1097  auto eltAttr = parseAugmentedType(
1098  state, cast<DictionaryAttr>(elt), root, companion, name,
1099  StringAttr::get(context, ""), id, std::nullopt, clazz, companionAttr,
1100  path + "_" + Twine(i));
1101  if (!eltAttr)
1102  return std::nullopt;
1103  elements.push_back(*eltAttr);
1104  }
1105  NamedAttrList attrs;
1106  attrs.append("class", classAttr);
1107  if (description)
1108  attrs.append("description", *description);
1109  attrs.append("elements", ArrayAttr::get(context, elements));
1110  attrs.append("name", name);
1111  return DictionaryAttr::getWithSorted(context, attrs);
1112  }
1113 
1114  // Any of the following are known and expected, but are legacy AugmentedTypes
1115  // do not have a target:
1116  // - AugmentedStringType
1117  // - AugmentedBooleanType
1118  // - AugmentedIntegerType
1119  // - AugmentedDoubleType
1120  bool isIgnorable =
1121  llvm::StringSwitch<bool>(classBase)
1122  .Cases("StringType", "BooleanType", "IntegerType", "DoubleType", true)
1123  .Default(false);
1124  if (isIgnorable) {
1125  NamedAttrList attrs;
1126  attrs.append("class", classAttr);
1127  attrs.append("name", name);
1128  auto value =
1129  tryGetAs<Attribute>(augmentedType, root, "value", loc, clazz, path);
1130  if (!value)
1131  return std::nullopt;
1132  attrs.append("value", value);
1133  return DictionaryAttr::getWithSorted(context, attrs);
1134  }
1135 
1136  // Anything else is unexpected or a user error if they manually wrote
1137  // annotations. Print an error and error out.
1138  mlir::emitError(loc, "found unknown AugmentedType '" + classAttr.getValue() +
1139  "' (Did you misspell it?)")
1140  .attachNote()
1141  << "see annotation: " << augmentedType;
1142  return std::nullopt;
1143 }
1144 
1145 LogicalResult circt::firrtl::applyGCTView(const AnnoPathValue &target,
1146  DictionaryAttr anno,
1147  ApplyState &state) {
1148 
1149  auto id = state.newID();
1150  auto *context = state.circuit.getContext();
1151  auto loc = state.circuit.getLoc();
1152  NamedAttrList companionAttrs;
1153  companionAttrs.append("class", StringAttr::get(context, companionAnnoClass));
1154  companionAttrs.append("id", id);
1155  auto viewAttr =
1156  tryGetAs<DictionaryAttr>(anno, anno, "view", loc, viewAnnoClass);
1157  if (!viewAttr)
1158  return failure();
1159  auto name = tryGetAs<StringAttr>(anno, anno, "name", loc, viewAnnoClass);
1160  if (!name)
1161  return failure();
1162  companionAttrs.append("name", name);
1163  auto companionAttr =
1164  tryGetAs<StringAttr>(anno, anno, "companion", loc, viewAnnoClass);
1165  if (!companionAttr)
1166  return failure();
1167  companionAttrs.append("target", companionAttr);
1168  state.addToWorklistFn(DictionaryAttr::get(context, companionAttrs));
1169 
1170  auto prunedAttr =
1171  parseAugmentedType(state, viewAttr, anno, companionAttr.getValue(), name,
1172  {}, id, {}, viewAnnoClass, companionAttr, Twine(name));
1173  if (!prunedAttr)
1174  return failure();
1175 
1176  AnnotationSet annotations(state.circuit);
1177  annotations.addAnnotations({*prunedAttr});
1178  annotations.applyToOperation(state.circuit);
1179 
1180  return success();
1181 }
1182 
1183 //===----------------------------------------------------------------------===//
1184 // GrandCentralPass Implementation
1185 //===----------------------------------------------------------------------===//
1186 
1187 std::optional<Attribute> GrandCentralPass::fromAttr(Attribute attr) {
1188  auto dict = dyn_cast<DictionaryAttr>(attr);
1189  if (!dict) {
1190  emitCircuitError() << "attribute is not a dictionary: " << attr << "\n";
1191  return std::nullopt;
1192  }
1193 
1194  auto clazz = dict.getAs<StringAttr>("class");
1195  if (!clazz) {
1196  emitCircuitError() << "missing 'class' key in " << dict << "\n";
1197  return std::nullopt;
1198  }
1199 
1200  auto classBase = clazz.getValue();
1201  classBase.consume_front("sifive.enterprise.grandcentral.Augmented");
1202 
1203  if (classBase == "BundleType") {
1204  if (dict.getAs<StringAttr>("defName") && dict.getAs<ArrayAttr>("elements"))
1205  return AugmentedBundleTypeAttr::get(&getContext(), dict);
1206  emitCircuitError() << "has an invalid AugmentedBundleType that does not "
1207  "contain 'defName' and 'elements' fields: "
1208  << dict;
1209  } else if (classBase == "VectorType") {
1210  if (dict.getAs<StringAttr>("name") && dict.getAs<ArrayAttr>("elements"))
1211  return AugmentedVectorTypeAttr::get(&getContext(), dict);
1212  emitCircuitError() << "has an invalid AugmentedVectorType that does not "
1213  "contain 'name' and 'elements' fields: "
1214  << dict;
1215  } else if (classBase == "GroundType") {
1216  auto id = dict.getAs<IntegerAttr>("id");
1217  auto name = dict.getAs<StringAttr>("name");
1218  if (id && leafMap.count(id) && name)
1219  return AugmentedGroundTypeAttr::get(&getContext(), dict);
1220  if (!id || !name)
1221  emitCircuitError() << "has an invalid AugmentedGroundType that does not "
1222  "contain 'id' and 'name' fields: "
1223  << dict;
1224  if (id && !leafMap.count(id))
1225  emitCircuitError() << "has an AugmentedGroundType with 'id == "
1226  << id.getValue().getZExtValue()
1227  << "' that does not have a scattered leaf to connect "
1228  "to in the circuit "
1229  "(was the leaf deleted or constant prop'd away?)";
1230  } else if (classBase == "StringType") {
1231  if (auto name = dict.getAs<StringAttr>("name"))
1232  return AugmentedStringTypeAttr::get(&getContext(), dict);
1233  } else if (classBase == "BooleanType") {
1234  if (auto name = dict.getAs<StringAttr>("name"))
1235  return AugmentedBooleanTypeAttr::get(&getContext(), dict);
1236  } else if (classBase == "IntegerType") {
1237  if (auto name = dict.getAs<StringAttr>("name"))
1238  return AugmentedIntegerTypeAttr::get(&getContext(), dict);
1239  } else if (classBase == "DoubleType") {
1240  if (auto name = dict.getAs<StringAttr>("name"))
1241  return AugmentedDoubleTypeAttr::get(&getContext(), dict);
1242  } else if (classBase == "LiteralType") {
1243  if (auto name = dict.getAs<StringAttr>("name"))
1244  return AugmentedLiteralTypeAttr::get(&getContext(), dict);
1245  } else if (classBase == "DeletedType") {
1246  if (auto name = dict.getAs<StringAttr>("name"))
1247  return AugmentedDeletedTypeAttr::get(&getContext(), dict);
1248  } else {
1249  emitCircuitError() << "has an invalid AugmentedType";
1250  }
1251  return std::nullopt;
1252 }
1253 
1254 bool GrandCentralPass::traverseField(
1255  Attribute field, IntegerAttr id, VerbatimBuilder &path,
1256  SmallVector<VerbatimXMRbuilder> &xmrElems,
1257  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1258  return TypeSwitch<Attribute, bool>(field)
1259  .Case<AugmentedGroundTypeAttr>([&](AugmentedGroundTypeAttr ground) {
1260  auto [fieldRef, sym] = leafMap.lookup(ground.getID());
1261  hw::HierPathOp nla;
1262  if (sym)
1263  nla = nlaTable->getNLA(sym.getAttr());
1264  Value leafValue = fieldRef.getValue();
1265  assert(leafValue && "leafValue not found");
1266 
1267  auto companionModule = companionIDMap.lookup(id).companion;
1268  igraph::ModuleOpInterface enclosing =
1269  getEnclosingModule(leafValue, sym);
1270 
1271  auto tpe = type_cast<FIRRTLBaseType>(leafValue.getType());
1272 
1273  // If the type is zero-width then do not emit an XMR.
1274  if (!tpe.getBitWidthOrSentinel())
1275  return true;
1276 
1277  // The leafValue is assumed to conform to a very specific pattern:
1278  //
1279  // 1) The leaf value is in the companion.
1280  // 2) The leaf value is a NodeOp
1281  //
1282  // Anything else means that there is an error or the IR is somehow using
1283  // "old-style" Annotations to encode a Grand Central View. This
1284  // _really_ should be impossible to hit given that LowerAnnotations must
1285  // generate code that conforms to the check here.
1286  auto *nodeOp = leafValue.getDefiningOp();
1287  if (companionModule != enclosing) {
1288  auto diag = companionModule->emitError()
1289  << "Grand Central View \""
1290  << companionIDMap.lookup(id).name
1291  << "\" is invalid because a leaf is not inside the "
1292  "companion module";
1293  diag.attachNote(leafValue.getLoc())
1294  << "the leaf value is declared here";
1295  if (nodeOp) {
1296  auto leafModule = nodeOp->getParentOfType<FModuleOp>();
1297  diag.attachNote(leafModule.getLoc())
1298  << "the leaf value is inside this module";
1299  }
1300  return false;
1301  }
1302 
1303  if (!isa<NodeOp>(nodeOp)) {
1304  emitError(leafValue.getLoc())
1305  << "Grand Central View \"" << companionIDMap.lookup(id).name
1306  << "\" has an invalid leaf value (this must be a node)";
1307  return false;
1308  }
1309 
1310  /// Increment all the indices inside `{{`, `}}` by one. This is to
1311  /// indicate that a value is added to the `substitutions` of the
1312  /// verbatim op, other than the symbols.
1313  auto getStrAndIncrementIds = [&](StringRef base) -> StringAttr {
1314  SmallString<128> replStr;
1315  StringRef begin = "{{";
1316  StringRef end = "}}";
1317  // The replacement string.
1318  size_t from = 0;
1319  while (from < base.size()) {
1320  // Search for the first `{{` and `}}`.
1321  size_t beginAt = base.find(begin, from);
1322  size_t endAt = base.find(end, from);
1323  // If not found, then done.
1324  if (beginAt == StringRef::npos || endAt == StringRef::npos ||
1325  (beginAt > endAt)) {
1326  replStr.append(base.substr(from));
1327  break;
1328  }
1329  // Copy the string as is, until the `{{`.
1330  replStr.append(base.substr(from, beginAt - from));
1331  // Advance `from` to the character after the `}}`.
1332  from = endAt + 2;
1333  auto idChar = base.substr(beginAt + 2, endAt - beginAt - 2);
1334  int idNum;
1335  bool failed = idChar.getAsInteger(10, idNum);
1336  (void)failed;
1337  assert(!failed && "failed to parse integer from verbatim string");
1338  // Now increment the id and append.
1339  replStr.append("{{");
1340  Twine(idNum + 1).toVector(replStr);
1341  replStr.append("}}");
1342  }
1343  return StringAttr::get(&getContext(), "assign " + replStr + ";");
1344  };
1345 
1346  // This is the new style of XMRs using RefTypes. The value subsitution
1347  // index is set to -1, as it will be incremented when generating the
1348  // string.
1349  // Generate the path from the LCA to the module that contains the leaf.
1350  path += " = {{-1}}";
1352  // Assemble the verbatim op.
1353  xmrElems.emplace_back(
1354  nodeOp->getOperand(0), getStrAndIncrementIds(path.getString()),
1355  ArrayAttr::get(&getContext(), path.getSymbols()), companionModule);
1356  return true;
1357  })
1358  .Case<AugmentedVectorTypeAttr>([&](auto vector) {
1359  bool notFailed = true;
1360  auto elements = vector.getElements();
1361  for (size_t i = 0, e = elements.size(); i != e; ++i) {
1362  auto field = fromAttr(elements[i]);
1363  if (!field)
1364  return false;
1365  notFailed &= traverseField(
1366  *field, id, path.snapshot().append("[" + Twine(i) + "]"),
1367  xmrElems, interfaceBuilder);
1368  }
1369  return notFailed;
1370  })
1371  .Case<AugmentedBundleTypeAttr>([&](AugmentedBundleTypeAttr bundle) {
1372  bool anyFailed = true;
1373  for (auto element : bundle.getElements()) {
1374  auto field = fromAttr(element);
1375  if (!field)
1376  return false;
1377  auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1378  if (!name)
1379  name = cast<DictionaryAttr>(element).getAs<StringAttr>("defName");
1380  anyFailed &= traverseField(
1381  *field, id, path.snapshot().append("." + name.getValue()),
1382  xmrElems, interfaceBuilder);
1383  }
1384 
1385  return anyFailed;
1386  })
1387  .Case<AugmentedStringTypeAttr>([&](auto a) { return false; })
1388  .Case<AugmentedBooleanTypeAttr>([&](auto a) { return false; })
1389  .Case<AugmentedIntegerTypeAttr>([&](auto a) { return false; })
1390  .Case<AugmentedDoubleTypeAttr>([&](auto a) { return false; })
1391  .Case<AugmentedLiteralTypeAttr>([&](auto a) { return false; })
1392  .Case<AugmentedDeletedTypeAttr>([&](auto a) { return false; })
1393  .Default([](auto a) { return true; });
1394 }
1395 
1396 std::optional<TypeSum> GrandCentralPass::computeField(
1397  Attribute field, IntegerAttr id, StringAttr prefix, VerbatimBuilder &path,
1398  SmallVector<VerbatimXMRbuilder> &xmrElems,
1399  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1400 
1401  auto unsupported = [&](StringRef name, StringRef kind) {
1402  return VerbatimType({("// <unsupported " + kind + " type>").str(), false});
1403  };
1404 
1405  return TypeSwitch<Attribute, std::optional<TypeSum>>(field)
1406  .Case<AugmentedGroundTypeAttr>(
1407  [&](AugmentedGroundTypeAttr ground) -> std::optional<TypeSum> {
1408  // Traverse to generate mappings.
1409  if (!traverseField(field, id, path, xmrElems, interfaceBuilder))
1410  return std::nullopt;
1411  FieldRef fieldRef = leafMap.lookup(ground.getID()).field;
1412  auto value = fieldRef.getValue();
1413  auto fieldID = fieldRef.getFieldID();
1414  auto tpe = firrtl::type_cast<FIRRTLBaseType>(
1416  fieldID));
1417  if (!tpe.isGround()) {
1418  value.getDefiningOp()->emitOpError()
1419  << "cannot be added to interface with id '"
1420  << id.getValue().getZExtValue()
1421  << "' because it is not a ground type";
1422  return std::nullopt;
1423  }
1424  return TypeSum(IntegerType::get(getOperation().getContext(),
1425  tpe.getBitWidthOrSentinel()));
1426  })
1427  .Case<AugmentedVectorTypeAttr>(
1428  [&](AugmentedVectorTypeAttr vector) -> std::optional<TypeSum> {
1429  auto elements = vector.getElements();
1430  auto firstElement = fromAttr(elements[0]);
1431  auto elementType =
1432  computeField(*firstElement, id, prefix,
1433  path.snapshot().append("[" + Twine(0) + "]"),
1434  xmrElems, interfaceBuilder);
1435  if (!elementType)
1436  return std::nullopt;
1437 
1438  for (size_t i = 1, e = elements.size(); i != e; ++i) {
1439  auto subField = fromAttr(elements[i]);
1440  if (!subField)
1441  return std::nullopt;
1442  (void)traverseField(*subField, id,
1443  path.snapshot().append("[" + Twine(i) + "]"),
1444  xmrElems, interfaceBuilder);
1445  }
1446 
1447  if (auto *tpe = std::get_if<Type>(&*elementType))
1448  return TypeSum(
1449  hw::UnpackedArrayType::get(*tpe, elements.getValue().size()));
1450  auto str = std::get<VerbatimType>(*elementType);
1451  str.dimensions.push_back(elements.getValue().size());
1452  return TypeSum(str);
1453  })
1454  .Case<AugmentedBundleTypeAttr>(
1455  [&](AugmentedBundleTypeAttr bundle) -> TypeSum {
1456  auto ifaceName = traverseBundle(bundle, id, prefix, path, xmrElems,
1457  interfaceBuilder);
1458  assert(ifaceName && *ifaceName);
1459  return VerbatimType({ifaceName->str(), true});
1460  })
1461  .Case<AugmentedStringTypeAttr>([&](auto field) -> TypeSum {
1462  return unsupported(field.getName().getValue(), "string");
1463  })
1464  .Case<AugmentedBooleanTypeAttr>([&](auto field) -> TypeSum {
1465  return unsupported(field.getName().getValue(), "boolean");
1466  })
1467  .Case<AugmentedIntegerTypeAttr>([&](auto field) -> TypeSum {
1468  return unsupported(field.getName().getValue(), "integer");
1469  })
1470  .Case<AugmentedDoubleTypeAttr>([&](auto field) -> TypeSum {
1471  return unsupported(field.getName().getValue(), "double");
1472  })
1473  .Case<AugmentedLiteralTypeAttr>([&](auto field) -> TypeSum {
1474  return unsupported(field.getName().getValue(), "literal");
1475  })
1476  .Case<AugmentedDeletedTypeAttr>([&](auto field) -> TypeSum {
1477  return unsupported(field.getName().getValue(), "deleted");
1478  });
1479 }
1480 
1481 /// Traverse an Annotation that is an AugmentedBundleType. During traversal,
1482 /// construct any discovered SystemVerilog interfaces. If this is the root
1483 /// interface, instantiate that interface in the companion. Recurse into fields
1484 /// of the AugmentedBundleType to construct nested interfaces and generate
1485 /// stringy-typed SystemVerilog hierarchical references to drive the
1486 /// interface. Returns false on any failure and true on success.
1487 std::optional<StringAttr> GrandCentralPass::traverseBundle(
1488  AugmentedBundleTypeAttr bundle, IntegerAttr id, StringAttr prefix,
1489  VerbatimBuilder &path, SmallVector<VerbatimXMRbuilder> &xmrElems,
1490  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1491 
1492  unsigned lastIndex = interfaceBuilder.size();
1493  auto iFaceName = StringAttr::get(
1494  &getContext(), getNamespace().newName(getInterfaceName(prefix, bundle)));
1495  interfaceBuilder.emplace_back(iFaceName, id);
1496 
1497  for (auto element : bundle.getElements()) {
1498  auto field = fromAttr(element);
1499  if (!field)
1500  return std::nullopt;
1501 
1502  auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1503  // auto signalSym = hw::InnerRefAttr::get(iface.sym_nameAttr(), name);
1504  // TODO: The `append(name.getValue())` in the following should actually be
1505  // `append(signalSym)`, but this requires that `computeField` and the
1506  // functions it calls always return a type for which we can construct an
1507  // `InterfaceSignalOp`. Since nested interface instances are currently
1508  // busted (due to the interface being a symbol table), this doesn't work at
1509  // the moment. Passing a `name` works most of the time, but can be brittle
1510  // if the interface field requires renaming in the output (e.g. due to
1511  // naming conflicts).
1512  auto elementType = computeField(
1513  *field, id, prefix, path.snapshot().append(".").append(name.getValue()),
1514  xmrElems, interfaceBuilder);
1515  if (!elementType)
1516  return std::nullopt;
1517  StringAttr description =
1518  cast<DictionaryAttr>(element).getAs<StringAttr>("description");
1519  interfaceBuilder[lastIndex].elementsList.emplace_back(description, name,
1520  *elementType);
1521  }
1522  return iFaceName;
1523 }
1524 
1525 /// Return the module that is associated with this value. Use the cached/lazily
1526 /// constructed symbol table to make this fast.
1527 igraph::ModuleOpInterface
1528 GrandCentralPass::getEnclosingModule(Value value, FlatSymbolRefAttr sym) {
1529  if (auto blockArg = dyn_cast<BlockArgument>(value))
1530  return cast<igraph::ModuleOpInterface>(blockArg.getOwner()->getParentOp());
1531 
1532  auto *op = value.getDefiningOp();
1533  if (InstanceOp instance = dyn_cast<InstanceOp>(op))
1534  return getSymbolTable().lookup<igraph::ModuleOpInterface>(
1535  instance.getModuleNameAttr().getValue());
1536 
1537  return op->getParentOfType<igraph::ModuleOpInterface>();
1538 }
1539 
1540 /// This method contains the business logic of this pass.
1541 void GrandCentralPass::runOnOperation() {
1542  LLVM_DEBUG(debugPassHeader(this) << "\n");
1543 
1544  CircuitOp circuitOp = getOperation();
1545 
1546  // Look at the circuit annotaitons to do two things:
1547  //
1548  // 1. Determine extraction information (directory and filename).
1549  // 2. Populate a worklist of all annotations that encode interfaces.
1550  //
1551  // Remove annotations encoding interfaces, but leave extraction information as
1552  // this may be needed by later passes.
1553  SmallVector<Annotation> worklist;
1554  bool removalError = false;
1555  AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
1556  if (anno.isClass(augmentedBundleTypeClass)) {
1557  // If we are in "Instantiate" companion mode, then we don't need to
1558  // create the interface, so we can skip adding it to the worklist. This
1559  // is a janky hack for situations where you want to synthesize assertion
1560  // logic included in the companion, but don't want to have a dead
1561  // interface hanging around (or have problems with tools understanding
1562  // interfaces).
1563  if (companionMode != CompanionMode::Instantiate)
1564  worklist.push_back(anno);
1565  ++numAnnosRemoved;
1566  return true;
1567  }
1568  if (anno.isClass(extractGrandCentralClass)) {
1569  if (maybeExtractInfo) {
1570  emitCircuitError("more than one 'ExtractGrandCentralAnnotation' was "
1571  "found, but exactly one must be provided");
1572  removalError = true;
1573  return false;
1574  }
1575 
1576  auto directory = anno.getMember<StringAttr>("directory");
1577  auto filename = anno.getMember<StringAttr>("filename");
1578  if (!directory || !filename) {
1579  emitCircuitError()
1580  << "contained an invalid 'ExtractGrandCentralAnnotation' that does "
1581  "not contain 'directory' and 'filename' fields: "
1582  << anno.getDict();
1583  removalError = true;
1584  return false;
1585  }
1586  if (directory.getValue().empty())
1587  directory = StringAttr::get(circuitOp.getContext(), ".");
1588 
1589  maybeExtractInfo = {directory, filename};
1590  // Do not delete this annotation. Extraction info may be needed later.
1591  return false;
1592  }
1594  if (maybeHierarchyFileYAML) {
1595  emitCircuitError("more than one 'GrandCentralHierarchyFileAnnotation' "
1596  "was found, but zero or one may be provided");
1597  removalError = true;
1598  return false;
1599  }
1600 
1601  auto filename = anno.getMember<StringAttr>("filename");
1602  if (!filename) {
1603  emitCircuitError()
1604  << "contained an invalid 'GrandCentralHierarchyFileAnnotation' "
1605  "that does not contain 'directory' and 'filename' fields: "
1606  << anno.getDict();
1607  removalError = true;
1608  return false;
1609  }
1610 
1611  maybeHierarchyFileYAML = filename;
1612  ++numAnnosRemoved;
1613  return true;
1614  }
1615  if (anno.isClass(prefixInterfacesAnnoClass)) {
1616  if (!interfacePrefix.empty()) {
1617  emitCircuitError("more than one 'PrefixInterfacesAnnotation' was "
1618  "found, but zero or one may be provided");
1619  removalError = true;
1620  return false;
1621  }
1622 
1623  auto prefix = anno.getMember<StringAttr>("prefix");
1624  if (!prefix) {
1625  emitCircuitError()
1626  << "contained an invalid 'PrefixInterfacesAnnotation' that does "
1627  "not contain a 'prefix' field: "
1628  << anno.getDict();
1629  removalError = true;
1630  return false;
1631  }
1632 
1633  interfacePrefix = prefix.getValue();
1634  ++numAnnosRemoved;
1635  return true;
1636  }
1637  if (anno.isClass(testBenchDirAnnoClass)) {
1638  testbenchDir = anno.getMember<StringAttr>("dirname");
1639  return false;
1640  }
1641  return false;
1642  });
1643 
1644  // Find the DUT if it exists. This needs to be known before the circuit is
1645  // walked.
1646  for (auto mod : circuitOp.getOps<FModuleOp>()) {
1647  if (failed(extractDUT(mod, dut)))
1648  removalError = true;
1649  }
1650 
1651  if (removalError)
1652  return signalPassFailure();
1653 
1654  LLVM_DEBUG({
1655  llvm::dbgs() << "Extraction Info:\n";
1656  if (maybeExtractInfo)
1657  llvm::dbgs() << " directory: " << maybeExtractInfo->directory << "\n"
1658  << " filename: " << maybeExtractInfo->bindFilename << "\n";
1659  else
1660  llvm::dbgs() << " <none>\n";
1661  llvm::dbgs() << "DUT: ";
1662  if (dut)
1663  llvm::dbgs() << dut.getModuleName() << "\n";
1664  else
1665  llvm::dbgs() << "<none>\n";
1666  llvm::dbgs()
1667  << "Prefix Info (from PrefixInterfacesAnnotation):\n"
1668  << " prefix: " << interfacePrefix << "\n"
1669  << "Hierarchy File Info (from GrandCentralHierarchyFileAnnotation):\n"
1670  << " filename: ";
1671  if (maybeHierarchyFileYAML)
1672  llvm::dbgs() << *maybeHierarchyFileYAML;
1673  else
1674  llvm::dbgs() << "<none>";
1675  llvm::dbgs() << "\n";
1676  });
1677 
1678  // Exit immediately if no annotations indicative of interfaces that need to be
1679  // built exist. However, still generate the YAML file if the annotation for
1680  // this was passed in because some flows expect this.
1681  if (worklist.empty()) {
1682  SmallVector<sv::InterfaceOp, 0> interfaceVec;
1683  emitHierarchyYamlFile(interfaceVec);
1684  return markAllAnalysesPreserved();
1685  }
1686 
1687  // Setup the builder to create ops _inside the FIRRTL circuit_. This is
1688  // necessary because interfaces and interface instances are created.
1689  // Instances link to their definitions via symbols and we don't want to
1690  // break this.
1691  auto builder = OpBuilder::atBlockEnd(circuitOp.getBodyBlock());
1692 
1693  // Maybe get an "id" from an Annotation. Generate error messages on the op if
1694  // no "id" exists.
1695  auto getID = [&](Operation *op,
1696  Annotation annotation) -> std::optional<IntegerAttr> {
1697  auto id = annotation.getMember<IntegerAttr>("id");
1698  if (!id) {
1699  op->emitOpError()
1700  << "contained a malformed "
1701  "'sifive.enterprise.grandcentral.AugmentedGroundType' annotation "
1702  "that did not contain an 'id' field";
1703  removalError = true;
1704  return std::nullopt;
1705  }
1706  return id;
1707  };
1708 
1709  /// TODO: Handle this differently to allow construction of an optionsl
1710  auto instancePathCache = InstancePathCache(getAnalysis<InstanceGraph>());
1711  instancePaths = &instancePathCache;
1712 
1713  /// Contains the set of modules which are instantiated by the DUT, but not a
1714  /// companion, instantiated by a companion, or instantiated under a bind. If
1715  /// no DUT exists, treat the top module as if it were the DUT. This works by
1716  /// doing a depth-first walk of the instance graph, starting from the
1717  /// "effective" DUT and stopping the search at any modules which are known
1718  /// companions or any instances which are marked "lowerToBind".
1719  DenseSet<InstanceGraphNode *> dutModules;
1720  InstanceGraphNode *effectiveDUT;
1721  if (dut)
1722  effectiveDUT = instancePaths->instanceGraph.lookup(dut);
1723  else
1724  effectiveDUT = instancePaths->instanceGraph.getTopLevelNode();
1725  {
1726  SmallVector<InstanceGraphNode *> modules({effectiveDUT});
1727  while (!modules.empty()) {
1728  auto *m = modules.pop_back_val();
1729  for (InstanceRecord *a : *m) {
1730  auto *mod = a->getTarget();
1731  // Skip modules that we've visited, that are are under the companion
1732  // module, or are bound/under a layer block.
1733  if (auto block = a->getInstance()->getParentOfType<LayerBlockOp>()) {
1734  auto diag = a->getInstance().emitOpError()
1735  << "is instantiated under a '" << block.getOperationName()
1736  << "' op which is unexpected by GrandCentral (did you "
1737  "forget to run the LowerLayers pass?)";
1738  diag.attachNote(block.getLoc())
1739  << "the '" << block.getOperationName() << "' op is here";
1740  removalError = true;
1741  }
1742  auto instOp = dyn_cast<InstanceOp>(*a->getInstance());
1743  if (dutModules.contains(mod) ||
1744  AnnotationSet(mod->getModule()).hasAnnotation(companionAnnoClass) ||
1745  (instOp && instOp.getLowerToBind()))
1746  continue;
1747  modules.push_back(mod);
1748  dutModules.insert(mod);
1749  }
1750  }
1751  }
1752 
1753  // Maybe return the lone instance of a module. Generate errors on the op if
1754  // the module is not instantiated or is multiply instantiated.
1755  auto exactlyOneInstance = [&](FModuleOp op,
1756  StringRef msg) -> std::optional<InstanceOp> {
1757  auto *node = instancePaths->instanceGraph[op];
1758 
1759  switch (node->getNumUses()) {
1760  case 0:
1761  op->emitOpError() << "is marked as a GrandCentral '" << msg
1762  << "', but is never instantiated";
1763  return std::nullopt;
1764  case 1:
1765  return cast<InstanceOp>(*(*node->uses().begin())->getInstance());
1766  default:
1767  auto diag = op->emitOpError()
1768  << "is marked as a GrandCentral '" << msg
1769  << "', but it is instantiated more than once";
1770  for (auto *instance : node->uses())
1771  diag.attachNote(instance->getInstance()->getLoc())
1772  << "it is instantiated here";
1773  return std::nullopt;
1774  }
1775  };
1776 
1777  nlaTable = &getAnalysis<NLATable>();
1778 
1779  /// Walk the circuit and extract all information related to scattered Grand
1780  /// Central annotations. This is used to populate: (1) the companionIDMap and
1781  /// (2) the leafMap. Annotations are removed as they are discovered and if
1782  /// they are not malformed.
1783  DenseSet<Operation *> modulesToDelete;
1784  circuitOp.walk([&](Operation *op) {
1785  TypeSwitch<Operation *>(op)
1786  .Case<RegOp, RegResetOp, WireOp, NodeOp>([&](auto op) {
1787  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1788  if (!annotation.isClass(augmentedGroundTypeClass))
1789  return false;
1790  auto maybeID = getID(op, annotation);
1791  if (!maybeID)
1792  return false;
1793  auto sym =
1794  annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
1795  leafMap[*maybeID] = {{op.getResult(), annotation.getFieldID()},
1796  sym};
1797  ++numAnnosRemoved;
1798  return true;
1799  });
1800  })
1801  // TODO: Figure out what to do with this.
1802  .Case<InstanceOp>([&](auto op) {
1803  AnnotationSet::removePortAnnotations(op, [&](unsigned i,
1804  Annotation annotation) {
1805  if (!annotation.isClass(augmentedGroundTypeClass))
1806  return false;
1807  op.emitOpError()
1808  << "is marked as an interface element, but this should be "
1809  "impossible due to how the Chisel Grand Central API works";
1810  removalError = true;
1811  return false;
1812  });
1813  })
1814  .Case<MemOp>([&](auto op) {
1815  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1816  if (!annotation.isClass(augmentedGroundTypeClass))
1817  return false;
1818  op.emitOpError()
1819  << "is marked as an interface element, but this does not make "
1820  "sense (is there a scattering bug or do you have a "
1821  "malformed hand-crafted MLIR circuit?)";
1822  removalError = true;
1823  return false;
1824  });
1826  op, [&](unsigned i, Annotation annotation) {
1827  if (!annotation.isClass(augmentedGroundTypeClass))
1828  return false;
1829  op.emitOpError()
1830  << "has port '" << i
1831  << "' marked as an interface element, but this does not "
1832  "make sense (is there a scattering bug or do you have a "
1833  "malformed hand-crafted MLIR circuit?)";
1834  removalError = true;
1835  return false;
1836  });
1837  })
1838  .Case<FModuleOp>([&](FModuleOp op) {
1839  // Handle annotations on the ports.
1840  AnnotationSet::removePortAnnotations(op, [&](unsigned i,
1841  Annotation annotation) {
1842  if (!annotation.isClass(augmentedGroundTypeClass))
1843  return false;
1844  auto maybeID = getID(op, annotation);
1845  if (!maybeID)
1846  return false;
1847  auto sym =
1848  annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
1849  leafMap[*maybeID] = {{op.getArgument(i), annotation.getFieldID()},
1850  sym};
1851  ++numAnnosRemoved;
1852  return true;
1853  });
1854 
1855  // Handle annotations on the module.
1856  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1857  if (!annotation.getClass().starts_with(viewAnnoClass))
1858  return false;
1859  auto isNonlocal = annotation.getMember<FlatSymbolRefAttr>(
1860  "circt.nonlocal") != nullptr;
1861  auto name = annotation.getMember<StringAttr>("name");
1862  auto id = annotation.getMember<IntegerAttr>("id");
1863  if (!id) {
1864  op.emitOpError()
1865  << "has a malformed "
1866  "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
1867  "not contain an 'id' field with an 'IntegerAttr' value";
1868  goto FModuleOp_error;
1869  }
1870  if (!name) {
1871  op.emitOpError()
1872  << "has a malformed "
1873  "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
1874  "not contain a 'name' field with a 'StringAttr' value";
1875  goto FModuleOp_error;
1876  }
1877 
1878  // If this is a companion, then:
1879  // 1. Insert it into the companion map
1880  // 2. Create a new mapping module.
1881  // 3. Instatiate the mapping module in the companion.
1882  // 4. Check that the companion is instantated exactly once.
1883  // 5. Set attributes on that lone instance so it will become a
1884  // bind if extraction information was provided. If a DUT is
1885  // known, then anything in the test harness will not be
1886  // extracted.
1887  if (annotation.getClass() == companionAnnoClass) {
1888  builder.setInsertionPointToEnd(circuitOp.getBodyBlock());
1889 
1890  companionIDMap[id] = {name.getValue(), op, isNonlocal};
1891 
1892  // Assert that the companion is instantiated once and only once.
1893  auto instance = exactlyOneInstance(op, "companion");
1894  if (!instance)
1895  goto FModuleOp_error;
1896 
1897  // Companions are only allowed to take inputs.
1898  for (auto [i, result] : llvm::enumerate(instance->getResults())) {
1899  if (instance->getPortDirection(i) == Direction::In)
1900  continue;
1901  // Do not allow any outputs in the drop mode.
1902  auto ty = result.getType();
1903  if (ty.isa<RefType>() && companionMode != CompanionMode::Drop)
1904  continue;
1905  op.emitOpError()
1906  << "companion instance cannot have output ports";
1907  goto FModuleOp_error;
1908  }
1909 
1910  // If no extraction info was provided, exit. Otherwise, setup the
1911  // lone instance of the companion to be lowered as a bind.
1912  if (!maybeExtractInfo) {
1913  ++numAnnosRemoved;
1914  return true;
1915  }
1916 
1917  // If the companion is instantiated above the DUT, then don't
1918  // extract it.
1919  if (dut && !instancePaths->instanceGraph.isAncestor(op, dut)) {
1920  ++numAnnosRemoved;
1921  return true;
1922  }
1923 
1924  // Look for any modules/extmodules _only_ instantiated by the
1925  // companion. If these have no output file attribute, then mark
1926  // them as being extracted into the Grand Central directory.
1927  InstanceGraphNode *companionNode =
1928  instancePaths->instanceGraph.lookup(op);
1929 
1930  LLVM_DEBUG({
1931  llvm::dbgs()
1932  << "Found companion module: "
1933  << companionNode->getModule().getModuleName() << "\n"
1934  << " submodules exclusively instantiated "
1935  "(including companion):\n";
1936  });
1937 
1938  if (companionMode == CompanionMode::Drop) {
1939  // Delete the instance if companions are disabled.
1940  OpBuilder builder(&getContext());
1941  for (auto port : instance->getResults()) {
1942  builder.setInsertionPointAfterValue(port);
1943  auto wire =
1944  builder.create<WireOp>(port.getLoc(), port.getType());
1945  port.replaceAllUsesWith(wire.getResult());
1946  }
1947  instance->erase();
1948  } else {
1949  // Lower the companion to a bind unless the user told us
1950  // explicitly not to.
1951  if (companionMode == CompanionMode::Bind)
1952  (*instance)->setAttr("lowerToBind", builder.getUnitAttr());
1953 
1954  (*instance)->setAttr(
1955  "output_file",
1956  hw::OutputFileAttr::getFromFilename(
1957  &getContext(),
1958  maybeExtractInfo->bindFilename.getValue(),
1959  /*excludeFromFileList=*/true));
1960  }
1961 
1962  for (auto &node : llvm::depth_first(companionNode)) {
1963  auto mod = node->getModule();
1964 
1965  // Check to see if we should change the output directory of a
1966  // module. Only update in the following conditions:
1967  // 1) The module is the companion.
1968  // 2) The module is NOT instantiated by the effective DUT or
1969  // is under a bind.
1970  auto *modNode = instancePaths->instanceGraph.lookup(mod);
1971  SmallVector<InstanceRecord *> instances(modNode->uses());
1972  if (modNode != companionNode && dutModules.count(modNode))
1973  continue;
1974 
1975  LLVM_DEBUG({
1976  llvm::dbgs()
1977  << " - module: " << mod.getModuleName() << "\n";
1978  });
1979 
1980  if (auto extmodule = dyn_cast<FExtModuleOp>(*mod)) {
1981  for (auto anno : AnnotationSet(extmodule)) {
1982  if (companionMode == CompanionMode::Drop) {
1983  modulesToDelete.insert(mod);
1984  break;
1985  }
1986  if (!anno.isClass(blackBoxInlineAnnoClass) &&
1988  continue;
1989  if (extmodule->hasAttr("output_file"))
1990  break;
1991  extmodule->setAttr(
1992  "output_file",
1993  hw::OutputFileAttr::getAsDirectory(
1994  &getContext(),
1995  maybeExtractInfo->directory.getValue()));
1996  break;
1997  }
1998  continue;
1999  }
2000 
2001  if (companionMode == CompanionMode::Drop) {
2002  modulesToDelete.insert(mod);
2003  } else {
2004  // Move this module under the Grand Central output directory
2005  // if no pre-existing output file information is present.
2006  if (!mod->hasAttr("output_file")) {
2007  mod->setAttr("output_file",
2008  hw::OutputFileAttr::getAsDirectory(
2009  &getContext(),
2010  maybeExtractInfo->directory.getValue(),
2011  /*excludeFromFileList=*/true,
2012  /*includeReplicatedOps=*/true));
2013  mod->setAttr("comment", builder.getStringAttr(
2014  "VCS coverage exclude_file"));
2015  }
2016  }
2017  }
2018 
2019  ++numAnnosRemoved;
2020  return true;
2021  }
2022 
2023  op.emitOpError()
2024  << "unknown annotation class: " << annotation.getDict();
2025 
2026  FModuleOp_error:
2027  removalError = true;
2028  return false;
2029  });
2030  });
2031  });
2032 
2033  if (removalError)
2034  return signalPassFailure();
2035 
2036  if (companionMode == CompanionMode::Drop) {
2037  for (auto *mod : modulesToDelete) {
2038  auto name = cast<FModuleLike>(mod).getModuleNameAttr();
2039 
2040  DenseSet<hw::HierPathOp> nlas;
2041  nlaTable->getNLAsInModule(name, nlas);
2042  nlaTable->removeNLAsfromModule(nlas, name);
2043  for (auto nla : nlas) {
2044  if (nla.root() == name)
2045  nla.erase();
2046  }
2047 
2048  mod->erase();
2049  }
2050 
2051  SmallVector<sv::InterfaceOp, 0> interfaceVec;
2052  emitHierarchyYamlFile(interfaceVec);
2053  return;
2054  }
2055 
2056  LLVM_DEBUG({
2057  // Print out the companion map and all leaf values that were discovered.
2058  // Sort these by their keys before printing to make this easier to read.
2059  SmallVector<IntegerAttr> ids;
2060  auto sort = [&ids]() {
2061  llvm::sort(ids, [](IntegerAttr a, IntegerAttr b) {
2062  return a.getValue().getZExtValue() < b.getValue().getZExtValue();
2063  });
2064  };
2065  for (auto tuple : companionIDMap)
2066  ids.push_back(cast<IntegerAttr>(tuple.first));
2067  sort();
2068  llvm::dbgs() << "companionIDMap:\n";
2069  for (auto id : ids) {
2070  auto value = companionIDMap.lookup(id);
2071  llvm::dbgs() << " - " << id.getValue() << ": "
2072  << value.companion.getName() << " -> " << value.name << "\n";
2073  }
2074  ids.clear();
2075  for (auto tuple : leafMap)
2076  ids.push_back(cast<IntegerAttr>(tuple.first));
2077  sort();
2078  llvm::dbgs() << "leafMap:\n";
2079  for (auto id : ids) {
2080  auto fieldRef = leafMap.lookup(id).field;
2081  auto value = fieldRef.getValue();
2082  auto fieldID = fieldRef.getFieldID();
2083  if (auto blockArg = dyn_cast<BlockArgument>(value)) {
2084  FModuleOp module = cast<FModuleOp>(blockArg.getOwner()->getParentOp());
2085  llvm::dbgs() << " - " << id.getValue() << ": "
2086  << module.getName() + ">" +
2087  module.getPortName(blockArg.getArgNumber());
2088  if (fieldID)
2089  llvm::dbgs() << ", fieldID=" << fieldID;
2090  llvm::dbgs() << "\n";
2091  } else {
2092  llvm::dbgs() << " - " << id.getValue() << ": "
2093  << value.getDefiningOp()
2094  ->getAttr("name")
2095  .cast<StringAttr>()
2096  .getValue();
2097  if (fieldID)
2098  llvm::dbgs() << ", fieldID=" << fieldID;
2099  llvm::dbgs() << "\n";
2100  }
2101  }
2102  });
2103 
2104  // Now, iterate over the worklist of interface-encoding annotations to create
2105  // the interface and all its sub-interfaces (interfaces that it instantiates),
2106  // instantiate the top-level interface, and generate a "mappings file" that
2107  // will use XMRs to drive the interface. If extraction info is available,
2108  // then the top-level instantiate interface will be marked for extraction via
2109  // a SystemVerilog bind.
2110  SmallVector<sv::InterfaceOp, 2> interfaceVec;
2112  companionToInterfaceMap;
2113  auto compareInterfaceSignal = [&](InterfaceElemsBuilder &lhs,
2114  InterfaceElemsBuilder &rhs) {
2115  auto compareProps = [&](InterfaceElemsBuilder::Properties &lhs,
2116  InterfaceElemsBuilder::Properties &rhs) {
2117  // If it's a verbatim op, no need to check the string, because the
2118  // interface names might not match. As long as the signal types match that
2119  // is sufficient.
2120  if (lhs.elemType.index() == 0 && rhs.elemType.index() == 0)
2121  return true;
2122  if (std::get<Type>(lhs.elemType) == std::get<Type>(rhs.elemType))
2123  return true;
2124  return false;
2125  };
2126  return std::equal(lhs.elementsList.begin(), lhs.elementsList.end(),
2127  rhs.elementsList.begin(), compareProps);
2128  };
2129  for (auto anno : worklist) {
2130  auto bundle = AugmentedBundleTypeAttr::get(&getContext(), anno.getDict());
2131 
2132  // The top-level AugmentedBundleType must have a global ID field so that
2133  // this can be linked to the companion.
2134  if (!bundle.isRoot()) {
2135  emitCircuitError() << "missing 'id' in root-level BundleType: "
2136  << anno.getDict() << "\n";
2137  removalError = true;
2138  continue;
2139  }
2140 
2141  if (companionIDMap.count(bundle.getID()) == 0) {
2142  emitCircuitError() << "no companion found with 'id' value '"
2143  << bundle.getID().getValue().getZExtValue() << "'\n";
2144  removalError = true;
2145  continue;
2146  }
2147 
2148  // Decide on a symbol name to use for the interface instance. This is needed
2149  // in `traverseBundle` as a placeholder for the connect operations.
2150  auto companionIter = companionIDMap.lookup(bundle.getID());
2151  auto companionModule = companionIter.companion;
2152  auto symbolName = getNamespace().newName(
2153  "__" + companionIDMap.lookup(bundle.getID()).name + "_" +
2154  getInterfaceName(bundle.getPrefix(), bundle) + "__");
2155 
2156  // Recursively walk the AugmentedBundleType to generate interfaces and XMRs.
2157  // Error out if this returns None (indicating that the annotation is
2158  // malformed in some way). A good error message is generated inside
2159  // `traverseBundle` or the functions it calls.
2160  auto instanceSymbol =
2161  hw::InnerRefAttr::get(SymbolTable::getSymbolName(companionModule),
2162  StringAttr::get(&getContext(), symbolName));
2163  VerbatimBuilder::Base verbatimData;
2164  VerbatimBuilder verbatim(verbatimData);
2165  verbatim += instanceSymbol;
2166  // List of interface elements.
2167 
2168  SmallVector<VerbatimXMRbuilder> xmrElems;
2169  SmallVector<InterfaceElemsBuilder> interfaceBuilder;
2170 
2171  auto ifaceName = traverseBundle(bundle, bundle.getID(), bundle.getPrefix(),
2172  verbatim, xmrElems, interfaceBuilder);
2173  if (!ifaceName) {
2174  removalError = true;
2175  continue;
2176  }
2177 
2178  if (companionIter.isNonlocal) {
2179  // If the companion module has two exactly same ViewAnnotation.companion
2180  // annotations, then add the interface for only one of them. This happens
2181  // when the companion is deduped.
2182  auto viewMapIter = companionToInterfaceMap.find(companionModule);
2183  if (viewMapIter != companionToInterfaceMap.end())
2184  if (std::equal(interfaceBuilder.begin(), interfaceBuilder.end(),
2185  viewMapIter->getSecond().begin(),
2186  compareInterfaceSignal)) {
2187  continue;
2188  }
2189 
2190  companionToInterfaceMap[companionModule] = interfaceBuilder;
2191  }
2192 
2193  if (interfaceBuilder.empty())
2194  continue;
2195  auto companionBuilder =
2196  OpBuilder::atBlockEnd(companionModule.getBodyBlock());
2197 
2198  // Generate gathered XMR's.
2199  for (auto xmrElem : xmrElems) {
2200  auto uloc = companionBuilder.getUnknownLoc();
2201  companionBuilder.create<sv::VerbatimOp>(uloc, xmrElem.str, xmrElem.val,
2202  xmrElem.syms);
2203  }
2204  numXMRs += xmrElems.size();
2205 
2206  sv::InterfaceOp topIface;
2207  for (const auto &ifaceBuilder : interfaceBuilder) {
2208  auto builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
2209  auto loc = getOperation().getLoc();
2210  sv::InterfaceOp iface =
2211  builder.create<sv::InterfaceOp>(loc, ifaceBuilder.iFaceName);
2212  if (!topIface)
2213  topIface = iface;
2214  ++numInterfaces;
2215  if (dut &&
2216  !instancePaths->instanceGraph.isAncestor(
2217  companionIDMap[ifaceBuilder.id].companion, dut) &&
2218  testbenchDir)
2219  iface->setAttr("output_file",
2220  hw::OutputFileAttr::getAsDirectory(
2221  &getContext(), testbenchDir.getValue(),
2222  /*excludeFromFileList=*/true));
2223  else if (maybeExtractInfo)
2224  iface->setAttr("output_file",
2225  hw::OutputFileAttr::getAsDirectory(
2226  &getContext(), getOutputDirectory().getValue(),
2227  /*excludeFromFileList=*/true));
2228  iface.setCommentAttr(builder.getStringAttr("VCS coverage exclude_file"));
2229  builder.setInsertionPointToEnd(
2230  cast<sv::InterfaceOp>(iface).getBodyBlock());
2231  interfaceMap[FlatSymbolRefAttr::get(builder.getContext(),
2232  ifaceBuilder.iFaceName)] = iface;
2233  for (auto elem : ifaceBuilder.elementsList) {
2234 
2235  auto uloc = builder.getUnknownLoc();
2236 
2237  auto description = elem.description;
2238 
2239  if (description) {
2240  auto descriptionOp = builder.create<sv::VerbatimOp>(
2241  uloc, ("// " + cleanupDescription(description.getValue())));
2242 
2243  // If we need to generate a YAML representation of this interface,
2244  // then add an attribute indicating that this `sv::VerbatimOp` is
2245  // actually a description.
2246  if (maybeHierarchyFileYAML)
2247  descriptionOp->setAttr("firrtl.grandcentral.yaml.type",
2248  builder.getStringAttr("description"));
2249  }
2250  if (auto *str = std::get_if<VerbatimType>(&elem.elemType)) {
2251  auto instanceOp = builder.create<sv::VerbatimOp>(
2252  uloc, str->toStr(elem.elemName.getValue()));
2253 
2254  // If we need to generate a YAML representation of the interface, then
2255  // add attirbutes that describe what this `sv::VerbatimOp` is.
2256  if (maybeHierarchyFileYAML) {
2257  if (str->instantiation)
2258  instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2259  builder.getStringAttr("instance"));
2260  else
2261  instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2262  builder.getStringAttr("unsupported"));
2263  instanceOp->setAttr("firrtl.grandcentral.yaml.name", elem.elemName);
2264  instanceOp->setAttr("firrtl.grandcentral.yaml.dimensions",
2265  builder.getI32ArrayAttr(str->dimensions));
2266  instanceOp->setAttr(
2267  "firrtl.grandcentral.yaml.symbol",
2268  FlatSymbolRefAttr::get(builder.getContext(), str->str));
2269  }
2270  continue;
2271  }
2272 
2273  auto tpe = std::get<Type>(elem.elemType);
2274  builder.create<sv::InterfaceSignalOp>(uloc, elem.elemName.getValue(),
2275  tpe);
2276  }
2277  }
2278 
2279  ++numViews;
2280 
2281  interfaceVec.push_back(topIface);
2282 
2283  // Instantiate the interface inside the companion.
2284  builder.setInsertionPointToStart(companionModule.getBodyBlock());
2285  builder.create<sv::InterfaceInstanceOp>(
2286  getOperation().getLoc(), topIface.getInterfaceType(),
2287  companionIDMap.lookup(bundle.getID()).name,
2288  hw::InnerSymAttr::get(builder.getStringAttr(symbolName)));
2289 
2290  // If no extraction information was present, then just leave the interface
2291  // instantiated in the companion. Otherwise, make it a bind.
2292  if (!maybeExtractInfo)
2293  continue;
2294 
2295  // If the interface is associated with a companion that is instantiated
2296  // above the DUT (e.g.., in the test harness), then don't extract it.
2297  if (dut && !instancePaths->instanceGraph.isAncestor(
2298  companionIDMap[bundle.getID()].companion, dut))
2299  continue;
2300  }
2301 
2302  emitHierarchyYamlFile(interfaceVec);
2303 
2304  // Signal pass failure if any errors were found while examining circuit
2305  // annotations.
2306  if (removalError)
2307  return signalPassFailure();
2308  markAnalysesPreserved<NLATable>();
2309 }
2310 
2311 void GrandCentralPass::emitHierarchyYamlFile(
2312  SmallVectorImpl<sv::InterfaceOp> &intfs) {
2313  // If a `GrandCentralHierarchyFileAnnotation` was passed in, generate a YAML
2314  // representation of the interfaces that we produced with the filename that
2315  // that annotation provided.
2316  if (!maybeHierarchyFileYAML)
2317  return;
2318 
2319  CircuitOp circuitOp = getOperation();
2320 
2321  std::string yamlString;
2322  llvm::raw_string_ostream stream(yamlString);
2323  ::yaml::Context yamlContext({interfaceMap});
2324  llvm::yaml::Output yout(stream);
2325  yamlize(yout, intfs, true, yamlContext);
2326 
2327  auto builder = OpBuilder::atBlockBegin(circuitOp.getBodyBlock());
2328  builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), yamlString)
2329  ->setAttr("output_file",
2330  hw::OutputFileAttr::getFromFilename(
2331  &getContext(), maybeHierarchyFileYAML->getValue(),
2332  /*excludeFromFileList=*/true));
2333  LLVM_DEBUG({ llvm::dbgs() << "Generated YAML:" << yamlString << "\n"; });
2334 }
2335 
2336 //===----------------------------------------------------------------------===//
2337 // Pass Creation
2338 //===----------------------------------------------------------------------===//
2339 
2340 std::unique_ptr<mlir::Pass>
2342  auto pass = std::make_unique<GrandCentralPass>();
2343  pass->companionMode = companionMode;
2344  return pass;
2345 }
assert(baseType &&"element must be base type")
MlirType elementType
Definition: CHIRRTL.cpp:29
int32_t width
Definition: FIRRTL.cpp:36
static std::optional< DictionaryAttr > parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType, DictionaryAttr root, StringRef companion, StringAttr name, StringAttr defName, std::optional< IntegerAttr > id, std::optional< StringAttr > description, Twine clazz, StringAttr companionAttr, Twine path={})
Recursively walk a sifive.enterprise.grandcentral.AugmentedType to extract any annotations it may con...
@ Output
Definition: HW.h:35
Builder builder
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Definition: FieldRef.h:59
Value getValue() const
Get the Value which created this location.
Definition: FieldRef.h:37
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
void addAnnotations(ArrayRef< Annotation > annotations)
Add more annotations to this annotation set.
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
DictionaryAttr getDict() const
Get the data dictionary of this attribute.
unsigned getFieldID() const
Get the field id this attribute targets.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
StringRef getClass() const
Return the 'class' that this annotation is representing.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
igraph::InstanceGraphNode * getTopLevelNode() override
Get the node corresponding to the top-level module of a circuit.
This table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
void removeNLAsfromModule(const DenseSet< hw::HierPathOp > &nlas, StringAttr mod)
Remove all the nlas in the set nlas from the module.
Definition: NLATable.h:173
void getNLAsInModule(StringAttr modName, DenseSet< hw::HierPathOp > &nlas)
Get the NLAs that the module modName particiaptes in, and insert them into the DenseSet nlas.
Definition: NLATable.h:94
hw::HierPathOp getNLA(StringAttr name)
Resolve a symbol to an NLA.
Definition: NLATable.cpp:48
This is a Node in the InstanceGraph.
bool isAncestor(ModuleOpInterface child, ModuleOpInterface parent, llvm::function_ref< bool(InstanceRecord *)> skipInstance=[](InstanceRecord *_) { return false;})
Check if child is instantiated by a parent.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
constexpr const char * augmentedBundleTypeClass
igraph::InstancePathCache InstancePathCache
constexpr const char * extractGrandCentralClass
LogicalResult extractDUT(FModuleOp mod, FModuleOp &dut)
Utility that searches for a MarkDUTAnnotation on a specific module, mod, and tries to update a design...
constexpr const char * testBenchDirAnnoClass
constexpr const char * augmentedGroundTypeClass
std::unique_ptr< mlir::Pass > createGrandCentralPass(CompanionMode companionMode=CompanionMode::Bind)
constexpr const char * viewAnnoClass
constexpr const char * blackBoxPathAnnoClass
Value getValueByFieldID(ImplicitLocOpBuilder builder, Value value, unsigned fieldID)
This gets the value targeted by a field id.
constexpr const char * companionAnnoClass
constexpr const char * blackBoxInlineAnnoClass
std::optional< AnnoPathValue > resolvePath(StringRef rawPath, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Resolve a string path to a named item inside a circuit.
LogicalResult applyGCTView(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
constexpr const char * prefixInterfacesAnnoClass
constexpr const char * grandCentralHierarchyFileAnnoClass
::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID)
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
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
static std::string stripComment(StringRef str)
Convert newlines and comments to remove the comments.
Definition: sv.py:1
State threaded through functions for resolving and applying annotations.
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
void insertOp(Operation *op)
Add a new op to the target cache.
A data structure that caches and provides absolute paths to module instances in the IR.
ArrayRef< InstancePath > getAbsolutePaths(ModuleOpInterface op)
InstanceGraph & instanceGraph
The instance graph of the IR.
SmallVector< int64_t, 2 > dimensions
An array describing the dimnensionality of the interface.
static void mapping(IO &io, DescribedInstance &op, Context &ctx)
DescribedSignal denormalize(IO &)
This cannot be denomralized back to an interface op.
std::optional< std::string > description
An optional, textual description of what the field is.
Field(IO &io)
A no-argument constructor is necessary to work with LLVM's YAML library.
Field(IO &io, DescribedSignal &op)
Construct a Field from a DescribedSignal (an sv::InterfaceSignalOp with an optional description).
SmallVector< unsigned, 2 > dimensions
The dimensions of the field.
static void mapping(IO &io, DescribedSignal &op, Context &ctx)
Interface(IO &io)
A no-argument constructor is necessary to work with LLVM's YAML library.
std::vector< DescribedInstance > instances
Instantiations of other interfaces.
Interface(IO &io, sv::InterfaceOp &op)
Construct an Interface from an sv::InterfaceOp.
sv::InterfaceOp denormalize(IO &)
This cannot be denomralized back to an interface op.
std::vector< DescribedSignal > fields
All ground or vectors that make up the interface.
static void mapping(IO &io, sv::InterfaceOp &op, Context &ctx)