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