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