CIRCT  20.0.0git
GrandCentral.cpp
Go to the documentation of this file.
1 //===- GrandCentral.cpp - Ingest black box sources --------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //===----------------------------------------------------------------------===//
7 //
8 // Implement SiFive's Grand Central transform. Currently, this supports
9 // SystemVerilog Interface generation.
10 //
11 //===----------------------------------------------------------------------===//
12 
24 #include "circt/Dialect/HW/HWOps.h"
26 #include "circt/Dialect/SV/SVOps.h"
27 #include "circt/Support/Debug.h"
28 #include "mlir/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  NLATable *nlaTable;
612 
613  /// The design-under-test (DUT) as determined by the presence of a
614  /// "sifive.enterprise.firrtl.MarkDUTAnnotation". This will be null if no DUT
615  /// was found.
616  FModuleLike dut;
617 
618  /// An optional directory for testbench-related files. This is null if no
619  /// "TestBenchDirAnnotation" is found.
620  StringAttr testbenchDir;
621 
622  /// Return a string containing the name of an interface.
623  std::string getInterfaceName(AugmentedBundleTypeAttr bundleType) {
624  return (bundleType.getDefName().getValue()).str();
625  }
626 
627  /// Recursively examine an AugmentedType to populate the "mappings" file
628  /// (generate XMRs) for this interface. This does not build new interfaces.
629  bool traverseField(Attribute field, IntegerAttr id, VerbatimBuilder &path,
630  SmallVector<VerbatimXMRbuilder> &xmrElems,
631  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
632 
633  /// Recursively examine an AugmentedType to both build new interfaces and
634  /// populate a "mappings" file (generate XMRs) using `traverseField`. Return
635  /// the type of the field examined.
636  std::optional<TypeSum>
637  computeField(Attribute field, IntegerAttr id, VerbatimBuilder &path,
638  SmallVector<VerbatimXMRbuilder> &xmrElems,
639  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
640 
641  /// Recursively examine an AugmentedBundleType to both build new interfaces
642  /// and populate a "mappings" file (generate XMRs). Return none if the
643  /// interface is invalid.
644  std::optional<StringAttr>
645  traverseBundle(AugmentedBundleTypeAttr bundle, IntegerAttr id,
646  VerbatimBuilder &path,
647  SmallVector<VerbatimXMRbuilder> &xmrElems,
648  SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
649 
650  /// Return the module associated with this value.
651  igraph::ModuleOpInterface getEnclosingModule(Value value,
652  FlatSymbolRefAttr sym = {});
653 
654  /// Information about how the circuit should be extracted. This will be
655  /// non-empty if an extraction annotation is found.
656  std::optional<ExtractionInfo> maybeExtractInfo = std::nullopt;
657 
658  /// A filename describing where to put a YAML representation of the
659  /// interfaces generated by this pass.
660  std::optional<StringAttr> maybeHierarchyFileYAML = std::nullopt;
661 
662  StringAttr getOutputDirectory() {
663  if (maybeExtractInfo)
664  return maybeExtractInfo->directory;
665  return {};
666  }
667 
668  /// Store of an instance paths analysis. This is constructed inside
669  /// `runOnOperation`, to work around the deleted copy constructor of
670  /// `InstancePathCache`'s internal `BumpPtrAllocator`.
671  ///
672  /// TODO: Investigate a way to not use a pointer here like how `getNamespace`
673  /// works below.
674  InstancePathCache *instancePaths = nullptr;
675 
676  /// An instance info analysis that is used to query if modules are in the
677  /// design or not.
678  InstanceInfo *instanceInfo = nullptr;
679 
680  /// The namespace associated with the circuit. This is lazily constructed
681  /// using `getNamespace`.
682  std::optional<CircuitNamespace> circuitNamespace;
683 
684  /// The module namespaces. These are lazily constructed by
685  /// `getModuleNamespace`.
686  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
687 
688  /// Return a reference to the circuit namespace. This will lazily construct a
689  /// namespace if one does not exist.
690  CircuitNamespace &getNamespace() {
691  if (!circuitNamespace)
692  circuitNamespace = CircuitNamespace(getOperation());
693  return *circuitNamespace;
694  }
695 
696  /// Get the cached namespace for a module.
697  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
698  return moduleNamespaces.try_emplace(module, module).first->second;
699  }
700 
701  /// A symbol table associated with the circuit. This is lazily constructed by
702  /// `getSymbolTable`.
703  std::optional<SymbolTable *> symbolTable;
704 
705  /// Return a reference to a circuit-level symbol table. Lazily construct one
706  /// if such a symbol table does not already exist.
707  SymbolTable &getSymbolTable() {
708  if (!symbolTable)
709  symbolTable = &getAnalysis<SymbolTable>();
710  return **symbolTable;
711  }
712 
713  // Utility that acts like emitOpError, but does _not_ include a note. The
714  // note in emitOpError includes the entire op which means the **ENTIRE**
715  // FIRRTL circuit. This doesn't communicate anything useful to the user
716  // other than flooding their terminal.
717  InFlightDiagnostic emitCircuitError(StringRef message = {}) {
718  return emitError(getOperation().getLoc(), "'firrtl.circuit' op " + message);
719  }
720 
721  // Insert comment delimiters ("// ") after newlines in the description string.
722  // This is necessary to prevent introducing invalid verbatim Verilog.
723  //
724  // TODO: Add a comment op and lower the description to that.
725  // TODO: Tracking issue: https://github.com/llvm/circt/issues/1677
726  std::string cleanupDescription(StringRef description) {
727  StringRef head;
728  SmallString<64> out;
729  do {
730  std::tie(head, description) = description.split("\n");
731  out.append(head);
732  if (!description.empty())
733  out.append("\n// ");
734  } while (!description.empty());
735  return std::string(out);
736  }
737 
738  /// A store of the YAML representation of interfaces.
739  DenseMap<Attribute, sv::InterfaceOp> interfaceMap;
740 
741  /// Emit the hierarchy yaml file.
742  void emitHierarchyYamlFile(SmallVectorImpl<sv::InterfaceOp> &intfs);
743 };
744 
745 } // namespace
746 
747 //===----------------------------------------------------------------------===//
748 // Code related to handling Grand Central View annotations
749 //===----------------------------------------------------------------------===//
750 
751 /// Recursively walk a sifive.enterprise.grandcentral.AugmentedType to extract
752 /// any annotations it may contain. This is going to generate two types of
753 /// annotations:
754 /// 1) Annotations necessary to build interfaces and store them at "~"
755 /// 2) Scattered annotations for how components bind to interfaces
756 static std::optional<DictionaryAttr>
757 parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType,
758  DictionaryAttr root, StringRef companion, StringAttr name,
759  StringAttr defName, std::optional<IntegerAttr> id,
760  std::optional<StringAttr> description, Twine clazz,
761  StringAttr companionAttr, Twine path = {}) {
762 
763  auto *context = state.circuit.getContext();
764  auto loc = state.circuit.getLoc();
765 
766  /// Optionally unpack a ReferenceTarget encoded as a DictionaryAttr. Return
767  /// either a pair containing the Target string (up to the reference) and an
768  /// array of components or none if the input is malformed. The input
769  /// DictionaryAttr encoding is a JSON object of a serialized ReferenceTarget
770  /// Scala class. By example, this is converting:
771  /// ~Foo|Foo>a.b[0]
772  /// To:
773  /// {"~Foo|Foo>a", {".b", "[0]"}}
774  /// The format of a ReferenceTarget object like:
775  /// circuit: String
776  /// module: String
777  /// path: Seq[(Instance, OfModule)]
778  /// ref: String
779  /// component: Seq[TargetToken]
780  auto refToTarget =
781  [&](DictionaryAttr refTarget) -> std::optional<std::string> {
782  auto circuitAttr =
783  tryGetAs<StringAttr>(refTarget, refTarget, "circuit", loc, clazz, path);
784  auto moduleAttr =
785  tryGetAs<StringAttr>(refTarget, refTarget, "module", loc, clazz, path);
786  auto pathAttr =
787  tryGetAs<ArrayAttr>(refTarget, refTarget, "path", loc, clazz, path);
788  auto componentAttr = tryGetAs<ArrayAttr>(refTarget, refTarget, "component",
789  loc, clazz, path);
790  if (!circuitAttr || !moduleAttr || !pathAttr || !componentAttr)
791  return {};
792 
793  // Parse non-local annotations.
794  SmallString<32> strpath;
795  for (auto p : pathAttr) {
796  auto dict = dyn_cast_or_null<DictionaryAttr>(p);
797  if (!dict) {
798  mlir::emitError(loc, "annotation '" + clazz +
799  " has invalid type (expected DictionaryAttr)");
800  return {};
801  }
802  auto instHolder =
803  tryGetAs<DictionaryAttr>(dict, dict, "_1", loc, clazz, path);
804  auto modHolder =
805  tryGetAs<DictionaryAttr>(dict, dict, "_2", loc, clazz, path);
806  if (!instHolder || !modHolder) {
807  mlir::emitError(loc, "annotation '" + clazz +
808  " has invalid type (expected DictionaryAttr)");
809  return {};
810  }
811  auto inst = tryGetAs<StringAttr>(instHolder, instHolder, "value", loc,
812  clazz, path);
813  auto mod =
814  tryGetAs<StringAttr>(modHolder, modHolder, "value", loc, clazz, path);
815  if (!inst || !mod) {
816  mlir::emitError(loc, "annotation '" + clazz +
817  " has invalid type (expected DictionaryAttr)");
818  return {};
819  }
820  strpath += "/" + inst.getValue().str() + ":" + mod.getValue().str();
821  }
822 
823  SmallVector<Attribute> componentAttrs;
824  SmallString<32> componentStr;
825  for (size_t i = 0, e = componentAttr.size(); i != e; ++i) {
826  auto cPath = (path + ".component[" + Twine(i) + "]").str();
827  auto component = componentAttr[i];
828  auto dict = dyn_cast_or_null<DictionaryAttr>(component);
829  if (!dict) {
830  mlir::emitError(loc, "annotation '" + clazz + "' with path '" + cPath +
831  " has invalid type (expected DictionaryAttr)");
832  return {};
833  }
834  auto classAttr =
835  tryGetAs<StringAttr>(dict, refTarget, "class", loc, clazz, cPath);
836  if (!classAttr)
837  return {};
838 
839  auto value = dict.get("value");
840 
841  // A subfield like "bar" in "~Foo|Foo>foo.bar".
842  if (auto field = dyn_cast<StringAttr>(value)) {
843  assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Field" &&
844  "A StringAttr target token must be found with a subfield target "
845  "token.");
846  componentStr.append((Twine(".") + field.getValue()).str());
847  continue;
848  }
849 
850  // A subindex like "42" in "~Foo|Foo>foo[42]".
851  if (auto index = dyn_cast<IntegerAttr>(value)) {
852  assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Index" &&
853  "An IntegerAttr target token must be found with a subindex "
854  "target token.");
855  componentStr.append(
856  (Twine("[") + Twine(index.getValue().getZExtValue()) + "]").str());
857  continue;
858  }
859 
860  mlir::emitError(loc,
861  "Annotation '" + clazz + "' with path '" + cPath +
862  ".value has unexpected type (should be StringAttr "
863  "for subfield or IntegerAttr for subindex).")
864  .attachNote()
865  << "The value received was: " << value << "\n";
866  return {};
867  }
868 
869  auto refAttr =
870  tryGetAs<StringAttr>(refTarget, refTarget, "ref", loc, clazz, path);
871  if (!refAttr)
872  return {};
873 
874  return (Twine("~" + circuitAttr.getValue() + "|" + moduleAttr.getValue() +
875  strpath + ">" + refAttr.getValue()) +
876  componentStr)
877  .str();
878  };
879 
880  auto classAttr =
881  tryGetAs<StringAttr>(augmentedType, root, "class", loc, clazz, path);
882  if (!classAttr)
883  return std::nullopt;
884  StringRef classBase = classAttr.getValue();
885  if (!classBase.consume_front("sifive.enterprise.grandcentral.Augmented")) {
886  mlir::emitError(loc,
887  "the 'class' was expected to start with "
888  "'sifive.enterprise.grandCentral.Augmented*', but was '" +
889  classAttr.getValue() + "' (Did you misspell it?)")
890  .attachNote()
891  << "see annotation: " << augmentedType;
892  return std::nullopt;
893  }
894 
895  // An AugmentedBundleType looks like:
896  // "defName": String
897  // "elements": Seq[AugmentedField]
898  if (classBase == "BundleType") {
899  defName =
900  tryGetAs<StringAttr>(augmentedType, root, "defName", loc, clazz, path);
901  if (!defName)
902  return std::nullopt;
903 
904  // Each element is an AugmentedField with members:
905  // "name": String
906  // "description": Option[String]
907  // "tpe": AugmentedType
908  SmallVector<Attribute> elements;
909  auto elementsAttr =
910  tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
911  if (!elementsAttr)
912  return std::nullopt;
913  for (size_t i = 0, e = elementsAttr.size(); i != e; ++i) {
914  auto field = dyn_cast_or_null<DictionaryAttr>(elementsAttr[i]);
915  if (!field) {
916  mlir::emitError(
917  loc,
918  "Annotation '" + Twine(clazz) + "' with path '.elements[" +
919  Twine(i) +
920  "]' contained an unexpected type (expected a DictionaryAttr).")
921  .attachNote()
922  << "The received element was: " << elementsAttr[i] << "\n";
923  return std::nullopt;
924  }
925  auto ePath = (path + ".elements[" + Twine(i) + "]").str();
926  auto name = tryGetAs<StringAttr>(field, root, "name", loc, clazz, ePath);
927  auto tpe =
928  tryGetAs<DictionaryAttr>(field, root, "tpe", loc, clazz, ePath);
929  if (!name || !tpe)
930  return std::nullopt;
931  std::optional<StringAttr> description;
932  if (auto maybeDescription = field.get("description"))
933  description = cast<StringAttr>(maybeDescription);
934  auto eltAttr = parseAugmentedType(
935  state, tpe, root, companion, name, defName, std::nullopt, description,
936  clazz, companionAttr, path + "_" + name.getValue());
937  if (!eltAttr)
938  return std::nullopt;
939 
940  // Collect information necessary to build a module with this view later.
941  // This includes the optional description and name.
942  NamedAttrList attrs;
943  if (auto maybeDescription = field.get("description"))
944  attrs.append("description", cast<StringAttr>(maybeDescription));
945  attrs.append("name", name);
946  auto tpeClass = tpe.getAs<StringAttr>("class");
947  if (!tpeClass) {
948  mlir::emitError(loc, "missing 'class' key in") << tpe;
949  return std::nullopt;
950  }
951  attrs.append("tpe", tpeClass);
952  elements.push_back(*eltAttr);
953  }
954  // Add an annotation that stores information necessary to construct the
955  // module for the view. This needs the name of the module (defName) and the
956  // names of the components inside it.
957  NamedAttrList attrs;
958  attrs.append("class", classAttr);
959  attrs.append("defName", defName);
960  if (description)
961  attrs.append("description", *description);
962  attrs.append("elements", ArrayAttr::get(context, elements));
963  if (id)
964  attrs.append("id", *id);
965  attrs.append("name", name);
966  return DictionaryAttr::getWithSorted(context, attrs);
967  }
968 
969  // An AugmentedGroundType looks like:
970  // "ref": ReferenceTarget
971  // "tpe": GroundType
972  // The ReferenceTarget is not serialized to a string. The GroundType will
973  // either be an actual FIRRTL ground type or a GrandCentral uninferred type.
974  // This can be ignored for us.
975  if (classBase == "GroundType") {
976  auto augRef = augmentedType.getAs<DictionaryAttr>("ref");
977  if (!augRef) {
978  mlir::emitError(loc, "missing 'ref' key in ") << augmentedType;
979  return std::nullopt;
980  }
981  auto maybeTarget = refToTarget(augRef);
982  if (!maybeTarget) {
983  mlir::emitError(loc, "Failed to parse ReferenceTarget").attachNote()
984  << "See the full Annotation here: " << root;
985  return std::nullopt;
986  }
987 
988  auto id = state.newID();
989 
990  auto target = *maybeTarget;
991 
992  NamedAttrList elementIface, elementScattered;
993 
994  // Populate the annotation for the interface element.
995  elementIface.append("class", classAttr);
996  if (description)
997  elementIface.append("description", *description);
998  elementIface.append("id", id);
999  elementIface.append("name", name);
1000  // Populate an annotation that will be scattered onto the element.
1001  elementScattered.append("class", classAttr);
1002  elementScattered.append("id", id);
1003  // If there are sub-targets, then add these.
1004  auto targetAttr = StringAttr::get(context, target);
1005  auto xmrSrcTarget = resolvePath(targetAttr.getValue(), state.circuit,
1006  state.symTbl, state.targetCaches);
1007  if (!xmrSrcTarget) {
1008  mlir::emitError(loc, "Failed to resolve target ") << targetAttr;
1009  return std::nullopt;
1010  }
1011 
1012  // Determine the source for this Wiring Problem. The source is the value
1013  // that will be eventually by read from, via cross-module reference, to
1014  // drive this element of the SystemVerilog Interface.
1015  auto sourceRef = xmrSrcTarget->ref;
1016  ImplicitLocOpBuilder builder(sourceRef.getOp()->getLoc(), context);
1017  std::optional<Value> source =
1018  TypeSwitch<Operation *, std::optional<Value>>(sourceRef.getOp())
1019  // The target is an external module port. The source is the
1020  // instance port of this singly-instantiated external module.
1021  .Case<FExtModuleOp>([&](FExtModuleOp extMod)
1022  -> std::optional<Value> {
1023  auto portNo = sourceRef.getImpl().getPortNo();
1024  if (xmrSrcTarget->instances.empty()) {
1025  auto paths = state.instancePathCache.getAbsolutePaths(extMod);
1026  if (paths.size() > 1) {
1027  extMod.emitError(
1028  "cannot resolve a unique instance path from the "
1029  "external module '")
1030  << targetAttr << "'";
1031  return std::nullopt;
1032  }
1033  auto *it = xmrSrcTarget->instances.begin();
1034  for (auto inst : paths.back()) {
1035  xmrSrcTarget->instances.insert(it, cast<InstanceOp>(inst));
1036  ++it;
1037  }
1038  }
1039  auto lastInst = xmrSrcTarget->instances.pop_back_val();
1040  builder.setInsertionPointAfter(lastInst);
1041  return getValueByFieldID(builder, lastInst.getResult(portNo),
1042  xmrSrcTarget->fieldIdx);
1043  })
1044  // The target is a module port. The source is the port _inside_
1045  // that module.
1046  .Case<FModuleOp>([&](FModuleOp module) -> std::optional<Value> {
1047  builder.setInsertionPointToEnd(module.getBodyBlock());
1048  auto portNum = sourceRef.getImpl().getPortNo();
1049  return getValueByFieldID(builder, module.getArgument(portNum),
1050  xmrSrcTarget->fieldIdx);
1051  })
1052  // The target is something else.
1053  .Default([&](Operation *op) -> std::optional<Value> {
1054  auto module = cast<FModuleOp>(sourceRef.getModule());
1055  builder.setInsertionPointToEnd(module.getBodyBlock());
1056  auto is = dyn_cast<hw::InnerSymbolOpInterface>(op);
1057  // Resolve InnerSymbol references to their target result.
1058  if (is && is.getTargetResult())
1059  return getValueByFieldID(builder, is.getTargetResult(),
1060  xmrSrcTarget->fieldIdx);
1061  if (sourceRef.getOp()->getNumResults() != 1) {
1062  op->emitOpError()
1063  << "cannot be used as a target of the Grand Central View \""
1064  << defName.getValue()
1065  << "\" because it does not have exactly one result";
1066  return std::nullopt;
1067  }
1068  return getValueByFieldID(builder, sourceRef.getOp()->getResult(0),
1069  xmrSrcTarget->fieldIdx);
1070  });
1071 
1072  // Exit if there was an error in the source.
1073  if (!source)
1074  return std::nullopt;
1075 
1076  // Compute the sink of this Wiring Problem. The final sink will eventually
1077  // be a SystemVerilog Interface. However, this cannot exist until the
1078  // GrandCentral pass runs. Create an undriven WireOp and use that as the
1079  // sink. The WireOp will be driven later when the Wiring Problem is
1080  // resolved. Apply the scattered element annotation to this directly to save
1081  // having to reprocess this in LowerAnnotations.
1082  auto companionMod =
1083  cast<FModuleOp>(resolvePath(companionAttr.getValue(), state.circuit,
1084  state.symTbl, state.targetCaches)
1085  ->ref.getOp());
1086  builder.setInsertionPointToEnd(companionMod.getBodyBlock());
1087  // Sink type must be passive. It's required to be converted to a NodeOp by
1088  // the wiring problem solving, and later checked to be a Node.
1089  // This also ensures passive sink so works equally well w/ or w/o probes.
1090  auto sinkType = source->getType();
1091  if (auto baseSinkType = type_dyn_cast<FIRRTLBaseType>(sinkType))
1092  sinkType = baseSinkType.getPassiveType();
1093  auto sink = builder.create<WireOp>(sinkType, name);
1094  state.targetCaches.insertOp(sink);
1095  AnnotationSet annotations(context);
1096  annotations.addAnnotations(
1097  {DictionaryAttr::getWithSorted(context, elementScattered)});
1098  annotations.applyToOperation(sink);
1099 
1100  // Append this new Wiring Problem to the ApplyState. The Wiring Problem
1101  // will be resolved to bore RefType ports before LowerAnnotations finishes.
1102  state.wiringProblems.push_back({*source, sink.getResult(),
1103  (path + "__bore").str(),
1105 
1106  return DictionaryAttr::getWithSorted(context, elementIface);
1107  }
1108 
1109  // An AugmentedVectorType looks like:
1110  // "elements": Seq[AugmentedType]
1111  if (classBase == "VectorType") {
1112  auto elementsAttr =
1113  tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
1114  if (!elementsAttr)
1115  return std::nullopt;
1116  SmallVector<Attribute> elements;
1117  for (auto [i, elt] : llvm::enumerate(elementsAttr)) {
1118  auto eltAttr = parseAugmentedType(
1119  state, cast<DictionaryAttr>(elt), root, companion, name,
1120  StringAttr::get(context, ""), id, std::nullopt, clazz, companionAttr,
1121  path + "_" + Twine(i));
1122  if (!eltAttr)
1123  return std::nullopt;
1124  elements.push_back(*eltAttr);
1125  }
1126  NamedAttrList attrs;
1127  attrs.append("class", classAttr);
1128  if (description)
1129  attrs.append("description", *description);
1130  attrs.append("elements", ArrayAttr::get(context, elements));
1131  attrs.append("name", name);
1132  return DictionaryAttr::getWithSorted(context, attrs);
1133  }
1134 
1135  // Anything else is unexpected or a user error if they manually wrote
1136  // annotations. Print an error and error out.
1137  mlir::emitError(loc, "found unknown AugmentedType '" + classAttr.getValue() +
1138  "' (Did you misspell it?)")
1139  .attachNote()
1140  << "see annotation: " << augmentedType;
1141  return std::nullopt;
1142 }
1143 
1144 LogicalResult circt::firrtl::applyGCTView(const AnnoPathValue &target,
1145  DictionaryAttr anno,
1146  ApplyState &state) {
1147 
1148  auto id = state.newID();
1149  auto *context = state.circuit.getContext();
1150  auto loc = state.circuit.getLoc();
1151  NamedAttrList companionAttrs;
1152  companionAttrs.append("class", StringAttr::get(context, companionAnnoClass));
1153  companionAttrs.append("id", id);
1154  auto viewAttr =
1155  tryGetAs<DictionaryAttr>(anno, anno, "view", loc, viewAnnoClass);
1156  if (!viewAttr)
1157  return failure();
1158  auto name = tryGetAs<StringAttr>(anno, anno, "name", loc, viewAnnoClass);
1159  if (!name)
1160  return failure();
1161  companionAttrs.append("name", name);
1162  auto companionAttr =
1163  tryGetAs<StringAttr>(anno, anno, "companion", loc, viewAnnoClass);
1164  if (!companionAttr)
1165  return failure();
1166  companionAttrs.append("target", companionAttr);
1167  state.addToWorklistFn(DictionaryAttr::get(context, companionAttrs));
1168 
1169  auto prunedAttr =
1170  parseAugmentedType(state, viewAttr, anno, companionAttr.getValue(), name,
1171  {}, id, {}, viewAnnoClass, companionAttr, Twine(name));
1172  if (!prunedAttr)
1173  return failure();
1174 
1175  AnnotationSet annotations(state.circuit);
1176  annotations.addAnnotations({*prunedAttr});
1177  annotations.applyToOperation(state.circuit);
1178 
1179  return success();
1180 }
1181 
1182 //===----------------------------------------------------------------------===//
1183 // GrandCentralPass Implementation
1184 //===----------------------------------------------------------------------===//
1185 
1186 std::optional<Attribute> GrandCentralPass::fromAttr(Attribute attr) {
1187  auto dict = dyn_cast<DictionaryAttr>(attr);
1188  if (!dict) {
1189  emitCircuitError() << "attribute is not a dictionary: " << attr << "\n";
1190  return std::nullopt;
1191  }
1192 
1193  auto clazz = dict.getAs<StringAttr>("class");
1194  if (!clazz) {
1195  emitCircuitError() << "missing 'class' key in " << dict << "\n";
1196  return std::nullopt;
1197  }
1198 
1199  auto classBase = clazz.getValue();
1200  classBase.consume_front("sifive.enterprise.grandcentral.Augmented");
1201 
1202  if (classBase == "BundleType") {
1203  if (dict.getAs<StringAttr>("defName") && dict.getAs<ArrayAttr>("elements"))
1204  return AugmentedBundleTypeAttr::get(&getContext(), dict);
1205  emitCircuitError() << "has an invalid AugmentedBundleType that does not "
1206  "contain 'defName' and 'elements' fields: "
1207  << dict;
1208  } else if (classBase == "VectorType") {
1209  if (dict.getAs<StringAttr>("name") && dict.getAs<ArrayAttr>("elements"))
1210  return AugmentedVectorTypeAttr::get(&getContext(), dict);
1211  emitCircuitError() << "has an invalid AugmentedVectorType that does not "
1212  "contain 'name' and 'elements' fields: "
1213  << dict;
1214  } else if (classBase == "GroundType") {
1215  auto id = dict.getAs<IntegerAttr>("id");
1216  auto name = dict.getAs<StringAttr>("name");
1217  if (id && leafMap.count(id) && name)
1218  return AugmentedGroundTypeAttr::get(&getContext(), dict);
1219  if (!id || !name)
1220  emitCircuitError() << "has an invalid AugmentedGroundType that does not "
1221  "contain 'id' and 'name' fields: "
1222  << dict;
1223  if (id && !leafMap.count(id))
1224  emitCircuitError() << "has an AugmentedGroundType with 'id == "
1225  << id.getValue().getZExtValue()
1226  << "' that does not have a scattered leaf to connect "
1227  "to in the circuit "
1228  "(was the leaf deleted or constant prop'd away?)";
1229  } else {
1230  emitCircuitError() << "has an invalid AugmentedType";
1231  }
1232  return std::nullopt;
1233 }
1234 
1235 bool GrandCentralPass::traverseField(
1236  Attribute field, IntegerAttr id, VerbatimBuilder &path,
1237  SmallVector<VerbatimXMRbuilder> &xmrElems,
1238  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1239  return TypeSwitch<Attribute, bool>(field)
1240  .Case<AugmentedGroundTypeAttr>([&](AugmentedGroundTypeAttr ground) {
1241  auto [fieldRef, sym] = leafMap.lookup(ground.getID());
1242  hw::HierPathOp nla;
1243  if (sym)
1244  nla = nlaTable->getNLA(sym.getAttr());
1245  Value leafValue = fieldRef.getValue();
1246  assert(leafValue && "leafValue not found");
1247 
1248  auto companionModule = companionIDMap.lookup(id).companion;
1249  igraph::ModuleOpInterface enclosing =
1250  getEnclosingModule(leafValue, sym);
1251 
1252  auto tpe = type_cast<FIRRTLBaseType>(leafValue.getType());
1253 
1254  // If the type is zero-width then do not emit an XMR.
1255  if (!tpe.getBitWidthOrSentinel())
1256  return true;
1257 
1258  // The leafValue is assumed to conform to a very specific pattern:
1259  //
1260  // 1) The leaf value is in the companion.
1261  // 2) The leaf value is a NodeOp
1262  //
1263  // Anything else means that there is an error or the IR is somehow using
1264  // "old-style" Annotations to encode a Grand Central View. This
1265  // _really_ should be impossible to hit given that LowerAnnotations must
1266  // generate code that conforms to the check here.
1267  auto *nodeOp = leafValue.getDefiningOp();
1268  if (companionModule != enclosing) {
1269  auto diag = companionModule->emitError()
1270  << "Grand Central View \""
1271  << companionIDMap.lookup(id).name
1272  << "\" is invalid because a leaf is not inside the "
1273  "companion module";
1274  diag.attachNote(leafValue.getLoc())
1275  << "the leaf value is declared here";
1276  if (nodeOp) {
1277  auto leafModule = nodeOp->getParentOfType<FModuleOp>();
1278  diag.attachNote(leafModule.getLoc())
1279  << "the leaf value is inside this module";
1280  }
1281  return false;
1282  }
1283 
1284  if (!isa<NodeOp>(nodeOp)) {
1285  emitError(leafValue.getLoc())
1286  << "Grand Central View \"" << companionIDMap.lookup(id).name
1287  << "\" has an invalid leaf value (this must be a node)";
1288  return false;
1289  }
1290 
1291  /// Increment all the indices inside `{{`, `}}` by one. This is to
1292  /// indicate that a value is added to the `substitutions` of the
1293  /// verbatim op, other than the symbols.
1294  auto getStrAndIncrementIds = [&](StringRef base) -> StringAttr {
1295  SmallString<128> replStr;
1296  StringRef begin = "{{";
1297  StringRef end = "}}";
1298  // The replacement string.
1299  size_t from = 0;
1300  while (from < base.size()) {
1301  // Search for the first `{{` and `}}`.
1302  size_t beginAt = base.find(begin, from);
1303  size_t endAt = base.find(end, from);
1304  // If not found, then done.
1305  if (beginAt == StringRef::npos || endAt == StringRef::npos ||
1306  (beginAt > endAt)) {
1307  replStr.append(base.substr(from));
1308  break;
1309  }
1310  // Copy the string as is, until the `{{`.
1311  replStr.append(base.substr(from, beginAt - from));
1312  // Advance `from` to the character after the `}}`.
1313  from = endAt + 2;
1314  auto idChar = base.substr(beginAt + 2, endAt - beginAt - 2);
1315  int idNum;
1316  bool failed = idChar.getAsInteger(10, idNum);
1317  (void)failed;
1318  assert(!failed && "failed to parse integer from verbatim string");
1319  // Now increment the id and append.
1320  replStr.append("{{");
1321  Twine(idNum + 1).toVector(replStr);
1322  replStr.append("}}");
1323  }
1324  return StringAttr::get(&getContext(), "assign " + replStr + ";");
1325  };
1326 
1327  // This is the new style of XMRs using RefTypes. The value substitution
1328  // index is set to -1, as it will be incremented when generating the
1329  // string.
1330  // Generate the path from the LCA to the module that contains the leaf.
1331  path += " = {{-1}}";
1333  // Assemble the verbatim op.
1334  xmrElems.emplace_back(
1335  nodeOp->getOperand(0), getStrAndIncrementIds(path.getString()),
1336  ArrayAttr::get(&getContext(), path.getSymbols()), companionModule);
1337  return true;
1338  })
1339  .Case<AugmentedVectorTypeAttr>([&](auto vector) {
1340  bool notFailed = true;
1341  auto elements = vector.getElements();
1342  for (size_t i = 0, e = elements.size(); i != e; ++i) {
1343  auto field = fromAttr(elements[i]);
1344  if (!field)
1345  return false;
1346  notFailed &= traverseField(
1347  *field, id, path.snapshot().append("[" + Twine(i) + "]"),
1348  xmrElems, interfaceBuilder);
1349  }
1350  return notFailed;
1351  })
1352  .Case<AugmentedBundleTypeAttr>([&](AugmentedBundleTypeAttr bundle) {
1353  bool anyFailed = true;
1354  for (auto element : bundle.getElements()) {
1355  auto field = fromAttr(element);
1356  if (!field)
1357  return false;
1358  auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1359  if (!name)
1360  name = cast<DictionaryAttr>(element).getAs<StringAttr>("defName");
1361  anyFailed &= traverseField(
1362  *field, id, path.snapshot().append("." + name.getValue()),
1363  xmrElems, interfaceBuilder);
1364  }
1365 
1366  return anyFailed;
1367  })
1368  .Default([](auto a) { return true; });
1369 }
1370 
1371 std::optional<TypeSum> GrandCentralPass::computeField(
1372  Attribute field, IntegerAttr id, VerbatimBuilder &path,
1373  SmallVector<VerbatimXMRbuilder> &xmrElems,
1374  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1375  return TypeSwitch<Attribute, std::optional<TypeSum>>(field)
1376  .Case<AugmentedGroundTypeAttr>(
1377  [&](AugmentedGroundTypeAttr ground) -> std::optional<TypeSum> {
1378  // Traverse to generate mappings.
1379  if (!traverseField(field, id, path, xmrElems, interfaceBuilder))
1380  return std::nullopt;
1381  FieldRef fieldRef = leafMap.lookup(ground.getID()).field;
1382  auto value = fieldRef.getValue();
1383  auto fieldID = fieldRef.getFieldID();
1384  auto tpe = firrtl::type_cast<FIRRTLBaseType>(
1386  fieldID));
1387  if (!tpe.isGround()) {
1388  value.getDefiningOp()->emitOpError()
1389  << "cannot be added to interface with id '"
1390  << id.getValue().getZExtValue()
1391  << "' because it is not a ground type";
1392  return std::nullopt;
1393  }
1394  return TypeSum(IntegerType::get(getOperation().getContext(),
1395  tpe.getBitWidthOrSentinel()));
1396  })
1397  .Case<AugmentedVectorTypeAttr>(
1398  [&](AugmentedVectorTypeAttr vector) -> std::optional<TypeSum> {
1399  auto elements = vector.getElements();
1400  if (elements.empty())
1401  llvm::report_fatal_error(
1402  "unexpected empty augmented vector in GrandCentral View");
1403  auto firstElement = fromAttr(elements[0]);
1404  if (!firstElement)
1405  return std::nullopt;
1406  auto elementType = computeField(
1407  *firstElement, id, path.snapshot().append("[" + Twine(0) + "]"),
1408  xmrElems, interfaceBuilder);
1409  if (!elementType)
1410  return std::nullopt;
1411 
1412  for (size_t i = 1, e = elements.size(); i != e; ++i) {
1413  auto subField = fromAttr(elements[i]);
1414  if (!subField)
1415  return std::nullopt;
1416  (void)traverseField(*subField, id,
1417  path.snapshot().append("[" + Twine(i) + "]"),
1418  xmrElems, interfaceBuilder);
1419  }
1420 
1421  if (auto *tpe = std::get_if<Type>(&*elementType))
1422  return TypeSum(
1423  hw::UnpackedArrayType::get(*tpe, elements.getValue().size()));
1424  auto str = std::get<VerbatimType>(*elementType);
1425  str.dimensions.push_back(elements.getValue().size());
1426  return TypeSum(str);
1427  })
1428  .Case<AugmentedBundleTypeAttr>(
1429  [&](AugmentedBundleTypeAttr bundle) -> TypeSum {
1430  auto ifaceName =
1431  traverseBundle(bundle, id, path, xmrElems, interfaceBuilder);
1432  assert(ifaceName && *ifaceName);
1433  return VerbatimType({ifaceName->str(), true});
1434  });
1435 }
1436 
1437 /// Traverse an Annotation that is an AugmentedBundleType. During traversal,
1438 /// construct any discovered SystemVerilog interfaces. If this is the root
1439 /// interface, instantiate that interface in the companion. Recurse into fields
1440 /// of the AugmentedBundleType to construct nested interfaces and generate
1441 /// stringy-typed SystemVerilog hierarchical references to drive the
1442 /// interface. Returns false on any failure and true on success.
1443 std::optional<StringAttr> GrandCentralPass::traverseBundle(
1444  AugmentedBundleTypeAttr bundle, IntegerAttr id, VerbatimBuilder &path,
1445  SmallVector<VerbatimXMRbuilder> &xmrElems,
1446  SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1447 
1448  unsigned lastIndex = interfaceBuilder.size();
1449  auto iFaceName = StringAttr::get(
1450  &getContext(), getNamespace().newName(getInterfaceName(bundle)));
1451  interfaceBuilder.emplace_back(iFaceName, id);
1452 
1453  for (auto element : bundle.getElements()) {
1454  auto field = fromAttr(element);
1455  if (!field)
1456  return std::nullopt;
1457 
1458  auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1459  // auto signalSym = hw::InnerRefAttr::get(iface.sym_nameAttr(), name);
1460  // TODO: The `append(name.getValue())` in the following should actually be
1461  // `append(signalSym)`, but this requires that `computeField` and the
1462  // functions it calls always return a type for which we can construct an
1463  // `InterfaceSignalOp`. Since nested interface instances are currently
1464  // busted (due to the interface being a symbol table), this doesn't work at
1465  // the moment. Passing a `name` works most of the time, but can be brittle
1466  // if the interface field requires renaming in the output (e.g. due to
1467  // naming conflicts).
1468  auto elementType = computeField(
1469  *field, id, path.snapshot().append(".").append(name.getValue()),
1470  xmrElems, interfaceBuilder);
1471  if (!elementType)
1472  return std::nullopt;
1473  StringAttr description =
1474  cast<DictionaryAttr>(element).getAs<StringAttr>("description");
1475  interfaceBuilder[lastIndex].elementsList.emplace_back(description, name,
1476  *elementType);
1477  }
1478  return iFaceName;
1479 }
1480 
1481 /// Return the module that is associated with this value. Use the cached/lazily
1482 /// constructed symbol table to make this fast.
1483 igraph::ModuleOpInterface
1484 GrandCentralPass::getEnclosingModule(Value value, FlatSymbolRefAttr sym) {
1485  if (auto blockArg = dyn_cast<BlockArgument>(value))
1486  return cast<igraph::ModuleOpInterface>(blockArg.getOwner()->getParentOp());
1487 
1488  auto *op = value.getDefiningOp();
1489  if (InstanceOp instance = dyn_cast<InstanceOp>(op))
1490  return getSymbolTable().lookup<igraph::ModuleOpInterface>(
1491  instance.getModuleNameAttr().getValue());
1492 
1493  return op->getParentOfType<igraph::ModuleOpInterface>();
1494 }
1495 
1496 /// This method contains the business logic of this pass.
1497 void GrandCentralPass::runOnOperation() {
1498  LLVM_DEBUG(debugPassHeader(this) << "\n");
1499 
1500  CircuitOp circuitOp = getOperation();
1501 
1502  // Look at the circuit annotations to do two things:
1503  //
1504  // 1. Determine extraction information (directory and filename).
1505  // 2. Populate a worklist of all annotations that encode interfaces.
1506  //
1507  // Remove annotations encoding interfaces, but leave extraction information as
1508  // this may be needed by later passes.
1509  SmallVector<Annotation> worklist;
1510  bool removalError = false;
1511  AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
1512  if (anno.isClass(augmentedBundleTypeClass)) {
1513  // If we are in "Instantiate" companion mode, then we don't need to
1514  // create the interface, so we can skip adding it to the worklist. This
1515  // is a janky hack for situations where you want to synthesize assertion
1516  // logic included in the companion, but don't want to have a dead
1517  // interface hanging around (or have problems with tools understanding
1518  // interfaces).
1519  if (companionMode != CompanionMode::Instantiate)
1520  worklist.push_back(anno);
1521  ++numAnnosRemoved;
1522  return true;
1523  }
1524  if (anno.isClass(extractGrandCentralClass)) {
1525  if (maybeExtractInfo) {
1526  emitCircuitError("more than one 'ExtractGrandCentralAnnotation' was "
1527  "found, but exactly one must be provided");
1528  removalError = true;
1529  return false;
1530  }
1531 
1532  auto directory = anno.getMember<StringAttr>("directory");
1533  auto filename = anno.getMember<StringAttr>("filename");
1534  if (!directory || !filename) {
1535  emitCircuitError()
1536  << "contained an invalid 'ExtractGrandCentralAnnotation' that does "
1537  "not contain 'directory' and 'filename' fields: "
1538  << anno.getDict();
1539  removalError = true;
1540  return false;
1541  }
1542  if (directory.getValue().empty())
1543  directory = StringAttr::get(circuitOp.getContext(), ".");
1544 
1545  maybeExtractInfo = {directory, filename};
1546  // Do not delete this annotation. Extraction info may be needed later.
1547  return false;
1548  }
1550  if (maybeHierarchyFileYAML) {
1551  emitCircuitError("more than one 'GrandCentralHierarchyFileAnnotation' "
1552  "was found, but zero or one may be provided");
1553  removalError = true;
1554  return false;
1555  }
1556 
1557  auto filename = anno.getMember<StringAttr>("filename");
1558  if (!filename) {
1559  emitCircuitError()
1560  << "contained an invalid 'GrandCentralHierarchyFileAnnotation' "
1561  "that does not contain 'directory' and 'filename' fields: "
1562  << anno.getDict();
1563  removalError = true;
1564  return false;
1565  }
1566 
1567  maybeHierarchyFileYAML = filename;
1568  ++numAnnosRemoved;
1569  return true;
1570  }
1571  if (anno.isClass(testBenchDirAnnoClass)) {
1572  testbenchDir = anno.getMember<StringAttr>("dirname");
1573  return false;
1574  }
1575  return false;
1576  });
1577 
1578  // Find the DUT if it exists. This needs to be known before the circuit is
1579  // walked.
1580  for (auto mod : circuitOp.getOps<FModuleLike>()) {
1581  if (failed(extractDUT(mod, dut)))
1582  removalError = true;
1583  }
1584 
1585  if (removalError)
1586  return signalPassFailure();
1587 
1588  LLVM_DEBUG({
1589  llvm::dbgs() << "Extraction Info:\n";
1590  if (maybeExtractInfo)
1591  llvm::dbgs() << " directory: " << maybeExtractInfo->directory << "\n"
1592  << " filename: " << maybeExtractInfo->bindFilename << "\n";
1593  else
1594  llvm::dbgs() << " <none>\n";
1595  llvm::dbgs() << "DUT: ";
1596  if (dut)
1597  llvm::dbgs() << dut.getModuleName() << "\n";
1598  else
1599  llvm::dbgs() << "<none>\n";
1600  llvm::dbgs()
1601  << "Hierarchy File Info (from GrandCentralHierarchyFileAnnotation):\n"
1602  << " filename: ";
1603  if (maybeHierarchyFileYAML)
1604  llvm::dbgs() << *maybeHierarchyFileYAML;
1605  else
1606  llvm::dbgs() << "<none>";
1607  llvm::dbgs() << "\n";
1608  });
1609 
1610  // Exit immediately if no annotations indicative of interfaces that need to be
1611  // built exist. However, still generate the YAML file if the annotation for
1612  // this was passed in because some flows expect this.
1613  if (worklist.empty()) {
1614  SmallVector<sv::InterfaceOp, 0> interfaceVec;
1615  emitHierarchyYamlFile(interfaceVec);
1616  return markAllAnalysesPreserved();
1617  }
1618 
1619  // Setup the builder to create ops _inside the FIRRTL circuit_. This is
1620  // necessary because interfaces and interface instances are created.
1621  // Instances link to their definitions via symbols and we don't want to
1622  // break this.
1623  auto builder = OpBuilder::atBlockEnd(circuitOp.getBodyBlock());
1624 
1625  // Maybe get an "id" from an Annotation. Generate error messages on the op if
1626  // no "id" exists.
1627  auto getID = [&](Operation *op,
1628  Annotation annotation) -> std::optional<IntegerAttr> {
1629  auto id = annotation.getMember<IntegerAttr>("id");
1630  if (!id) {
1631  op->emitOpError()
1632  << "contained a malformed "
1633  "'sifive.enterprise.grandcentral.AugmentedGroundType' annotation "
1634  "that did not contain an 'id' field";
1635  removalError = true;
1636  return std::nullopt;
1637  }
1638  return id;
1639  };
1640 
1641  /// TODO: Handle this differently to allow construction of an options
1642  auto instancePathCache = InstancePathCache(getAnalysis<InstanceGraph>());
1643  instancePaths = &instancePathCache;
1644  instanceInfo = &getAnalysis<InstanceInfo>();
1645 
1646  // Maybe return the lone instance of a module. Generate errors on the op if
1647  // the module is not instantiated or is multiply instantiated.
1648  auto exactlyOneInstance = [&](FModuleOp op,
1649  StringRef msg) -> std::optional<InstanceOp> {
1650  auto *node = instancePaths->instanceGraph[op];
1651 
1652  switch (node->getNumUses()) {
1653  case 0:
1654  op->emitOpError() << "is marked as a GrandCentral '" << msg
1655  << "', but is never instantiated";
1656  return std::nullopt;
1657  case 1:
1658  return cast<InstanceOp>(*(*node->uses().begin())->getInstance());
1659  default:
1660  auto diag = op->emitOpError()
1661  << "is marked as a GrandCentral '" << msg
1662  << "', but it is instantiated more than once";
1663  for (auto *instance : node->uses())
1664  diag.attachNote(instance->getInstance()->getLoc())
1665  << "it is instantiated here";
1666  return std::nullopt;
1667  }
1668  };
1669 
1670  nlaTable = &getAnalysis<NLATable>();
1671 
1672  /// Walk the circuit and extract all information related to scattered Grand
1673  /// Central annotations. This is used to populate: (1) the companionIDMap and
1674  /// (2) the leafMap. Annotations are removed as they are discovered and if
1675  /// they are not malformed.
1676  DenseSet<Operation *> modulesToDelete;
1677  circuitOp.walk([&](Operation *op) {
1678  TypeSwitch<Operation *>(op)
1679  .Case<RegOp, RegResetOp, WireOp, NodeOp>([&](auto op) {
1680  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1681  if (!annotation.isClass(augmentedGroundTypeClass))
1682  return false;
1683  auto maybeID = getID(op, annotation);
1684  if (!maybeID)
1685  return false;
1686  auto sym =
1687  annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
1688  leafMap[*maybeID] = {{op.getResult(), annotation.getFieldID()},
1689  sym};
1690  ++numAnnosRemoved;
1691  return true;
1692  });
1693  })
1694  // TODO: Figure out what to do with this.
1695  .Case<InstanceOp>([&](auto op) {
1696  AnnotationSet::removePortAnnotations(op, [&](unsigned i,
1697  Annotation annotation) {
1698  if (!annotation.isClass(augmentedGroundTypeClass))
1699  return false;
1700  op.emitOpError()
1701  << "is marked as an interface element, but this should be "
1702  "impossible due to how the Chisel Grand Central API works";
1703  removalError = true;
1704  return false;
1705  });
1706  })
1707  .Case<MemOp>([&](auto op) {
1708  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1709  if (!annotation.isClass(augmentedGroundTypeClass))
1710  return false;
1711  op.emitOpError()
1712  << "is marked as an interface element, but this does not make "
1713  "sense (is there a scattering bug or do you have a "
1714  "malformed hand-crafted MLIR circuit?)";
1715  removalError = true;
1716  return false;
1717  });
1719  op, [&](unsigned i, Annotation annotation) {
1720  if (!annotation.isClass(augmentedGroundTypeClass))
1721  return false;
1722  op.emitOpError()
1723  << "has port '" << i
1724  << "' marked as an interface element, but this does not "
1725  "make sense (is there a scattering bug or do you have a "
1726  "malformed hand-crafted MLIR circuit?)";
1727  removalError = true;
1728  return false;
1729  });
1730  })
1731  .Case<FModuleOp>([&](FModuleOp op) {
1732  // Handle annotations on the ports.
1733  AnnotationSet::removePortAnnotations(op, [&](unsigned i,
1734  Annotation annotation) {
1735  if (!annotation.isClass(augmentedGroundTypeClass))
1736  return false;
1737  auto maybeID = getID(op, annotation);
1738  if (!maybeID)
1739  return false;
1740  auto sym =
1741  annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
1742  leafMap[*maybeID] = {{op.getArgument(i), annotation.getFieldID()},
1743  sym};
1744  ++numAnnosRemoved;
1745  return true;
1746  });
1747 
1748  // Handle annotations on the module.
1749  AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
1750  if (!annotation.getClass().starts_with(viewAnnoClass))
1751  return false;
1752  auto isNonlocal = annotation.getMember<FlatSymbolRefAttr>(
1753  "circt.nonlocal") != nullptr;
1754  auto name = annotation.getMember<StringAttr>("name");
1755  auto id = annotation.getMember<IntegerAttr>("id");
1756  if (!id) {
1757  op.emitOpError()
1758  << "has a malformed "
1759  "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
1760  "not contain an 'id' field with an 'IntegerAttr' value";
1761  goto FModuleOp_error;
1762  }
1763  if (!name) {
1764  op.emitOpError()
1765  << "has a malformed "
1766  "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
1767  "not contain a 'name' field with a 'StringAttr' value";
1768  goto FModuleOp_error;
1769  }
1770 
1771  // If this is a companion, then:
1772  // 1. Insert it into the companion map
1773  // 2. Create a new mapping module.
1774  // 3. Instantiate the mapping module in the companion.
1775  // 4. Check that the companion is instantiated exactly once.
1776  // 5. Set attributes on that lone instance so it will become a
1777  // bind if extraction information was provided. If a DUT is
1778  // known, then anything in the test harness will not be
1779  // extracted.
1780  if (annotation.getClass() == companionAnnoClass) {
1781  builder.setInsertionPointToEnd(circuitOp.getBodyBlock());
1782 
1783  companionIDMap[id] = {name.getValue(), op, isNonlocal};
1784 
1785  // Assert that the companion is instantiated once and only once.
1786  auto instance = exactlyOneInstance(op, "companion");
1787  if (!instance)
1788  goto FModuleOp_error;
1789 
1790  // Companions are only allowed to take inputs.
1791  for (auto [i, result] : llvm::enumerate(instance->getResults())) {
1792  if (instance->getPortDirection(i) == Direction::In)
1793  continue;
1794  // Do not allow any outputs in the drop mode.
1795  auto ty = result.getType();
1796  if (isa<RefType>(ty) && companionMode != CompanionMode::Drop)
1797  continue;
1798  op.emitOpError()
1799  << "companion instance cannot have output ports";
1800  goto FModuleOp_error;
1801  }
1802 
1803  // If no extraction info was provided, exit. Otherwise, setup the
1804  // lone instance of the companion to be lowered as a bind.
1805  if (!maybeExtractInfo) {
1806  ++numAnnosRemoved;
1807  return true;
1808  }
1809 
1810  // If the companion is instantiated above the DUT, then don't
1811  // extract it.
1812  if (dut && !instancePaths->instanceGraph.isAncestor(op, dut)) {
1813  ++numAnnosRemoved;
1814  return true;
1815  }
1816 
1817  // Look for any modules/extmodules _only_ instantiated by the
1818  // companion. If these have no output file attribute, then mark
1819  // them as being extracted into the Grand Central directory.
1820  InstanceGraphNode *companionNode =
1821  instancePaths->instanceGraph.lookup(op);
1822 
1823  LLVM_DEBUG({
1824  llvm::dbgs()
1825  << "Found companion module: "
1826  << companionNode->getModule().getModuleName() << "\n"
1827  << " submodules exclusively instantiated "
1828  "(including companion):\n";
1829  });
1830 
1831  if (companionMode == CompanionMode::Drop) {
1832  // Delete the instance if companions are disabled.
1833  OpBuilder builder(&getContext());
1834  for (auto port : instance->getResults()) {
1835  builder.setInsertionPointAfterValue(port);
1836  auto wire =
1837  builder.create<WireOp>(port.getLoc(), port.getType());
1838  port.replaceAllUsesWith(wire.getResult());
1839  }
1840  instance->erase();
1841  } else {
1842  // Lower the companion to a bind unless the user told us
1843  // explicitly not to.
1844  if (companionMode == CompanionMode::Bind)
1845  (*instance)->setAttr("lowerToBind", builder.getUnitAttr());
1846 
1847  (*instance)->setAttr(
1848  "output_file",
1849  hw::OutputFileAttr::getFromFilename(
1850  &getContext(),
1851  maybeExtractInfo->bindFilename.getValue(),
1852  /*excludeFromFileList=*/true));
1853  }
1854 
1855  for (auto &node : llvm::depth_first(companionNode)) {
1856  auto mod = node->getModule();
1857 
1858  // Check to see if we should change the output directory of a
1859  // module. Only update in the following conditions:
1860  // 1) The module is the companion.
1861  // 2) The module is NOT instantiated by the effective DUT or
1862  // is under a bind.
1863  auto *modNode = instancePaths->instanceGraph.lookup(mod);
1864  if (modNode != companionNode &&
1865  instanceInfo->anyInstanceInEffectiveDesign(
1866  modNode->getModule()))
1867  continue;
1868 
1869  LLVM_DEBUG({
1870  llvm::dbgs()
1871  << " - module: " << mod.getModuleName() << "\n";
1872  });
1873 
1874  if (auto extmodule = dyn_cast<FExtModuleOp>(*mod)) {
1875  for (auto anno : AnnotationSet(extmodule)) {
1876  if (companionMode == CompanionMode::Drop) {
1877  modulesToDelete.insert(mod);
1878  break;
1879  }
1880  if (!anno.isClass(blackBoxInlineAnnoClass) &&
1882  continue;
1883  if (extmodule->hasAttr("output_file"))
1884  break;
1885  extmodule->setAttr(
1886  "output_file",
1887  hw::OutputFileAttr::getAsDirectory(
1888  &getContext(),
1889  maybeExtractInfo->directory.getValue()));
1890  break;
1891  }
1892  continue;
1893  }
1894 
1895  if (companionMode == CompanionMode::Drop) {
1896  modulesToDelete.insert(mod);
1897  } else {
1898  // Move this module under the Grand Central output directory
1899  // if no pre-existing output file information is present.
1900  if (!mod->hasAttr("output_file")) {
1901  mod->setAttr("output_file",
1902  hw::OutputFileAttr::getAsDirectory(
1903  &getContext(),
1904  maybeExtractInfo->directory.getValue(),
1905  /*excludeFromFileList=*/true,
1906  /*includeReplicatedOps=*/true));
1907  mod->setAttr("comment", builder.getStringAttr(
1908  "VCS coverage exclude_file"));
1909  }
1910  }
1911  }
1912 
1913  ++numAnnosRemoved;
1914  return true;
1915  }
1916 
1917  op.emitOpError()
1918  << "unknown annotation class: " << annotation.getDict();
1919 
1920  FModuleOp_error:
1921  removalError = true;
1922  return false;
1923  });
1924  });
1925  });
1926 
1927  if (removalError)
1928  return signalPassFailure();
1929 
1930  if (companionMode == CompanionMode::Drop) {
1931  for (auto *mod : modulesToDelete) {
1932  auto name = cast<FModuleLike>(mod).getModuleNameAttr();
1933 
1934  DenseSet<hw::HierPathOp> nlas;
1935  nlaTable->getNLAsInModule(name, nlas);
1936  nlaTable->removeNLAsfromModule(nlas, name);
1937  for (auto nla : nlas) {
1938  if (nla.root() == name)
1939  nla.erase();
1940  }
1941 
1942  mod->erase();
1943  }
1944 
1945  SmallVector<sv::InterfaceOp, 0> interfaceVec;
1946  emitHierarchyYamlFile(interfaceVec);
1947  return;
1948  }
1949 
1950  LLVM_DEBUG({
1951  // Print out the companion map and all leaf values that were discovered.
1952  // Sort these by their keys before printing to make this easier to read.
1953  SmallVector<IntegerAttr> ids;
1954  auto sort = [&ids]() {
1955  llvm::sort(ids, [](IntegerAttr a, IntegerAttr b) {
1956  return a.getValue().getZExtValue() < b.getValue().getZExtValue();
1957  });
1958  };
1959  for (auto tuple : companionIDMap)
1960  ids.push_back(cast<IntegerAttr>(tuple.first));
1961  sort();
1962  llvm::dbgs() << "companionIDMap:\n";
1963  for (auto id : ids) {
1964  auto value = companionIDMap.lookup(id);
1965  llvm::dbgs() << " - " << id.getValue() << ": "
1966  << value.companion.getName() << " -> " << value.name << "\n";
1967  }
1968  ids.clear();
1969  for (auto tuple : leafMap)
1970  ids.push_back(cast<IntegerAttr>(tuple.first));
1971  sort();
1972  llvm::dbgs() << "leafMap:\n";
1973  for (auto id : ids) {
1974  auto fieldRef = leafMap.lookup(id).field;
1975  auto value = fieldRef.getValue();
1976  auto fieldID = fieldRef.getFieldID();
1977  if (auto blockArg = dyn_cast<BlockArgument>(value)) {
1978  FModuleOp module = cast<FModuleOp>(blockArg.getOwner()->getParentOp());
1979  llvm::dbgs() << " - " << id.getValue() << ": "
1980  << module.getName() + ">" +
1981  module.getPortName(blockArg.getArgNumber());
1982  if (fieldID)
1983  llvm::dbgs() << ", fieldID=" << fieldID;
1984  llvm::dbgs() << "\n";
1985  } else {
1986  llvm::dbgs() << " - " << id.getValue() << ": "
1987  << cast<StringAttr>(value.getDefiningOp()->getAttr("name"))
1988  .getValue();
1989  if (fieldID)
1990  llvm::dbgs() << ", fieldID=" << fieldID;
1991  llvm::dbgs() << "\n";
1992  }
1993  }
1994  });
1995 
1996  // Now, iterate over the worklist of interface-encoding annotations to create
1997  // the interface and all its sub-interfaces (interfaces that it instantiates),
1998  // instantiate the top-level interface, and generate a "mappings file" that
1999  // will use XMRs to drive the interface. If extraction info is available,
2000  // then the top-level instantiate interface will be marked for extraction via
2001  // a SystemVerilog bind.
2002  SmallVector<sv::InterfaceOp, 2> interfaceVec;
2004  companionToInterfaceMap;
2005  auto compareInterfaceSignal = [&](InterfaceElemsBuilder &lhs,
2006  InterfaceElemsBuilder &rhs) {
2007  auto compareProps = [&](InterfaceElemsBuilder::Properties &lhs,
2008  InterfaceElemsBuilder::Properties &rhs) {
2009  // If it's a verbatim op, no need to check the string, because the
2010  // interface names might not match. As long as the signal types match that
2011  // is sufficient.
2012  if (lhs.elemType.index() == 0 && rhs.elemType.index() == 0)
2013  return true;
2014  if (std::get<Type>(lhs.elemType) == std::get<Type>(rhs.elemType))
2015  return true;
2016  return false;
2017  };
2018  return std::equal(lhs.elementsList.begin(), lhs.elementsList.end(),
2019  rhs.elementsList.begin(), compareProps);
2020  };
2021  for (auto anno : worklist) {
2022  auto bundle = AugmentedBundleTypeAttr::get(&getContext(), anno.getDict());
2023 
2024  // The top-level AugmentedBundleType must have a global ID field so that
2025  // this can be linked to the companion.
2026  if (!bundle.isRoot()) {
2027  emitCircuitError() << "missing 'id' in root-level BundleType: "
2028  << anno.getDict() << "\n";
2029  removalError = true;
2030  continue;
2031  }
2032 
2033  if (companionIDMap.count(bundle.getID()) == 0) {
2034  emitCircuitError() << "no companion found with 'id' value '"
2035  << bundle.getID().getValue().getZExtValue() << "'\n";
2036  removalError = true;
2037  continue;
2038  }
2039 
2040  // Decide on a symbol name to use for the interface instance. This is needed
2041  // in `traverseBundle` as a placeholder for the connect operations.
2042  auto companionIter = companionIDMap.lookup(bundle.getID());
2043  auto companionModule = companionIter.companion;
2044  auto symbolName = getNamespace().newName(
2045  "__" + companionIDMap.lookup(bundle.getID()).name + "_" +
2046  getInterfaceName(bundle) + "__");
2047 
2048  // Recursively walk the AugmentedBundleType to generate interfaces and XMRs.
2049  // Error out if this returns None (indicating that the annotation is
2050  // malformed in some way). A good error message is generated inside
2051  // `traverseBundle` or the functions it calls.
2052  auto instanceSymbol =
2053  hw::InnerRefAttr::get(SymbolTable::getSymbolName(companionModule),
2054  StringAttr::get(&getContext(), symbolName));
2055  VerbatimBuilder::Base verbatimData;
2056  VerbatimBuilder verbatim(verbatimData);
2057  verbatim += instanceSymbol;
2058  // List of interface elements.
2059 
2060  SmallVector<VerbatimXMRbuilder> xmrElems;
2061  SmallVector<InterfaceElemsBuilder> interfaceBuilder;
2062 
2063  auto ifaceName = traverseBundle(bundle, bundle.getID(), verbatim, xmrElems,
2064  interfaceBuilder);
2065  if (!ifaceName) {
2066  removalError = true;
2067  continue;
2068  }
2069 
2070  if (companionIter.isNonlocal) {
2071  // If the companion module has two exactly same ViewAnnotation.companion
2072  // annotations, then add the interface for only one of them. This happens
2073  // when the companion is deduped.
2074  auto viewMapIter = companionToInterfaceMap.find(companionModule);
2075  if (viewMapIter != companionToInterfaceMap.end())
2076  if (std::equal(interfaceBuilder.begin(), interfaceBuilder.end(),
2077  viewMapIter->getSecond().begin(),
2078  compareInterfaceSignal)) {
2079  continue;
2080  }
2081 
2082  companionToInterfaceMap[companionModule] = interfaceBuilder;
2083  }
2084 
2085  if (interfaceBuilder.empty())
2086  continue;
2087  auto companionBuilder =
2088  OpBuilder::atBlockEnd(companionModule.getBodyBlock());
2089 
2090  // Generate gathered XMR's.
2091  for (auto xmrElem : xmrElems) {
2092  auto uloc = companionBuilder.getUnknownLoc();
2093  companionBuilder.create<sv::VerbatimOp>(uloc, xmrElem.str, xmrElem.val,
2094  xmrElem.syms);
2095  }
2096  numXMRs += xmrElems.size();
2097 
2098  sv::InterfaceOp topIface;
2099  for (const auto &ifaceBuilder : interfaceBuilder) {
2100  auto builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
2101  auto loc = getOperation().getLoc();
2102  sv::InterfaceOp iface =
2103  builder.create<sv::InterfaceOp>(loc, ifaceBuilder.iFaceName);
2104  if (!topIface)
2105  topIface = iface;
2106  ++numInterfaces;
2107  if (dut &&
2108  !instancePaths->instanceGraph.isAncestor(
2109  companionIDMap[ifaceBuilder.id].companion, dut) &&
2110  testbenchDir)
2111  iface->setAttr("output_file",
2112  hw::OutputFileAttr::getAsDirectory(
2113  &getContext(), testbenchDir.getValue(),
2114  /*excludeFromFileList=*/true));
2115  else if (maybeExtractInfo)
2116  iface->setAttr("output_file",
2117  hw::OutputFileAttr::getAsDirectory(
2118  &getContext(), getOutputDirectory().getValue(),
2119  /*excludeFromFileList=*/true));
2120  iface.setCommentAttr(builder.getStringAttr("VCS coverage exclude_file"));
2121  builder.setInsertionPointToEnd(
2122  cast<sv::InterfaceOp>(iface).getBodyBlock());
2123  interfaceMap[FlatSymbolRefAttr::get(builder.getContext(),
2124  ifaceBuilder.iFaceName)] = iface;
2125  for (auto elem : ifaceBuilder.elementsList) {
2126 
2127  auto uloc = builder.getUnknownLoc();
2128 
2129  auto description = elem.description;
2130 
2131  if (description) {
2132  auto descriptionOp = builder.create<sv::VerbatimOp>(
2133  uloc, ("// " + cleanupDescription(description.getValue())));
2134 
2135  // If we need to generate a YAML representation of this interface,
2136  // then add an attribute indicating that this `sv::VerbatimOp` is
2137  // actually a description.
2138  if (maybeHierarchyFileYAML)
2139  descriptionOp->setAttr("firrtl.grandcentral.yaml.type",
2140  builder.getStringAttr("description"));
2141  }
2142  if (auto *str = std::get_if<VerbatimType>(&elem.elemType)) {
2143  auto instanceOp = builder.create<sv::VerbatimOp>(
2144  uloc, str->toStr(elem.elemName.getValue()));
2145 
2146  // If we need to generate a YAML representation of the interface, then
2147  // add attributes that describe what this `sv::VerbatimOp` is.
2148  if (maybeHierarchyFileYAML) {
2149  if (str->instantiation)
2150  instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2151  builder.getStringAttr("instance"));
2152  else
2153  instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2154  builder.getStringAttr("unsupported"));
2155  instanceOp->setAttr("firrtl.grandcentral.yaml.name", elem.elemName);
2156  instanceOp->setAttr("firrtl.grandcentral.yaml.dimensions",
2157  builder.getI32ArrayAttr(str->dimensions));
2158  instanceOp->setAttr(
2159  "firrtl.grandcentral.yaml.symbol",
2160  FlatSymbolRefAttr::get(builder.getContext(), str->str));
2161  }
2162  continue;
2163  }
2164 
2165  auto tpe = std::get<Type>(elem.elemType);
2166  builder.create<sv::InterfaceSignalOp>(uloc, elem.elemName.getValue(),
2167  tpe);
2168  }
2169  }
2170 
2171  ++numViews;
2172 
2173  interfaceVec.push_back(topIface);
2174 
2175  // Instantiate the interface inside the companion.
2176  builder.setInsertionPointToStart(companionModule.getBodyBlock());
2177  builder.create<sv::InterfaceInstanceOp>(
2178  getOperation().getLoc(), topIface.getInterfaceType(),
2179  companionIDMap.lookup(bundle.getID()).name,
2180  hw::InnerSymAttr::get(builder.getStringAttr(symbolName)));
2181 
2182  // If no extraction information was present, then just leave the interface
2183  // instantiated in the companion. Otherwise, make it a bind.
2184  if (!maybeExtractInfo)
2185  continue;
2186 
2187  // If the interface is associated with a companion that is instantiated
2188  // above the DUT (e.g.., in the test harness), then don't extract it.
2189  if (dut && !instancePaths->instanceGraph.isAncestor(
2190  companionIDMap[bundle.getID()].companion, dut))
2191  continue;
2192  }
2193 
2194  emitHierarchyYamlFile(interfaceVec);
2195 
2196  // Signal pass failure if any errors were found while examining circuit
2197  // annotations.
2198  if (removalError)
2199  return signalPassFailure();
2200  markAnalysesPreserved<NLATable>();
2201 }
2202 
2203 void GrandCentralPass::emitHierarchyYamlFile(
2204  SmallVectorImpl<sv::InterfaceOp> &intfs) {
2205  // If a `GrandCentralHierarchyFileAnnotation` was passed in, generate a YAML
2206  // representation of the interfaces that we produced with the filename that
2207  // that annotation provided.
2208  if (!maybeHierarchyFileYAML)
2209  return;
2210 
2211  CircuitOp circuitOp = getOperation();
2212 
2213  std::string yamlString;
2214  llvm::raw_string_ostream stream(yamlString);
2215  ::yaml::Context yamlContext({interfaceMap});
2216  llvm::yaml::Output yout(stream);
2217  yamlize(yout, intfs, true, yamlContext);
2218 
2219  auto builder = OpBuilder::atBlockBegin(circuitOp.getBodyBlock());
2220  builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), yamlString)
2221  ->setAttr("output_file",
2222  hw::OutputFileAttr::getFromFilename(
2223  &getContext(), maybeHierarchyFileYAML->getValue(),
2224  /*excludeFromFileList=*/true));
2225  LLVM_DEBUG({ llvm::dbgs() << "Generated YAML:" << yamlString << "\n"; });
2226 }
2227 
2228 //===----------------------------------------------------------------------===//
2229 // Pass Creation
2230 //===----------------------------------------------------------------------===//
2231 
2232 std::unique_ptr<mlir::Pass>
2234  auto pass = std::make_unique<GrandCentralPass>();
2235  pass->companionMode = companionMode;
2236  return pass;
2237 }
assert(baseType &&"element must be base type")
MlirType elementType
Definition: CHIRRTL.cpp:29
static std::optional< DictionaryAttr > parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType, DictionaryAttr root, StringRef companion, StringAttr name, StringAttr defName, std::optional< IntegerAttr > id, std::optional< StringAttr > description, Twine clazz, StringAttr companionAttr, Twine path={})
Recursively walk a sifive.enterprise.grandcentral.AugmentedType to extract any annotations it may con...
@ Output
Definition: HW.h:35
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
static Block * getBodyBlock(FModuleLike mod)
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Definition: FieldRef.h:59
Value getValue() const
Get the Value which created this location.
Definition: FieldRef.h:37
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
void addAnnotations(ArrayRef< Annotation > annotations)
Add more annotations to this annotation set.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
DictionaryAttr getDict() const
Get the data dictionary of this attribute.
unsigned getFieldID() const
Get the field id this attribute targets.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
StringRef getClass() const
Return the 'class' that this annotation is representing.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
bool anyInstanceInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if any instance of this module is within (or transitively within) the effective design.
This table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
void removeNLAsfromModule(const DenseSet< hw::HierPathOp > &nlas, StringAttr mod)
Remove all the nlas in the set nlas from the module.
Definition: NLATable.h:173
void getNLAsInModule(StringAttr modName, DenseSet< hw::HierPathOp > &nlas)
Get the NLAs that the module modName particiaptes in, and insert them into the DenseSet nlas.
Definition: NLATable.h:94
hw::HierPathOp getNLA(StringAttr name)
Resolve a symbol to an NLA.
Definition: NLATable.cpp:48
This is a Node in the InstanceGraph.
bool isAncestor(ModuleOpInterface child, ModuleOpInterface parent, llvm::function_ref< bool(InstanceRecord *)> skipInstance=[](InstanceRecord *_) { return false;})
Check if child is instantiated by a parent.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
constexpr const char * augmentedBundleTypeClass
igraph::InstancePathCache InstancePathCache
constexpr const char * extractGrandCentralClass
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
LogicalResult extractDUT(FModuleLike mod, FModuleLike &dut)
Utility that searches for a MarkDUTAnnotation on a specific module, mod, and tries to update a design...
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 * 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)