CIRCT 21.0.0git
Loading...
Searching...
No Matches
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
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
37namespace circt {
38namespace firrtl {
39#define GEN_PASS_DEF_GRANDCENTRAL
40#include "circt/Dialect/FIRRTL/Passes.h.inc"
41} // namespace firrtl
42} // namespace circt
43
44using namespace circt;
45using namespace firrtl;
46
47//===----------------------------------------------------------------------===//
48// Collateral for generating a YAML representation of a SystemVerilog interface
49//===----------------------------------------------------------------------===//
50
51namespace {
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.
74namespace yaml {
75
76/// Context information necessary for YAML generation.
77struct 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.
92struct 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.
105struct 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).
125LLVM_YAML_IS_SEQUENCE_VECTOR(::yaml::DescribedSignal)
126LLVM_YAML_IS_SEQUENCE_VECTOR(::yaml::DescribedInstance)
127LLVM_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).
132namespace llvm {
133namespace yaml {
134
135using 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
147std::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.
165template <>
166struct 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.
246template <>
247struct 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.
296template <>
297struct 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
406namespace {
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.
425struct 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
484private:
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.
493struct 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.
523using TypeSum = std::variant<VerbatimType, Type>;
524
525/// Stores the information content of an ExtractGrandCentralAnnotation.
526struct 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.
538struct 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.
547struct FieldAndNLA {
548 FieldRef field;
549 FlatSymbolRefAttr nlaSym;
550};
551
552/// Stores the arguments required to construct the verbatim xmr assignment.
553struct 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.
565struct 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.
592struct GrandCentralPass
593 : public circt::firrtl::impl::GrandCentralBase<GrandCentralPass> {
594 using GrandCentralBase::companionMode;
595
596 void runOnOperation() override;
597
598private:
599 //===- Annotation handling data -----------------------------------------===//
600
601 /// Mapping of ID to leaf ground type and an optional non-local annotation
602 /// associated with that ID.
603 DenseMap<Attribute, FieldAndNLA> leafMap;
604
605 /// Mapping of ID to companion module.
606 DenseMap<Attribute, CompanionInfo> companionIDMap;
607
608 NLATable *nlaTable;
609
610 /// An optional directory for testbench-related files. This is null if no
611 /// "TestBenchDirAnnotation" is found.
612 StringAttr testbenchDir;
613
614 //===- Annotation handling functions ------------------------------------===//
615
616 /// Optionally build an AugmentedType from an attribute. Return none if the
617 /// attribute is not a dictionary or if it does not match any of the known
618 /// templates for AugmentedTypes.
619 std::optional<Attribute> fromAttr(Attribute attr);
620
621 /// Recursively examine an AugmentedType to populate the "mappings" file
622 /// (generate XMRs) for this interface. This does not build new interfaces.
623 bool traverseField(Attribute field, IntegerAttr id, VerbatimBuilder &path,
624 SmallVector<VerbatimXMRbuilder> &xmrElems,
625 SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
626
627 /// Recursively examine an AugmentedType to both build new interfaces and
628 /// populate a "mappings" file (generate XMRs) using `traverseField`. Return
629 /// the type of the field examined.
630 std::optional<TypeSum>
631 computeField(Attribute field, IntegerAttr id, VerbatimBuilder &path,
632 SmallVector<VerbatimXMRbuilder> &xmrElems,
633 SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
634
635 /// Recursively examine an AugmentedBundleType to both build new interfaces
636 /// and populate a "mappings" file (generate XMRs). Return none if the
637 /// interface is invalid.
638 std::optional<StringAttr>
639 traverseBundle(AugmentedBundleTypeAttr bundle, IntegerAttr id,
640 VerbatimBuilder &path,
641 SmallVector<VerbatimXMRbuilder> &xmrElems,
642 SmallVector<InterfaceElemsBuilder> &interfaceBuilder);
643
644 /// Return the module associated with this value.
645 igraph::ModuleOpInterface getEnclosingModule(Value value,
646 FlatSymbolRefAttr sym = {});
647
648 // Utility that acts like emitOpError, but does _not_ include a note. The
649 // note in emitOpError includes the entire op which means the **ENTIRE**
650 // FIRRTL circuit. This doesn't communicate anything useful to the user
651 // other than flooding their terminal.
652 InFlightDiagnostic emitCircuitError(StringRef message = {}) {
653 return emitError(getOperation().getLoc(), "'firrtl.circuit' op " + message);
654 }
655
656 //===- Intrinsic Handling Functions -------------------------------------===//
657
658 /// Optionally build an AugmentedType from an attribute. Return none if the
659 /// attribute is not a dictionary or if it does not match any of the known
660 /// templates for AugmentedTypes.
661 std::optional<Attribute> fromViewAttr(ViewIntrinsicOp view, Attribute attr);
662
663 /// Recursively examine an AugmentedType to populate the "mappings" file
664 /// (generate XMRs) for this interface. This does not build new interfaces.
665 bool traverseViewField(Attribute field, VerbatimBuilder &path,
666 SmallVector<VerbatimXMRbuilder> &xmrElems,
667 SmallVector<InterfaceElemsBuilder> &interfaceBuilder,
668 ViewIntrinsicOp view, size_t &idx);
669
670 /// Recursively examine an AugmentedType to both build new interfaces and
671 /// populate a "mappings" file (generate XMRs) using `traverseField`. Return
672 /// the type of the field examined.
673 std::optional<TypeSum>
674 computeViewField(Attribute field, VerbatimBuilder &path,
675 SmallVector<VerbatimXMRbuilder> &xmrElems,
676 SmallVector<InterfaceElemsBuilder> &interfaceBuilder,
677 ViewIntrinsicOp view, size_t &idx);
678
679 /// Recursively examine an AugmentedBundleType to both build new interfaces
680 /// and populate a "mappings" file (generate XMRs). Return none if the
681 /// interface is invalid.
682 std::optional<StringAttr>
683 traverseViewBundle(AugmentedBundleTypeAttr bundle, VerbatimBuilder &path,
684 SmallVector<VerbatimXMRbuilder> &xmrElems,
685 SmallVector<InterfaceElemsBuilder> &interfaceBuilder,
686 ViewIntrinsicOp view, size_t &idx);
687
688 //===- Common helpers and state -----------------------------------------===//
689
690 /// Return a string containing the name of an interface.
691 std::string getInterfaceName(AugmentedBundleTypeAttr bundleType) {
692 return (bundleType.getDefName().getValue()).str();
693 }
694
695 /// Information about how the circuit should be extracted. This will be
696 /// non-empty if an extraction annotation is found.
697 std::optional<ExtractionInfo> maybeExtractInfo = std::nullopt;
698
699 /// A filename describing where to put a YAML representation of the
700 /// interfaces generated by this pass.
701 std::optional<StringAttr> maybeHierarchyFileYAML = std::nullopt;
702
703 StringAttr getOutputDirectory() {
704 if (maybeExtractInfo)
705 return maybeExtractInfo->directory;
706 return {};
707 }
708
709 /// Store of an instance paths analysis. This is constructed inside
710 /// `runOnOperation`, to work around the deleted copy constructor of
711 /// `InstancePathCache`'s internal `BumpPtrAllocator`.
712 ///
713 /// TODO: Investigate a way to not use a pointer here like how `getNamespace`
714 /// works below.
715 /// Only used for annotation handling / companions.
716 InstancePathCache *instancePaths = nullptr;
717
718 /// An instance info analysis that is used to query if modules are in the
719 /// design or not.
720 InstanceInfo *instanceInfo = nullptr;
721
722 /// The namespace associated with the circuit. This is lazily constructed
723 /// using `getNamespace`.
724 std::optional<CircuitNamespace> circuitNamespace;
725
726 /// The module namespaces. These are lazily constructed by
727 /// `getModuleNamespace`.
728 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
729
730 /// Return a reference to the circuit namespace. This will lazily construct a
731 /// namespace if one does not exist.
732 CircuitNamespace &getNamespace() {
733 if (!circuitNamespace)
734 circuitNamespace = CircuitNamespace(getOperation());
735 return *circuitNamespace;
736 }
737
738 /// Get the cached namespace for a module.
739 hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
740 return moduleNamespaces.try_emplace(module, module).first->second;
741 }
742
743 /// A symbol table associated with the circuit. This is lazily constructed by
744 /// `getSymbolTable`.
745 std::optional<SymbolTable *> symbolTable;
746
747 /// Return a reference to a circuit-level symbol table. Lazily construct one
748 /// if such a symbol table does not already exist.
749 SymbolTable &getSymbolTable() {
750 if (!symbolTable)
751 symbolTable = &getAnalysis<SymbolTable>();
752 return **symbolTable;
753 }
754
755 // Insert comment delimiters ("// ") after newlines in the description string.
756 // This is necessary to prevent introducing invalid verbatim Verilog.
757 //
758 // TODO: Add a comment op and lower the description to that.
759 // TODO: Tracking issue: https://github.com/llvm/circt/issues/1677
760 std::string cleanupDescription(StringRef description) {
761 StringRef head;
762 SmallString<64> out;
763 do {
764 std::tie(head, description) = description.split("\n");
765 out.append(head);
766 if (!description.empty())
767 out.append("\n// ");
768 } while (!description.empty());
769 return std::string(out);
770 }
771
772 /// A store of the YAML representation of interfaces.
773 DenseMap<Attribute, sv::InterfaceOp> interfaceMap;
774
775 /// Emit the hierarchy yaml file.
776 void emitHierarchyYamlFile(StringRef yamlPath,
777 SmallVectorImpl<sv::InterfaceOp> &intfs);
778};
779
780} // namespace
781
782//===----------------------------------------------------------------------===//
783// Code related to handling Grand Central View annotations
784//===----------------------------------------------------------------------===//
785
786/// Recursively walk a sifive.enterprise.grandcentral.AugmentedType to extract
787/// any annotations it may contain. This is going to generate two types of
788/// annotations:
789/// 1) Annotations necessary to build interfaces and store them at "~"
790/// 2) Scattered annotations for how components bind to interfaces
791static std::optional<DictionaryAttr>
792parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType,
793 DictionaryAttr root, StringAttr name, StringAttr defName,
794 std::optional<IntegerAttr> id,
795 std::optional<StringAttr> description, Twine clazz,
796 StringAttr companionAttr, Twine path = {}) {
797
798 auto *context = state.circuit.getContext();
799 auto loc = state.circuit.getLoc();
800
801 /// Optionally unpack a ReferenceTarget encoded as a DictionaryAttr. Return
802 /// either a pair containing the Target string (up to the reference) and an
803 /// array of components or none if the input is malformed. The input
804 /// DictionaryAttr encoding is a JSON object of a serialized ReferenceTarget
805 /// Scala class. By example, this is converting:
806 /// ~Foo|Foo>a.b[0]
807 /// To:
808 /// {"~Foo|Foo>a", {".b", "[0]"}}
809 /// The format of a ReferenceTarget object like:
810 /// circuit: String
811 /// module: String
812 /// path: Seq[(Instance, OfModule)]
813 /// ref: String
814 /// component: Seq[TargetToken]
815 auto refToTarget =
816 [&](DictionaryAttr refTarget) -> std::optional<std::string> {
817 auto circuitAttr =
818 tryGetAs<StringAttr>(refTarget, refTarget, "circuit", loc, clazz, path);
819 auto moduleAttr =
820 tryGetAs<StringAttr>(refTarget, refTarget, "module", loc, clazz, path);
821 auto pathAttr =
822 tryGetAs<ArrayAttr>(refTarget, refTarget, "path", loc, clazz, path);
823 auto componentAttr = tryGetAs<ArrayAttr>(refTarget, refTarget, "component",
824 loc, clazz, path);
825 if (!circuitAttr || !moduleAttr || !pathAttr || !componentAttr)
826 return {};
827
828 // Parse non-local annotations.
829 SmallString<32> strpath;
830 for (auto p : pathAttr) {
831 auto dict = dyn_cast_or_null<DictionaryAttr>(p);
832 if (!dict) {
833 mlir::emitError(loc, "annotation '" + clazz +
834 " has invalid type (expected DictionaryAttr)");
835 return {};
836 }
837 auto instHolder =
838 tryGetAs<DictionaryAttr>(dict, dict, "_1", loc, clazz, path);
839 auto modHolder =
840 tryGetAs<DictionaryAttr>(dict, dict, "_2", loc, clazz, path);
841 if (!instHolder || !modHolder) {
842 mlir::emitError(loc, "annotation '" + clazz +
843 " has invalid type (expected DictionaryAttr)");
844 return {};
845 }
846 auto inst = tryGetAs<StringAttr>(instHolder, instHolder, "value", loc,
847 clazz, path);
848 auto mod =
849 tryGetAs<StringAttr>(modHolder, modHolder, "value", loc, clazz, path);
850 if (!inst || !mod) {
851 mlir::emitError(loc, "annotation '" + clazz +
852 " has invalid type (expected DictionaryAttr)");
853 return {};
854 }
855 strpath += "/" + inst.getValue().str() + ":" + mod.getValue().str();
856 }
857
858 SmallVector<Attribute> componentAttrs;
859 SmallString<32> componentStr;
860 for (size_t i = 0, e = componentAttr.size(); i != e; ++i) {
861 auto cPath = (path + ".component[" + Twine(i) + "]").str();
862 auto component = componentAttr[i];
863 auto dict = dyn_cast_or_null<DictionaryAttr>(component);
864 if (!dict) {
865 mlir::emitError(loc, "annotation '" + clazz + "' with path '" + cPath +
866 " has invalid type (expected DictionaryAttr)");
867 return {};
868 }
869 auto classAttr =
870 tryGetAs<StringAttr>(dict, refTarget, "class", loc, clazz, cPath);
871 if (!classAttr)
872 return {};
873
874 auto value = dict.get("value");
875
876 // A subfield like "bar" in "~Foo|Foo>foo.bar".
877 if (auto field = dyn_cast<StringAttr>(value)) {
878 assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Field" &&
879 "A StringAttr target token must be found with a subfield target "
880 "token.");
881 componentStr.append((Twine(".") + field.getValue()).str());
882 continue;
883 }
884
885 // A subindex like "42" in "~Foo|Foo>foo[42]".
886 if (auto index = dyn_cast<IntegerAttr>(value)) {
887 assert(classAttr.getValue() == "firrtl.annotations.TargetToken$Index" &&
888 "An IntegerAttr target token must be found with a subindex "
889 "target token.");
890 componentStr.append(
891 (Twine("[") + Twine(index.getValue().getZExtValue()) + "]").str());
892 continue;
893 }
894
895 mlir::emitError(loc,
896 "Annotation '" + clazz + "' with path '" + cPath +
897 ".value has unexpected type (should be StringAttr "
898 "for subfield or IntegerAttr for subindex).")
899 .attachNote()
900 << "The value received was: " << value << "\n";
901 return {};
902 }
903
904 auto refAttr =
905 tryGetAs<StringAttr>(refTarget, refTarget, "ref", loc, clazz, path);
906 if (!refAttr)
907 return {};
908
909 return (Twine("~" + circuitAttr.getValue() + "|" + moduleAttr.getValue() +
910 strpath + ">" + refAttr.getValue()) +
911 componentStr)
912 .str();
913 };
914
915 auto classAttr =
916 tryGetAs<StringAttr>(augmentedType, root, "class", loc, clazz, path);
917 if (!classAttr)
918 return std::nullopt;
919 StringRef classBase = classAttr.getValue();
920 if (!classBase.consume_front("sifive.enterprise.grandcentral.Augmented")) {
921 mlir::emitError(loc,
922 "the 'class' was expected to start with "
923 "'sifive.enterprise.grandCentral.Augmented*', but was '" +
924 classAttr.getValue() + "' (Did you misspell it?)")
925 .attachNote()
926 << "see annotation: " << augmentedType;
927 return std::nullopt;
928 }
929
930 // An AugmentedBundleType looks like:
931 // "defName": String
932 // "elements": Seq[AugmentedField]
933 if (classBase == "BundleType") {
934 defName =
935 tryGetAs<StringAttr>(augmentedType, root, "defName", loc, clazz, path);
936 if (!defName)
937 return std::nullopt;
938
939 // Each element is an AugmentedField with members:
940 // "name": String
941 // "description": Option[String]
942 // "tpe": AugmentedType
943 SmallVector<Attribute> elements;
944 auto elementsAttr =
945 tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
946 if (!elementsAttr)
947 return std::nullopt;
948 for (size_t i = 0, e = elementsAttr.size(); i != e; ++i) {
949 auto field = dyn_cast_or_null<DictionaryAttr>(elementsAttr[i]);
950 if (!field) {
951 mlir::emitError(
952 loc,
953 "Annotation '" + Twine(clazz) + "' with path '.elements[" +
954 Twine(i) +
955 "]' contained an unexpected type (expected a DictionaryAttr).")
956 .attachNote()
957 << "The received element was: " << elementsAttr[i] << "\n";
958 return std::nullopt;
959 }
960 auto ePath = (path + ".elements[" + Twine(i) + "]").str();
961 auto name = tryGetAs<StringAttr>(field, root, "name", loc, clazz, ePath);
962 auto tpe =
963 tryGetAs<DictionaryAttr>(field, root, "tpe", loc, clazz, ePath);
964 if (!name || !tpe)
965 return std::nullopt;
966 std::optional<StringAttr> description;
967 if (auto maybeDescription = field.get("description"))
968 description = cast<StringAttr>(maybeDescription);
969 auto eltAttr = parseAugmentedType(
970 state, tpe, root, name, defName, std::nullopt, description, clazz,
971 companionAttr, path + "_" + name.getValue());
972 if (!eltAttr)
973 return std::nullopt;
974
975 // Collect information necessary to build a module with this view later.
976 // This includes the optional description and name.
977 NamedAttrList attrs;
978 if (auto maybeDescription = field.get("description"))
979 attrs.append("description", cast<StringAttr>(maybeDescription));
980 attrs.append("name", name);
981 auto tpeClass = tpe.getAs<StringAttr>("class");
982 if (!tpeClass) {
983 mlir::emitError(loc, "missing 'class' key in") << tpe;
984 return std::nullopt;
985 }
986 attrs.append("tpe", tpeClass);
987 elements.push_back(*eltAttr);
988 }
989 // Add an annotation that stores information necessary to construct the
990 // module for the view. This needs the name of the module (defName) and the
991 // names of the components inside it.
992 NamedAttrList attrs;
993 attrs.append("class", classAttr);
994 attrs.append("defName", defName);
995 if (description)
996 attrs.append("description", *description);
997 attrs.append("elements", ArrayAttr::get(context, elements));
998 if (id)
999 attrs.append("id", *id);
1000 attrs.append("name", name);
1001 return DictionaryAttr::getWithSorted(context, attrs);
1002 }
1003
1004 // An AugmentedGroundType looks like:
1005 // "ref": ReferenceTarget
1006 // "tpe": GroundType
1007 // The ReferenceTarget is not serialized to a string. The GroundType will
1008 // either be an actual FIRRTL ground type or a GrandCentral uninferred type.
1009 // This can be ignored for us.
1010 if (classBase == "GroundType") {
1011 auto augRef = augmentedType.getAs<DictionaryAttr>("ref");
1012 if (!augRef) {
1013 mlir::emitError(loc, "missing 'ref' key in ") << augmentedType;
1014 return std::nullopt;
1015 }
1016 auto maybeTarget = refToTarget(augRef);
1017 if (!maybeTarget) {
1018 mlir::emitError(loc, "Failed to parse ReferenceTarget").attachNote()
1019 << "See the full Annotation here: " << root;
1020 return std::nullopt;
1021 }
1022
1023 auto id = state.newID();
1024
1025 auto target = *maybeTarget;
1026
1027 NamedAttrList elementIface, elementScattered;
1028
1029 // Populate the annotation for the interface element.
1030 elementIface.append("class", classAttr);
1031 if (description)
1032 elementIface.append("description", *description);
1033 elementIface.append("id", id);
1034 elementIface.append("name", name);
1035 // Populate an annotation that will be scattered onto the element.
1036 elementScattered.append("class", classAttr);
1037 elementScattered.append("id", id);
1038 // If there are sub-targets, then add these.
1039 auto targetAttr = StringAttr::get(context, target);
1040 auto xmrSrcTarget = resolvePath(targetAttr.getValue(), state.circuit,
1041 state.symTbl, state.targetCaches);
1042 if (!xmrSrcTarget) {
1043 mlir::emitError(loc, "Failed to resolve target ") << targetAttr;
1044 return std::nullopt;
1045 }
1046
1047 // Determine the source for this Wiring Problem. The source is the value
1048 // that will be eventually by read from, via cross-module reference, to
1049 // drive this element of the SystemVerilog Interface.
1050 auto sourceRef = xmrSrcTarget->ref;
1051 ImplicitLocOpBuilder builder(sourceRef.getOp()->getLoc(), context);
1052 std::optional<Value> source =
1053 TypeSwitch<Operation *, std::optional<Value>>(sourceRef.getOp())
1054 // The target is an external module port. The source is the
1055 // instance port of this singly-instantiated external module.
1056 .Case<FExtModuleOp>([&](FExtModuleOp extMod)
1057 -> std::optional<Value> {
1058 auto portNo = sourceRef.getImpl().getPortNo();
1059 if (xmrSrcTarget->instances.empty()) {
1060 auto paths = state.instancePathCache.getAbsolutePaths(extMod);
1061 if (paths.size() > 1) {
1062 extMod.emitError(
1063 "cannot resolve a unique instance path from the "
1064 "external module '")
1065 << targetAttr << "'";
1066 return std::nullopt;
1067 }
1068 auto *it = xmrSrcTarget->instances.begin();
1069 for (auto inst : paths.back()) {
1070 xmrSrcTarget->instances.insert(it, cast<InstanceOp>(inst));
1071 ++it;
1072 }
1073 }
1074 auto lastInst = xmrSrcTarget->instances.pop_back_val();
1075 builder.setInsertionPointAfter(lastInst);
1076 return getValueByFieldID(builder, lastInst.getResult(portNo),
1077 xmrSrcTarget->fieldIdx);
1078 })
1079 // The target is a module port. The source is the port _inside_
1080 // that module.
1081 .Case<FModuleOp>([&](FModuleOp module) -> std::optional<Value> {
1082 builder.setInsertionPointToEnd(module.getBodyBlock());
1083 auto portNum = sourceRef.getImpl().getPortNo();
1084 return getValueByFieldID(builder, module.getArgument(portNum),
1085 xmrSrcTarget->fieldIdx);
1086 })
1087 // The target is something else.
1088 .Default([&](Operation *op) -> std::optional<Value> {
1089 auto module = cast<FModuleOp>(sourceRef.getModule());
1090 builder.setInsertionPointToEnd(module.getBodyBlock());
1091 auto is = dyn_cast<hw::InnerSymbolOpInterface>(op);
1092 // Resolve InnerSymbol references to their target result.
1093 if (is && is.getTargetResult())
1094 return getValueByFieldID(builder, is.getTargetResult(),
1095 xmrSrcTarget->fieldIdx);
1096 if (sourceRef.getOp()->getNumResults() != 1) {
1097 op->emitOpError()
1098 << "cannot be used as a target of the Grand Central View \""
1099 << defName.getValue()
1100 << "\" because it does not have exactly one result";
1101 return std::nullopt;
1102 }
1103 return getValueByFieldID(builder, sourceRef.getOp()->getResult(0),
1104 xmrSrcTarget->fieldIdx);
1105 });
1106
1107 // Exit if there was an error in the source.
1108 if (!source)
1109 return std::nullopt;
1110
1111 // Compute the sink of this Wiring Problem. The final sink will eventually
1112 // be a SystemVerilog Interface. However, this cannot exist until the
1113 // GrandCentral pass runs. Create an undriven WireOp and use that as the
1114 // sink. The WireOp will be driven later when the Wiring Problem is
1115 // resolved. Apply the scattered element annotation to this directly to save
1116 // having to reprocess this in LowerAnnotations.
1117 auto companionMod =
1118 cast<FModuleOp>(resolvePath(companionAttr.getValue(), state.circuit,
1119 state.symTbl, state.targetCaches)
1120 ->ref.getOp());
1121 builder.setInsertionPointToEnd(companionMod.getBodyBlock());
1122 // Sink type must be passive. It's required to be converted to a NodeOp by
1123 // the wiring problem solving, and later checked to be a Node.
1124 // This also ensures passive sink so works equally well w/ or w/o probes.
1125 auto sinkType = source->getType();
1126 if (auto baseSinkType = type_dyn_cast<FIRRTLBaseType>(sinkType))
1127 sinkType = baseSinkType.getPassiveType();
1128 auto sink = builder.create<WireOp>(sinkType, name);
1129 state.targetCaches.insertOp(sink);
1130 AnnotationSet annotations(context);
1131 annotations.addAnnotations(
1132 {DictionaryAttr::getWithSorted(context, elementScattered)});
1133 annotations.applyToOperation(sink);
1134
1135 // Append this new Wiring Problem to the ApplyState. The Wiring Problem
1136 // will be resolved to bore RefType ports before LowerAnnotations finishes.
1137 state.wiringProblems.push_back({*source, sink.getResult(),
1138 (path + "__bore").str(),
1139 WiringProblem::RefTypeUsage::Prefer});
1140
1141 return DictionaryAttr::getWithSorted(context, elementIface);
1142 }
1143
1144 // An AugmentedVectorType looks like:
1145 // "elements": Seq[AugmentedType]
1146 if (classBase == "VectorType") {
1147 auto elementsAttr =
1148 tryGetAs<ArrayAttr>(augmentedType, root, "elements", loc, clazz, path);
1149 if (!elementsAttr)
1150 return std::nullopt;
1151 SmallVector<Attribute> elements;
1152 for (auto [i, elt] : llvm::enumerate(elementsAttr)) {
1153 auto eltAttr =
1154 parseAugmentedType(state, cast<DictionaryAttr>(elt), root, name,
1155 StringAttr::get(context, ""), id, std::nullopt,
1156 clazz, companionAttr, path + "_" + Twine(i));
1157 if (!eltAttr)
1158 return std::nullopt;
1159 elements.push_back(*eltAttr);
1160 }
1161 NamedAttrList attrs;
1162 attrs.append("class", classAttr);
1163 if (description)
1164 attrs.append("description", *description);
1165 attrs.append("elements", ArrayAttr::get(context, elements));
1166 attrs.append("name", name);
1167 return DictionaryAttr::getWithSorted(context, attrs);
1168 }
1169
1170 // Anything else is unexpected or a user error if they manually wrote
1171 // annotations. Print an error and error out.
1172 mlir::emitError(loc, "found unknown AugmentedType '" + classAttr.getValue() +
1173 "' (Did you misspell it?)")
1174 .attachNote()
1175 << "see annotation: " << augmentedType;
1176 return std::nullopt;
1177}
1178
1179LogicalResult circt::firrtl::applyGCTView(const AnnoPathValue &target,
1180 DictionaryAttr anno,
1181 ApplyState &state) {
1182
1183 auto id = state.newID();
1184 auto *context = state.circuit.getContext();
1185 auto loc = state.circuit.getLoc();
1186 NamedAttrList companionAttrs;
1187 companionAttrs.append("class", StringAttr::get(context, companionAnnoClass));
1188 companionAttrs.append("id", id);
1189 auto viewAttr =
1190 tryGetAs<DictionaryAttr>(anno, anno, "view", loc, viewAnnoClass);
1191 if (!viewAttr)
1192 return failure();
1193 auto name = tryGetAs<StringAttr>(anno, anno, "name", loc, viewAnnoClass);
1194 if (!name)
1195 return failure();
1196 companionAttrs.append("name", name);
1197 auto companionAttr =
1198 tryGetAs<StringAttr>(anno, anno, "companion", loc, viewAnnoClass);
1199 if (!companionAttr)
1200 return failure();
1201 companionAttrs.append("target", companionAttr);
1202 state.addToWorklistFn(DictionaryAttr::get(context, companionAttrs));
1203
1204 auto prunedAttr =
1205 parseAugmentedType(state, viewAttr, anno, name, {}, id, {}, viewAnnoClass,
1206 companionAttr, Twine(name));
1207 if (!prunedAttr)
1208 return failure();
1209
1210 AnnotationSet annotations(state.circuit);
1211 annotations.addAnnotations({*prunedAttr});
1212 annotations.applyToOperation(state.circuit);
1213
1214 return success();
1215}
1216
1217//===----------------------------------------------------------------------===//
1218// GrandCentralPass Implementation
1219//===----------------------------------------------------------------------===//
1220
1221std::optional<Attribute> GrandCentralPass::fromAttr(Attribute attr) {
1222 auto dict = dyn_cast<DictionaryAttr>(attr);
1223 if (!dict) {
1224 emitCircuitError() << "attribute is not a dictionary: " << attr << "\n";
1225 return std::nullopt;
1226 }
1227
1228 auto clazz = dict.getAs<StringAttr>("class");
1229 if (!clazz) {
1230 emitCircuitError() << "missing 'class' key in " << dict << "\n";
1231 return std::nullopt;
1232 }
1233
1234 auto classBase = clazz.getValue();
1235 classBase.consume_front("sifive.enterprise.grandcentral.Augmented");
1236
1237 if (classBase == "BundleType") {
1238 if (dict.getAs<StringAttr>("defName") && dict.getAs<ArrayAttr>("elements"))
1239 return AugmentedBundleTypeAttr::get(&getContext(), dict);
1240 emitCircuitError() << "has an invalid AugmentedBundleType that does not "
1241 "contain 'defName' and 'elements' fields: "
1242 << dict;
1243 } else if (classBase == "VectorType") {
1244 if (dict.getAs<StringAttr>("name") && dict.getAs<ArrayAttr>("elements"))
1245 return AugmentedVectorTypeAttr::get(&getContext(), dict);
1246 emitCircuitError() << "has an invalid AugmentedVectorType that does not "
1247 "contain 'name' and 'elements' fields: "
1248 << dict;
1249 } else if (classBase == "GroundType") {
1250 auto id = dict.getAs<IntegerAttr>("id");
1251 auto name = dict.getAs<StringAttr>("name");
1252 if (id && leafMap.count(id) && name)
1253 return AugmentedGroundTypeAttr::get(&getContext(), dict);
1254 if (!id || !name)
1255 emitCircuitError() << "has an invalid AugmentedGroundType that does not "
1256 "contain 'id' and 'name' fields: "
1257 << dict;
1258 if (id && !leafMap.count(id))
1259 emitCircuitError() << "has an AugmentedGroundType with 'id == "
1260 << id.getValue().getZExtValue()
1261 << "' that does not have a scattered leaf to connect "
1262 "to in the circuit "
1263 "(was the leaf deleted or constant prop'd away?)";
1264 } else {
1265 emitCircuitError() << "has an invalid AugmentedType";
1266 }
1267 return std::nullopt;
1268}
1269
1270std::optional<Attribute> GrandCentralPass::fromViewAttr(ViewIntrinsicOp view,
1271 Attribute attr) {
1272 auto dict = dyn_cast<DictionaryAttr>(attr);
1273 if (!dict) {
1274 view.emitError() << "attribute is not a dictionary: " << attr;
1275 return std::nullopt;
1276 }
1277
1278 auto clazz = dict.getAs<StringAttr>("class");
1279 if (!clazz) {
1280 view.emitError() << "missing 'class' key in " << dict;
1281 return std::nullopt;
1282 }
1283
1284 auto classBase = clazz.getValue();
1285 if (!classBase.consume_front("sifive.enterprise.grandcentral.Augmented"))
1286 view.emitOpError() << "has an invalid AugmentedType class '" << classBase
1287 << "'";
1288 else if (classBase == "BundleType") {
1289 if (dict.getAs<StringAttr>("defName") && dict.getAs<ArrayAttr>("elements"))
1290 return AugmentedBundleTypeAttr::get(&getContext(), dict);
1291 view.emitError() << "has an invalid AugmentedBundleType that does not "
1292 "contain 'defName' and 'elements' fields: "
1293 << dict;
1294 } else if (classBase == "VectorType") {
1295 if (dict.getAs<StringAttr>("name") && dict.getAs<ArrayAttr>("elements"))
1296 return AugmentedVectorTypeAttr::get(&getContext(), dict);
1297 view.emitError() << "has an invalid AugmentedVectorType that does not "
1298 "contain 'name' and 'elements' fields: "
1299 << dict;
1300 } else if (classBase == "GroundType") {
1301 auto id = dict.getAs<IntegerAttr>("id");
1302 if (id) {
1303 (
1304 view.emitOpError()
1305 << "has 'id' field which is only for old annotation encoding")
1306 .attachNote()
1307 << "id within GroundType attribute: " << dict;
1308 return std::nullopt;
1309 }
1310 auto name = dict.getAs<StringAttr>("name");
1311 if (name)
1312 return AugmentedGroundTypeAttr::get(&getContext(), dict);
1313 view.emitError() << "has an invalid AugmentedGroundType that does not "
1314 "contain 'name' field: "
1315 << dict;
1316 } else {
1317 view.emitOpError() << "has an invalid AugmentedType '" << classBase << "'";
1318 }
1319 return std::nullopt;
1320}
1321
1322bool GrandCentralPass::traverseField(
1323 Attribute field, IntegerAttr id, VerbatimBuilder &path,
1324 SmallVector<VerbatimXMRbuilder> &xmrElems,
1325 SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1326 return TypeSwitch<Attribute, bool>(field)
1327 .Case<AugmentedGroundTypeAttr>([&](AugmentedGroundTypeAttr ground) {
1328 auto [fieldRef, sym] = leafMap.lookup(ground.getID());
1329 hw::HierPathOp nla;
1330 if (sym)
1331 nla = nlaTable->getNLA(sym.getAttr());
1332 Value leafValue = fieldRef.getValue();
1333 assert(leafValue && "leafValue not found");
1334
1335 auto companionModule = companionIDMap.lookup(id).companion;
1336 igraph::ModuleOpInterface enclosing =
1337 getEnclosingModule(leafValue, sym);
1338
1339 auto tpe = type_cast<FIRRTLBaseType>(leafValue.getType());
1340
1341 // If the type is zero-width then do not emit an XMR.
1342 if (!tpe.getBitWidthOrSentinel())
1343 return true;
1344
1345 // The leafValue is assumed to conform to a very specific pattern:
1346 //
1347 // 1) The leaf value is in the companion.
1348 // 2) The leaf value is a NodeOp
1349 //
1350 // Anything else means that there is an error or the IR is somehow using
1351 // "old-style" Annotations to encode a Grand Central View. This
1352 // _really_ should be impossible to hit given that LowerAnnotations must
1353 // generate code that conforms to the check here.
1354 auto *nodeOp = leafValue.getDefiningOp();
1355 if (companionModule != enclosing) {
1356 auto diag = companionModule->emitError()
1357 << "Grand Central View \""
1358 << companionIDMap.lookup(id).name
1359 << "\" is invalid because a leaf is not inside the "
1360 "companion module";
1361 diag.attachNote(leafValue.getLoc())
1362 << "the leaf value is declared here";
1363 if (nodeOp) {
1364 auto leafModule = nodeOp->getParentOfType<FModuleOp>();
1365 diag.attachNote(leafModule.getLoc())
1366 << "the leaf value is inside this module";
1367 }
1368 return false;
1369 }
1370
1371 if (!isa<NodeOp>(nodeOp)) {
1372 emitError(leafValue.getLoc())
1373 << "Grand Central View \"" << companionIDMap.lookup(id).name
1374 << "\" has an invalid leaf value (this must be a node)";
1375 return false;
1376 }
1377
1378 /// Increment all the indices inside `{{`, `}}` by one. This is to
1379 /// indicate that a value is added to the `substitutions` of the
1380 /// verbatim op, other than the symbols.
1381 auto getStrAndIncrementIds = [&](StringRef base) -> StringAttr {
1382 SmallString<128> replStr;
1383 StringRef begin = "{{";
1384 StringRef end = "}}";
1385 // The replacement string.
1386 size_t from = 0;
1387 while (from < base.size()) {
1388 // Search for the first `{{` and `}}`.
1389 size_t beginAt = base.find(begin, from);
1390 size_t endAt = base.find(end, from);
1391 // If not found, then done.
1392 if (beginAt == StringRef::npos || endAt == StringRef::npos ||
1393 (beginAt > endAt)) {
1394 replStr.append(base.substr(from));
1395 break;
1396 }
1397 // Copy the string as is, until the `{{`.
1398 replStr.append(base.substr(from, beginAt - from));
1399 // Advance `from` to the character after the `}}`.
1400 from = endAt + 2;
1401 auto idChar = base.substr(beginAt + 2, endAt - beginAt - 2);
1402 int idNum;
1403 bool failed = idChar.getAsInteger(10, idNum);
1404 (void)failed;
1405 assert(!failed && "failed to parse integer from verbatim string");
1406 // Now increment the id and append.
1407 replStr.append("{{");
1408 Twine(idNum + 1).toVector(replStr);
1409 replStr.append("}}");
1410 }
1411 return StringAttr::get(&getContext(), "assign " + replStr + ";");
1412 };
1413
1414 // This is the new style of XMRs using RefTypes. The value substitution
1415 // index is set to -1, as it will be incremented when generating the
1416 // string.
1417 // Generate the path from the LCA to the module that contains the leaf.
1418 path += " = {{-1}}";
1420 // Assemble the verbatim op.
1421 xmrElems.emplace_back(
1422 nodeOp->getOperand(0), getStrAndIncrementIds(path.getString()),
1423 ArrayAttr::get(&getContext(), path.getSymbols()), companionModule);
1424 return true;
1425 })
1426 .Case<AugmentedVectorTypeAttr>([&](auto vector) {
1427 bool notFailed = true;
1428 auto elements = vector.getElements();
1429 for (size_t i = 0, e = elements.size(); i != e; ++i) {
1430 auto field = fromAttr(elements[i]);
1431 if (!field)
1432 return false;
1433 notFailed &= traverseField(
1434 *field, id, path.snapshot().append("[" + Twine(i) + "]"),
1435 xmrElems, interfaceBuilder);
1436 }
1437 return notFailed;
1438 })
1439 .Case<AugmentedBundleTypeAttr>([&](AugmentedBundleTypeAttr bundle) {
1440 bool anyFailed = true;
1441 for (auto element : bundle.getElements()) {
1442 auto field = fromAttr(element);
1443 if (!field)
1444 return false;
1445 auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1446 if (!name)
1447 name = cast<DictionaryAttr>(element).getAs<StringAttr>("defName");
1448 anyFailed &= traverseField(
1449 *field, id, path.snapshot().append("." + name.getValue()),
1450 xmrElems, interfaceBuilder);
1451 }
1452
1453 return anyFailed;
1454 })
1455 .Default([](auto a) { return true; });
1456}
1457
1458bool GrandCentralPass::traverseViewField(
1459 Attribute field, VerbatimBuilder &path,
1460 SmallVector<VerbatimXMRbuilder> &xmrElems,
1461 SmallVector<InterfaceElemsBuilder> &interfaceBuilder, ViewIntrinsicOp view,
1462 size_t &idx) {
1463 return TypeSwitch<Attribute, bool>(field)
1464 .Case<AugmentedGroundTypeAttr>([&](AugmentedGroundTypeAttr ground) {
1465 // Grab next signal, increment index counter.
1466 auto index = idx++;
1467 if (index >= view.getNumOperands()) {
1468 view.emitOpError("more ground types needed (")
1469 << idx << " so far) than view has operands ("
1470 << view.getNumOperands() << ")";
1471 return false;
1472 }
1473
1474 auto val = view.getOperand(index);
1475 auto tpe = type_cast<FIRRTLBaseType>(val.getType());
1476
1477 // If the type is zero-width then do not emit an XMR.
1478 if (!tpe.getBitWidthOrSentinel())
1479 return true;
1480
1481 /// Increment all the indices inside `{{`, `}}` by one. This is to
1482 /// indicate that a value is added to the `substitutions` of the
1483 /// verbatim op, other than the symbols.
1484 auto getStrAndIncrementIds = [&](StringRef base) -> StringAttr {
1485 SmallString<128> replStr;
1486 StringRef begin = "{{";
1487 StringRef end = "}}";
1488 // The replacement string.
1489 size_t from = 0;
1490 while (from < base.size()) {
1491 // Search for the first `{{` and `}}`.
1492 size_t beginAt = base.find(begin, from);
1493 size_t endAt = base.find(end, from);
1494 // If not found, then done.
1495 if (beginAt == StringRef::npos || endAt == StringRef::npos ||
1496 (beginAt > endAt)) {
1497 replStr.append(base.substr(from));
1498 break;
1499 }
1500 // Copy the string as is, until the `{{`.
1501 replStr.append(base.substr(from, beginAt - from));
1502 // Advance `from` to the character after the `}}`.
1503 from = endAt + 2;
1504 auto idChar = base.substr(beginAt + 2, endAt - beginAt - 2);
1505 int idNum;
1506 bool failed = idChar.getAsInteger(10, idNum);
1507 (void)failed;
1508 assert(!failed && "failed to parse integer from verbatim string");
1509 // Now increment the id and append.
1510 replStr.append("{{");
1511 Twine(idNum + 1).toVector(replStr);
1512 replStr.append("}}");
1513 }
1514 return StringAttr::get(&getContext(), "assign " + replStr + ";");
1515 };
1516
1517 // This is the new style of XMRs using RefTypes. The value substitution
1518 // index is set to -1, as it will be incremented when generating the
1519 // string.
1520 path += " = {{-1}}";
1521
1522 // Assemble the verbatim op.
1523 auto mod = view->getParentOfType<FModuleOp>();
1524 xmrElems.emplace_back(val, getStrAndIncrementIds(path.getString()),
1525 ArrayAttr::get(&getContext(), path.getSymbols()),
1526 mod);
1527 return true;
1528 })
1529 .Case<AugmentedVectorTypeAttr>([&](auto vector) {
1530 bool notFailed = true;
1531 auto elements = vector.getElements();
1532 for (size_t i = 0, e = elements.size(); i != e; ++i) {
1533 auto field = fromViewAttr(view, elements[i]);
1534 if (!field)
1535 return false;
1536 notFailed &= traverseViewField(
1537 *field, path.snapshot().append("[" + Twine(i) + "]"), xmrElems,
1538 interfaceBuilder, view, idx);
1539 }
1540 return notFailed;
1541 })
1542 .Case<AugmentedBundleTypeAttr>([&](AugmentedBundleTypeAttr bundle) {
1543 bool anyFailed = true;
1544 for (auto element : bundle.getElements()) {
1545 auto field = fromViewAttr(view, element);
1546 if (!field)
1547 return false;
1548 auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1549 if (!name)
1550 name = cast<DictionaryAttr>(element).getAs<StringAttr>("defName");
1551 anyFailed &= traverseViewField(
1552 *field, path.snapshot().append("." + name.getValue()), xmrElems,
1553 interfaceBuilder, view, idx);
1554 }
1555
1556 return anyFailed;
1557 })
1558 .Default([](auto a) { return true; });
1559}
1560
1561std::optional<TypeSum> GrandCentralPass::computeField(
1562 Attribute field, IntegerAttr id, VerbatimBuilder &path,
1563 SmallVector<VerbatimXMRbuilder> &xmrElems,
1564 SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1565 return TypeSwitch<Attribute, std::optional<TypeSum>>(field)
1566 .Case<AugmentedGroundTypeAttr>(
1567 [&](AugmentedGroundTypeAttr ground) -> std::optional<TypeSum> {
1568 // Traverse to generate mappings.
1569 if (!traverseField(field, id, path, xmrElems, interfaceBuilder))
1570 return std::nullopt;
1571 FieldRef fieldRef = leafMap.lookup(ground.getID()).field;
1572 auto value = fieldRef.getValue();
1573 auto fieldID = fieldRef.getFieldID();
1574 auto tpe = firrtl::type_cast<FIRRTLBaseType>(
1576 fieldID));
1577 if (!tpe.isGround()) {
1578 value.getDefiningOp()->emitOpError()
1579 << "cannot be added to interface with id '"
1580 << id.getValue().getZExtValue()
1581 << "' because it is not a ground type";
1582 return std::nullopt;
1583 }
1584 return TypeSum(IntegerType::get(getOperation().getContext(),
1585 tpe.getBitWidthOrSentinel()));
1586 })
1587 .Case<AugmentedVectorTypeAttr>(
1588 [&](AugmentedVectorTypeAttr vector) -> std::optional<TypeSum> {
1589 auto elements = vector.getElements();
1590 if (elements.empty())
1591 llvm::report_fatal_error(
1592 "unexpected empty augmented vector in GrandCentral View");
1593 auto firstElement = fromAttr(elements[0]);
1594 if (!firstElement)
1595 return std::nullopt;
1596 auto elementType = computeField(
1597 *firstElement, id, path.snapshot().append("[" + Twine(0) + "]"),
1598 xmrElems, interfaceBuilder);
1599 if (!elementType)
1600 return std::nullopt;
1601
1602 for (size_t i = 1, e = elements.size(); i != e; ++i) {
1603 auto subField = fromAttr(elements[i]);
1604 if (!subField)
1605 return std::nullopt;
1606 (void)traverseField(*subField, id,
1607 path.snapshot().append("[" + Twine(i) + "]"),
1608 xmrElems, interfaceBuilder);
1609 }
1610
1611 if (auto *tpe = std::get_if<Type>(&*elementType))
1612 return TypeSum(
1613 hw::UnpackedArrayType::get(*tpe, elements.getValue().size()));
1614 auto str = std::get<VerbatimType>(*elementType);
1615 str.dimensions.push_back(elements.getValue().size());
1616 return TypeSum(str);
1617 })
1618 .Case<AugmentedBundleTypeAttr>(
1619 [&](AugmentedBundleTypeAttr bundle) -> TypeSum {
1620 auto ifaceName =
1621 traverseBundle(bundle, id, path, xmrElems, interfaceBuilder);
1622 assert(ifaceName && *ifaceName);
1623 return VerbatimType({ifaceName->str(), true});
1624 });
1625}
1626
1627std::optional<TypeSum> GrandCentralPass::computeViewField(
1628 Attribute field, VerbatimBuilder &path,
1629 SmallVector<VerbatimXMRbuilder> &xmrElems,
1630 SmallVector<InterfaceElemsBuilder> &interfaceBuilder, ViewIntrinsicOp view,
1631 size_t &idx) {
1632 return TypeSwitch<Attribute, std::optional<TypeSum>>(field)
1633 .Case<AugmentedGroundTypeAttr>(
1634 [&](AugmentedGroundTypeAttr ground) -> std::optional<TypeSum> {
1635 // Traverse to generate mappings.
1636 auto index = idx;
1637 if (!traverseViewField(field, path, xmrElems, interfaceBuilder,
1638 view, idx))
1639 return std::nullopt;
1640
1641 auto val = view.getOperand(index);
1642 auto tpe = dyn_cast<FIRRTLBaseType>(val.getType());
1643 if (!tpe || !tpe.isGround()) {
1644 mlir::emitError(val.getLoc(), "cannot be added to interface, "
1645 "because it is not a ground type")
1646 .attachNote(view.getLoc())
1647 .append("interface part of view");
1648 return std::nullopt;
1649 }
1650
1651 return TypeSum(
1652 IntegerType::get(&getContext(), tpe.getBitWidthOrSentinel()));
1653 })
1654 .Case<AugmentedVectorTypeAttr>(
1655 [&](AugmentedVectorTypeAttr vector) -> std::optional<TypeSum> {
1656 auto elements = vector.getElements();
1657 if (elements.empty())
1658 llvm::report_fatal_error(
1659 "unexpected empty augmented vector in GrandCentral View");
1660 auto firstElement = fromViewAttr(view, elements[0]);
1661 if (!firstElement)
1662 return std::nullopt;
1663 auto elementType = computeViewField(
1664 *firstElement, path.snapshot().append("[" + Twine(0) + "]"),
1665 xmrElems, interfaceBuilder, view, idx);
1666 if (!elementType)
1667 return std::nullopt;
1668
1669 for (size_t i = 1, e = elements.size(); i != e; ++i) {
1670 auto subField = fromViewAttr(view, elements[i]);
1671 if (!subField)
1672 return std::nullopt;
1673 (void)traverseViewField(
1674 *subField, path.snapshot().append("[" + Twine(i) + "]"),
1675 xmrElems, interfaceBuilder, view, idx);
1676 }
1677
1678 if (auto *tpe = std::get_if<Type>(&*elementType))
1679 return TypeSum(
1680 hw::UnpackedArrayType::get(*tpe, elements.getValue().size()));
1681 auto str = std::get<VerbatimType>(*elementType);
1682 str.dimensions.push_back(elements.getValue().size());
1683 return TypeSum(str);
1684 })
1685 .Case<AugmentedBundleTypeAttr>(
1686 [&](AugmentedBundleTypeAttr bundle) -> TypeSum {
1687 auto ifaceName = traverseViewBundle(bundle, path, xmrElems,
1688 interfaceBuilder, view, idx);
1689 assert(ifaceName && *ifaceName);
1690 return VerbatimType({ifaceName->str(), true});
1691 });
1692}
1693
1694/// Traverse an Annotation that is an AugmentedBundleType. During traversal,
1695/// construct any discovered SystemVerilog interfaces. If this is the root
1696/// interface, instantiate that interface in the companion. Recurse into fields
1697/// of the AugmentedBundleType to construct nested interfaces and generate
1698/// stringy-typed SystemVerilog hierarchical references to drive the
1699/// interface. Returns false on any failure and true on success.
1700std::optional<StringAttr> GrandCentralPass::traverseBundle(
1701 AugmentedBundleTypeAttr bundle, IntegerAttr id, VerbatimBuilder &path,
1702 SmallVector<VerbatimXMRbuilder> &xmrElems,
1703 SmallVector<InterfaceElemsBuilder> &interfaceBuilder) {
1704
1705 unsigned lastIndex = interfaceBuilder.size();
1706 auto iFaceName = StringAttr::get(
1707 &getContext(), getNamespace().newName(getInterfaceName(bundle)));
1708 interfaceBuilder.emplace_back(iFaceName, id);
1709
1710 for (auto element : bundle.getElements()) {
1711 auto field = fromAttr(element);
1712 if (!field)
1713 return std::nullopt;
1714
1715 auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1716 // auto signalSym = hw::InnerRefAttr::get(iface.sym_nameAttr(), name);
1717 // TODO: The `append(name.getValue())` in the following should actually be
1718 // `append(signalSym)`, but this requires that `computeField` and the
1719 // functions it calls always return a type for which we can construct an
1720 // `InterfaceSignalOp`. Since nested interface instances are currently
1721 // busted (due to the interface being a symbol table), this doesn't work at
1722 // the moment. Passing a `name` works most of the time, but can be brittle
1723 // if the interface field requires renaming in the output (e.g. due to
1724 // naming conflicts).
1725 auto elementType = computeField(
1726 *field, id, path.snapshot().append(".").append(name.getValue()),
1727 xmrElems, interfaceBuilder);
1728 if (!elementType)
1729 return std::nullopt;
1730 StringAttr description =
1731 cast<DictionaryAttr>(element).getAs<StringAttr>("description");
1732 interfaceBuilder[lastIndex].elementsList.emplace_back(description, name,
1733 *elementType);
1734 }
1735 return iFaceName;
1736}
1737
1738/// Traverse an attribute that is an AugmentedBundleType. During traversal,
1739/// construct any discovered SystemVerilog interfaces. If this is the root
1740/// interface, instantiate that interface in the companion. Recurse into fields
1741/// of the AugmentedBundleType to construct nested interfaces and generate
1742/// stringy-typed SystemVerilog hierarchical references to drive the
1743/// interface. Returns false on any failure and true on success.
1744std::optional<StringAttr> GrandCentralPass::traverseViewBundle(
1745 AugmentedBundleTypeAttr bundle, VerbatimBuilder &path,
1746 SmallVector<VerbatimXMRbuilder> &xmrElems,
1747 SmallVector<InterfaceElemsBuilder> &interfaceBuilder, ViewIntrinsicOp view,
1748 size_t &idx) {
1749
1750 // TODO: Require as part of attribute, structurally.
1751 if (!bundle.getDefName()) {
1752 view.emitOpError("missing 'defName' at top-level");
1753 return std::nullopt;
1754 }
1755 if (!bundle.getElements()) {
1756 view.emitOpError("missing 'elements' at top-level");
1757 return std::nullopt;
1758 }
1759
1760 unsigned lastIndex = interfaceBuilder.size();
1761 auto iFaceName = StringAttr::get(
1762 &getContext(), getNamespace().newName(getInterfaceName(bundle)));
1763 interfaceBuilder.emplace_back(iFaceName, IntegerAttr() /* XXX */);
1764
1765 for (auto element : bundle.getElements()) {
1766 auto field = fromViewAttr(view, element);
1767 if (!field)
1768 return std::nullopt;
1769
1770 auto name = cast<DictionaryAttr>(element).getAs<StringAttr>("name");
1771 if (!name) {
1772 view.emitError("missing 'name' field in element of bundle: ") << element;
1773 return std::nullopt;
1774 }
1775 // auto signalSym = hw::InnerRefAttr::get(iface.sym_nameAttr(), name);
1776 // TODO: The `append(name.getValue())` in the following should actually be
1777 // `append(signalSym)`, but this requires that `computeField` and the
1778 // functions it calls always return a type for which we can construct an
1779 // `InterfaceSignalOp`. Since nested interface instances are currently
1780 // busted (due to the interface being a symbol table), this doesn't work at
1781 // the moment. Passing a `name` works most of the time, but can be brittle
1782 // if the interface field requires renaming in the output (e.g. due to
1783 // naming conflicts).
1784 auto elementType = computeViewField(
1785 *field, path.snapshot().append(".").append(name.getValue()), xmrElems,
1786 interfaceBuilder, view, idx);
1787 if (!elementType)
1788 return std::nullopt;
1789 StringAttr description =
1790 cast<DictionaryAttr>(element).getAs<StringAttr>("description");
1791 interfaceBuilder[lastIndex].elementsList.emplace_back(description, name,
1792 *elementType);
1793 }
1794 return iFaceName;
1795}
1796
1797/// Return the module that is associated with this value. Use the cached/lazily
1798/// constructed symbol table to make this fast.
1799igraph::ModuleOpInterface
1800GrandCentralPass::getEnclosingModule(Value value, FlatSymbolRefAttr sym) {
1801 if (auto blockArg = dyn_cast<BlockArgument>(value))
1802 return cast<igraph::ModuleOpInterface>(blockArg.getOwner()->getParentOp());
1803
1804 auto *op = value.getDefiningOp();
1805 if (InstanceOp instance = dyn_cast<InstanceOp>(op))
1806 return getSymbolTable().lookup<igraph::ModuleOpInterface>(
1807 instance.getModuleNameAttr().getValue());
1808
1809 return op->getParentOfType<igraph::ModuleOpInterface>();
1810}
1811
1812/// This method contains the business logic of this pass.
1813void GrandCentralPass::runOnOperation() {
1814 LLVM_DEBUG(debugPassHeader(this) << "\n");
1815
1816 CircuitOp circuitOp = getOperation();
1817
1818 // Look at the circuit annotations to do two things:
1819 //
1820 // 1. Determine extraction information (directory and filename).
1821 // 2. Populate a worklist of all annotations that encode interfaces.
1822 //
1823 // Remove annotations encoding interfaces, but leave extraction information as
1824 // this may be needed by later passes.
1825 SmallVector<Annotation> worklist;
1826 bool removalError = false;
1827 AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
1829 // If we are in "Instantiate" companion mode, then we don't need to
1830 // create the interface, so we can skip adding it to the worklist. This
1831 // is a janky hack for situations where you want to synthesize assertion
1832 // logic included in the companion, but don't want to have a dead
1833 // interface hanging around (or have problems with tools understanding
1834 // interfaces).
1835 if (companionMode != CompanionMode::Instantiate)
1836 worklist.push_back(anno);
1837 ++numAnnosRemoved;
1838 return true;
1839 }
1841 if (maybeExtractInfo) {
1842 emitCircuitError("more than one 'ExtractGrandCentralAnnotation' was "
1843 "found, but exactly one must be provided");
1844 removalError = true;
1845 return false;
1846 }
1847
1848 auto directory = anno.getMember<StringAttr>("directory");
1849 auto filename = anno.getMember<StringAttr>("filename");
1850 if (!directory || !filename) {
1851 emitCircuitError()
1852 << "contained an invalid 'ExtractGrandCentralAnnotation' that does "
1853 "not contain 'directory' and 'filename' fields: "
1854 << anno.getDict();
1855 removalError = true;
1856 return false;
1857 }
1858 if (directory.getValue().empty())
1859 directory = StringAttr::get(circuitOp.getContext(), ".");
1860
1861 maybeExtractInfo = {directory, filename};
1862 // Do not delete this annotation. Extraction info may be needed later.
1863 return false;
1864 }
1866 if (maybeHierarchyFileYAML) {
1867 emitCircuitError("more than one 'GrandCentralHierarchyFileAnnotation' "
1868 "was found, but zero or one may be provided");
1869 removalError = true;
1870 return false;
1871 }
1872
1873 auto filename = anno.getMember<StringAttr>("filename");
1874 if (!filename) {
1875 emitCircuitError()
1876 << "contained an invalid 'GrandCentralHierarchyFileAnnotation' "
1877 "that does not contain 'directory' and 'filename' fields: "
1878 << anno.getDict();
1879 removalError = true;
1880 return false;
1881 }
1882
1883 maybeHierarchyFileYAML = filename;
1884 ++numAnnosRemoved;
1885 return true;
1886 }
1887 if (anno.isClass(testBenchDirAnnoClass)) {
1888 testbenchDir = anno.getMember<StringAttr>("dirname");
1889 return false;
1890 }
1891 return false;
1892 });
1893
1894 if (removalError)
1895 return signalPassFailure();
1896
1897 LLVM_DEBUG({
1898 llvm::dbgs() << "Extraction Info:\n";
1899 if (maybeExtractInfo)
1900 llvm::dbgs() << " directory: " << maybeExtractInfo->directory << "\n"
1901 << " filename: " << maybeExtractInfo->bindFilename << "\n";
1902 else
1903 llvm::dbgs() << " <none>\n";
1904 llvm::dbgs() << "DUT: ";
1905 if (auto dut = instanceInfo->getDut())
1906 llvm::dbgs() << dut.getModuleName() << "\n";
1907 else
1908 llvm::dbgs() << "<none>\n";
1909 llvm::dbgs()
1910 << "Hierarchy File Info (from GrandCentralHierarchyFileAnnotation):\n"
1911 << " filename: ";
1912 if (maybeHierarchyFileYAML)
1913 llvm::dbgs() << *maybeHierarchyFileYAML;
1914 else
1915 llvm::dbgs() << "<none>";
1916 llvm::dbgs() << "\n";
1917 });
1918
1919 // per-YAML output list of interfaces.
1920 llvm::SmallMapVector<StringAttr, SmallVector<sv::InterfaceOp, 0>, 1>
1921 interfaceYAMLMap;
1922 if (maybeHierarchyFileYAML.has_value())
1923 interfaceYAMLMap[maybeHierarchyFileYAML.value()] = {};
1924
1925 // Scan entire design for View operations.
1926 SmallVector<ViewIntrinsicOp> views;
1927 circuitOp.walk([&views](ViewIntrinsicOp view) { views.push_back(view); });
1928
1929 // Exit immediately if no annotations indicative of interfaces that need to be
1930 // built exist. However, still generate the YAML file if the annotation for
1931 // this was passed in because some flows expect this.
1932 if (worklist.empty() && views.empty()) {
1933 for (auto &[yamlPath, intfs] : interfaceYAMLMap)
1934 emitHierarchyYamlFile(yamlPath.getValue(), intfs);
1935 return markAllAnalysesPreserved();
1936 }
1937
1938 // Setup the builder to create ops _inside the FIRRTL circuit_. This is
1939 // necessary because interfaces and interface instances are created.
1940 // Instances link to their definitions via symbols and we don't want to
1941 // break this.
1942 auto builder = OpBuilder::atBlockEnd(circuitOp.getBodyBlock());
1943
1944 // Maybe get an "id" from an Annotation. Generate error messages on the op if
1945 // no "id" exists.
1946 auto getID = [&](Operation *op,
1947 Annotation annotation) -> std::optional<IntegerAttr> {
1948 auto id = annotation.getMember<IntegerAttr>("id");
1949 if (!id) {
1950 op->emitOpError()
1951 << "contained a malformed "
1952 "'sifive.enterprise.grandcentral.AugmentedGroundType' annotation "
1953 "that did not contain an 'id' field";
1954 removalError = true;
1955 return std::nullopt;
1956 }
1957 return id;
1958 };
1959
1960 /// TODO: Handle this differently to allow construction of an options
1961 auto instancePathCache = InstancePathCache(getAnalysis<InstanceGraph>());
1962 instancePaths = &instancePathCache;
1963 instanceInfo = &getAnalysis<InstanceInfo>();
1964
1965 // Maybe return the lone instance of a module. Generate errors on the op if
1966 // the module is not instantiated or is multiply instantiated.
1967 auto exactlyOneInstance = [&](FModuleOp op,
1968 StringRef msg) -> std::optional<InstanceOp> {
1969 auto *node = instancePaths->instanceGraph[op];
1970
1971 switch (node->getNumUses()) {
1972 case 0:
1973 op->emitOpError() << "is marked as a GrandCentral '" << msg
1974 << "', but is never instantiated";
1975 return std::nullopt;
1976 case 1:
1977 return cast<InstanceOp>(*(*node->uses().begin())->getInstance());
1978 default:
1979 auto diag = op->emitOpError()
1980 << "is marked as a GrandCentral '" << msg
1981 << "', but it is instantiated more than once";
1982 for (auto *instance : node->uses())
1983 diag.attachNote(instance->getInstance()->getLoc())
1984 << "it is instantiated here";
1985 return std::nullopt;
1986 }
1987 };
1988
1989 nlaTable = &getAnalysis<NLATable>();
1990
1991 /// Walk the circuit and extract all information related to scattered Grand
1992 /// Central annotations. This is used to populate: (1) the companionIDMap and
1993 /// (2) the leafMap. Annotations are removed as they are discovered and if
1994 /// they are not malformed.
1995 DenseSet<Operation *> modulesToDelete;
1996 circuitOp.walk([&](Operation *op) {
1997 TypeSwitch<Operation *>(op)
1998 .Case<RegOp, RegResetOp, WireOp, NodeOp>([&](auto op) {
1999 AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
2000 if (!annotation.isClass(augmentedGroundTypeClass))
2001 return false;
2002 auto maybeID = getID(op, annotation);
2003 if (!maybeID)
2004 return false;
2005 auto sym =
2006 annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
2007 leafMap[*maybeID] = {{op.getResult(), annotation.getFieldID()},
2008 sym};
2009 ++numAnnosRemoved;
2010 return true;
2011 });
2012 })
2013 // TODO: Figure out what to do with this.
2014 .Case<InstanceOp>([&](auto op) {
2015 AnnotationSet::removePortAnnotations(op, [&](unsigned i,
2016 Annotation annotation) {
2017 if (!annotation.isClass(augmentedGroundTypeClass))
2018 return false;
2019 op.emitOpError()
2020 << "is marked as an interface element, but this should be "
2021 "impossible due to how the Chisel Grand Central API works";
2022 removalError = true;
2023 return false;
2024 });
2025 })
2026 .Case<MemOp>([&](auto op) {
2027 AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
2028 if (!annotation.isClass(augmentedGroundTypeClass))
2029 return false;
2030 op.emitOpError()
2031 << "is marked as an interface element, but this does not make "
2032 "sense (is there a scattering bug or do you have a "
2033 "malformed hand-crafted MLIR circuit?)";
2034 removalError = true;
2035 return false;
2036 });
2038 op, [&](unsigned i, Annotation annotation) {
2039 if (!annotation.isClass(augmentedGroundTypeClass))
2040 return false;
2041 op.emitOpError()
2042 << "has port '" << i
2043 << "' marked as an interface element, but this does not "
2044 "make sense (is there a scattering bug or do you have a "
2045 "malformed hand-crafted MLIR circuit?)";
2046 removalError = true;
2047 return false;
2048 });
2049 })
2050 .Case<FModuleOp>([&](FModuleOp op) {
2051 // Handle annotations on the ports.
2052 AnnotationSet::removePortAnnotations(op, [&](unsigned i,
2053 Annotation annotation) {
2054 if (!annotation.isClass(augmentedGroundTypeClass))
2055 return false;
2056 auto maybeID = getID(op, annotation);
2057 if (!maybeID)
2058 return false;
2059 auto sym =
2060 annotation.getMember<FlatSymbolRefAttr>("circt.nonlocal");
2061 leafMap[*maybeID] = {{op.getArgument(i), annotation.getFieldID()},
2062 sym};
2063 ++numAnnosRemoved;
2064 return true;
2065 });
2066
2067 // Handle annotations on the module.
2068 AnnotationSet::removeAnnotations(op, [&](Annotation annotation) {
2069 if (!annotation.getClass().starts_with(viewAnnoClass))
2070 return false;
2071 auto isNonlocal = annotation.getMember<FlatSymbolRefAttr>(
2072 "circt.nonlocal") != nullptr;
2073 auto name = annotation.getMember<StringAttr>("name");
2074 auto id = annotation.getMember<IntegerAttr>("id");
2075 if (!id) {
2076 op.emitOpError()
2077 << "has a malformed "
2078 "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
2079 "not contain an 'id' field with an 'IntegerAttr' value";
2080 goto FModuleOp_error;
2081 }
2082 if (!name) {
2083 op.emitOpError()
2084 << "has a malformed "
2085 "'sifive.enterprise.grandcentral.ViewAnnotation' that did "
2086 "not contain a 'name' field with a 'StringAttr' value";
2087 goto FModuleOp_error;
2088 }
2089
2090 // If this is a companion, then:
2091 // 1. Insert it into the companion map
2092 // 2. Create a new mapping module.
2093 // 3. Instantiate the mapping module in the companion.
2094 // 4. Check that the companion is instantiated exactly once.
2095 // 5. Set attributes on that lone instance so it will become a
2096 // bind if extraction information was provided. If a DUT is
2097 // known, then anything in the test harness will not be
2098 // extracted.
2099 if (annotation.getClass() == companionAnnoClass) {
2100 builder.setInsertionPointToEnd(circuitOp.getBodyBlock());
2101
2102 companionIDMap[id] = {name.getValue(), op, isNonlocal};
2103
2104 // Assert that the companion is instantiated once and only once.
2105 auto instance = exactlyOneInstance(op, "companion");
2106 if (!instance)
2107 goto FModuleOp_error;
2108
2109 // Companions are only allowed to take inputs.
2110 for (auto [i, result] : llvm::enumerate(instance->getResults())) {
2111 if (instance->getPortDirection(i) == Direction::In)
2112 continue;
2113 // Do not allow any outputs in the drop mode.
2114 auto ty = result.getType();
2115 if (isa<RefType>(ty) && companionMode != CompanionMode::Drop)
2116 continue;
2117 op.emitOpError()
2118 << "companion instance cannot have output ports";
2119 goto FModuleOp_error;
2120 }
2121
2122 // If no extraction info was provided, exit. Otherwise, setup the
2123 // lone instance of the companion to be lowered as a bind.
2124 if (!maybeExtractInfo) {
2125 ++numAnnosRemoved;
2126 return true;
2127 }
2128
2129 // If the companion is instantiated above the DUT, then don't
2130 // extract it.
2131 if (!instanceInfo->allInstancesUnderEffectiveDut(op)) {
2132 ++numAnnosRemoved;
2133 return true;
2134 }
2135
2136 // Look for any modules/extmodules _only_ instantiated by the
2137 // companion. If these have no output file attribute, then mark
2138 // them as being extracted into the Grand Central directory.
2139 InstanceGraphNode *companionNode =
2140 instancePaths->instanceGraph.lookup(op);
2141
2142 LLVM_DEBUG({
2143 llvm::dbgs()
2144 << "Found companion module: "
2145 << companionNode->getModule().getModuleName() << "\n"
2146 << " submodules exclusively instantiated "
2147 "(including companion):\n";
2148 });
2149
2150 if (companionMode == CompanionMode::Drop) {
2151 // Delete the instance if companions are disabled.
2152 OpBuilder builder(&getContext());
2153 for (auto port : instance->getResults()) {
2154 builder.setInsertionPointAfterValue(port);
2155 auto wire =
2156 builder.create<WireOp>(port.getLoc(), port.getType());
2157 port.replaceAllUsesWith(wire.getResult());
2158 }
2159 instance->erase();
2160 } else {
2161 // Lower the companion to a bind unless the user told us
2162 // explicitly not to.
2163 if (companionMode == CompanionMode::Bind)
2164 (*instance)->setAttr("lowerToBind", builder.getUnitAttr());
2165
2166 (*instance)->setAttr(
2167 "output_file",
2168 hw::OutputFileAttr::getFromFilename(
2169 &getContext(),
2170 maybeExtractInfo->bindFilename.getValue(),
2171 /*excludeFromFileList=*/true));
2172 }
2173
2174 for (auto &node : llvm::depth_first(companionNode)) {
2175 auto mod = node->getModule();
2176
2177 // Check to see if we should change the output directory of a
2178 // module. Only update in the following conditions:
2179 // 1) The module is the companion.
2180 // 2) The module is NOT instantiated by the effective DUT or
2181 // is under a bind.
2182 auto *modNode = instancePaths->instanceGraph.lookup(mod);
2183 if (modNode != companionNode &&
2184 instanceInfo->anyInstanceInEffectiveDesign(
2185 modNode->getModule()))
2186 continue;
2187
2188 LLVM_DEBUG({
2189 llvm::dbgs()
2190 << " - module: " << mod.getModuleName() << "\n";
2191 });
2192
2193 if (auto extmodule = dyn_cast<FExtModuleOp>(*mod)) {
2194 for (auto anno : AnnotationSet(extmodule)) {
2195 if (companionMode == CompanionMode::Drop) {
2196 modulesToDelete.insert(mod);
2197 break;
2198 }
2199 if (!anno.isClass(blackBoxInlineAnnoClass) &&
2201 continue;
2202 if (extmodule->hasAttr("output_file"))
2203 break;
2204 extmodule->setAttr(
2205 "output_file",
2206 hw::OutputFileAttr::getAsDirectory(
2207 &getContext(),
2208 maybeExtractInfo->directory.getValue()));
2209 break;
2210 }
2211 continue;
2212 }
2213
2214 if (companionMode == CompanionMode::Drop) {
2215 modulesToDelete.insert(mod);
2216 } else {
2217 // Move this module under the Grand Central output directory
2218 // if no pre-existing output file information is present.
2219 if (!mod->hasAttr("output_file")) {
2220 mod->setAttr("output_file",
2221 hw::OutputFileAttr::getAsDirectory(
2222 &getContext(),
2223 maybeExtractInfo->directory.getValue(),
2224 /*excludeFromFileList=*/true,
2225 /*includeReplicatedOps=*/true));
2226 mod->setAttr("comment", builder.getStringAttr(
2227 "VCS coverage exclude_file"));
2228 }
2229 }
2230 }
2231
2232 ++numAnnosRemoved;
2233 return true;
2234 }
2235
2236 op.emitOpError()
2237 << "unknown annotation class: " << annotation.getDict();
2238
2239 FModuleOp_error:
2240 removalError = true;
2241 return false;
2242 });
2243 });
2244 });
2245
2246 if (removalError)
2247 return signalPassFailure();
2248
2249 if (companionMode == CompanionMode::Drop) {
2250 for (auto *mod : modulesToDelete) {
2251 auto name = cast<FModuleLike>(mod).getModuleNameAttr();
2252
2253 DenseSet<hw::HierPathOp> nlas;
2254 nlaTable->getNLAsInModule(name, nlas);
2255 nlaTable->removeNLAsfromModule(nlas, name);
2256 for (auto nla : nlas) {
2257 if (nla.root() == name)
2258 nla.erase();
2259 }
2260
2261 mod->erase();
2262 }
2263
2264 for (auto &[yamlPath, intfs] : interfaceYAMLMap)
2265 emitHierarchyYamlFile(yamlPath.getValue(), intfs);
2266 return;
2267 }
2268
2269 LLVM_DEBUG({
2270 // Print out the companion map and all leaf values that were discovered.
2271 // Sort these by their keys before printing to make this easier to read.
2272 SmallVector<IntegerAttr> ids;
2273 auto sort = [&ids]() {
2274 llvm::sort(ids, [](IntegerAttr a, IntegerAttr b) {
2275 return a.getValue().getZExtValue() < b.getValue().getZExtValue();
2276 });
2277 };
2278 for (auto tuple : companionIDMap)
2279 ids.push_back(cast<IntegerAttr>(tuple.first));
2280 sort();
2281 llvm::dbgs() << "companionIDMap:\n";
2282 for (auto id : ids) {
2283 auto value = companionIDMap.lookup(id);
2284 llvm::dbgs() << " - " << id.getValue() << ": "
2285 << value.companion.getName() << " -> " << value.name << "\n";
2286 }
2287 ids.clear();
2288 for (auto tuple : leafMap)
2289 ids.push_back(cast<IntegerAttr>(tuple.first));
2290 sort();
2291 llvm::dbgs() << "leafMap:\n";
2292 for (auto id : ids) {
2293 auto fieldRef = leafMap.lookup(id).field;
2294 auto value = fieldRef.getValue();
2295 auto fieldID = fieldRef.getFieldID();
2296 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
2297 FModuleOp module = cast<FModuleOp>(blockArg.getOwner()->getParentOp());
2298 llvm::dbgs() << " - " << id.getValue() << ": "
2299 << module.getName() + ">" +
2300 module.getPortName(blockArg.getArgNumber());
2301 if (fieldID)
2302 llvm::dbgs() << ", fieldID=" << fieldID;
2303 llvm::dbgs() << "\n";
2304 } else {
2305 llvm::dbgs() << " - " << id.getValue() << ": "
2306 << cast<StringAttr>(value.getDefiningOp()->getAttr("name"))
2307 .getValue();
2308 if (fieldID)
2309 llvm::dbgs() << ", fieldID=" << fieldID;
2310 llvm::dbgs() << "\n";
2311 }
2312 }
2313 });
2314
2315 // Now, iterate over the worklist of interface-encoding annotations to create
2316 // the interface and all its sub-interfaces (interfaces that it instantiates),
2317 // instantiate the top-level interface, and generate a "mappings file" that
2318 // will use XMRs to drive the interface. If extraction info is available,
2319 // then the top-level instantiate interface will be marked for extraction via
2320 // a SystemVerilog bind.
2322 companionToInterfaceMap;
2323 auto compareInterfaceSignal = [&](InterfaceElemsBuilder &lhs,
2324 InterfaceElemsBuilder &rhs) {
2325 auto compareProps = [&](InterfaceElemsBuilder::Properties &lhs,
2326 InterfaceElemsBuilder::Properties &rhs) {
2327 // If it's a verbatim op, no need to check the string, because the
2328 // interface names might not match. As long as the signal types match that
2329 // is sufficient.
2330 if (lhs.elemType.index() == 0 && rhs.elemType.index() == 0)
2331 return true;
2332 if (std::get<Type>(lhs.elemType) == std::get<Type>(rhs.elemType))
2333 return true;
2334 return false;
2335 };
2336 return std::equal(lhs.elementsList.begin(), lhs.elementsList.end(),
2337 rhs.elementsList.begin(), compareProps);
2338 };
2339 for (auto anno : worklist) {
2340 auto bundle = AugmentedBundleTypeAttr::get(&getContext(), anno.getDict());
2341
2342 // The top-level AugmentedBundleType must have a global ID field so that
2343 // this can be linked to the companion.
2344 if (!bundle.isRoot()) {
2345 emitCircuitError() << "missing 'id' in root-level BundleType: "
2346 << anno.getDict() << "\n";
2347 removalError = true;
2348 continue;
2349 }
2350
2351 if (companionIDMap.count(bundle.getID()) == 0) {
2352 emitCircuitError() << "no companion found with 'id' value '"
2353 << bundle.getID().getValue().getZExtValue() << "'\n";
2354 removalError = true;
2355 continue;
2356 }
2357
2358 // Decide on a symbol name to use for the interface instance. This is needed
2359 // in `traverseBundle` as a placeholder for the connect operations.
2360 auto companionIter = companionIDMap.lookup(bundle.getID());
2361 auto companionModule = companionIter.companion;
2362 auto symbolName = getNamespace().newName(
2363 "__" + companionIDMap.lookup(bundle.getID()).name + "_" +
2364 getInterfaceName(bundle) + "__");
2365
2366 // Recursively walk the AugmentedBundleType to generate interfaces and XMRs.
2367 // Error out if this returns None (indicating that the annotation is
2368 // malformed in some way). A good error message is generated inside
2369 // `traverseBundle` or the functions it calls.
2370 auto instanceSymbol =
2371 hw::InnerRefAttr::get(SymbolTable::getSymbolName(companionModule),
2372 StringAttr::get(&getContext(), symbolName));
2373 VerbatimBuilder::Base verbatimData;
2374 VerbatimBuilder verbatim(verbatimData);
2375 verbatim += instanceSymbol;
2376 // List of interface elements.
2377
2378 SmallVector<VerbatimXMRbuilder> xmrElems;
2379 SmallVector<InterfaceElemsBuilder> interfaceBuilder;
2380
2381 auto ifaceName = traverseBundle(bundle, bundle.getID(), verbatim, xmrElems,
2382 interfaceBuilder);
2383 if (!ifaceName) {
2384 removalError = true;
2385 continue;
2386 }
2387
2388 if (companionIter.isNonlocal) {
2389 // If the companion module has two exactly same ViewAnnotation.companion
2390 // annotations, then add the interface for only one of them. This happens
2391 // when the companion is deduped.
2392 auto viewMapIter = companionToInterfaceMap.find(companionModule);
2393 if (viewMapIter != companionToInterfaceMap.end())
2394 if (std::equal(interfaceBuilder.begin(), interfaceBuilder.end(),
2395 viewMapIter->getSecond().begin(),
2396 compareInterfaceSignal)) {
2397 continue;
2398 }
2399
2400 companionToInterfaceMap[companionModule] = interfaceBuilder;
2401 }
2402
2403 if (interfaceBuilder.empty())
2404 continue;
2405 auto companionBuilder =
2406 OpBuilder::atBlockEnd(companionModule.getBodyBlock());
2407
2408 // Generate gathered XMR's.
2409 for (auto xmrElem : xmrElems) {
2410 auto uloc = companionBuilder.getUnknownLoc();
2411 companionBuilder.create<sv::VerbatimOp>(uloc, xmrElem.str, xmrElem.val,
2412 xmrElem.syms);
2413 }
2414 numXMRs += xmrElems.size();
2415
2416 sv::InterfaceOp topIface;
2417 for (const auto &ifaceBuilder : interfaceBuilder) {
2418 auto builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
2419 auto loc = getOperation().getLoc();
2420 sv::InterfaceOp iface =
2421 builder.create<sv::InterfaceOp>(loc, ifaceBuilder.iFaceName);
2422 if (!topIface)
2423 topIface = iface;
2424 ++numInterfaces;
2425 if (!instanceInfo->allInstancesUnderEffectiveDut(
2426 companionIDMap[ifaceBuilder.id].companion) &&
2427 testbenchDir)
2428 iface->setAttr("output_file",
2429 hw::OutputFileAttr::getAsDirectory(
2430 &getContext(), testbenchDir.getValue(),
2431 /*excludeFromFileList=*/true));
2432 else if (maybeExtractInfo)
2433 iface->setAttr("output_file",
2434 hw::OutputFileAttr::getAsDirectory(
2435 &getContext(), getOutputDirectory().getValue(),
2436 /*excludeFromFileList=*/true));
2437 iface.setCommentAttr(builder.getStringAttr("VCS coverage exclude_file"));
2438 builder.setInsertionPointToEnd(
2439 cast<sv::InterfaceOp>(iface).getBodyBlock());
2440 interfaceMap[FlatSymbolRefAttr::get(builder.getContext(),
2441 ifaceBuilder.iFaceName)] = iface;
2442 for (auto elem : ifaceBuilder.elementsList) {
2443
2444 auto uloc = builder.getUnknownLoc();
2445
2446 auto description = elem.description;
2447
2448 if (description) {
2449 auto descriptionOp = builder.create<sv::VerbatimOp>(
2450 uloc, ("// " + cleanupDescription(description.getValue())));
2451
2452 // If we need to generate a YAML representation of this interface,
2453 // then add an attribute indicating that this `sv::VerbatimOp` is
2454 // actually a description.
2455 if (maybeHierarchyFileYAML)
2456 descriptionOp->setAttr("firrtl.grandcentral.yaml.type",
2457 builder.getStringAttr("description"));
2458 }
2459 if (auto *str = std::get_if<VerbatimType>(&elem.elemType)) {
2460 auto instanceOp = builder.create<sv::VerbatimOp>(
2461 uloc, str->toStr(elem.elemName.getValue()));
2462
2463 // If we need to generate a YAML representation of the interface, then
2464 // add attributes that describe what this `sv::VerbatimOp` is.
2465 if (maybeHierarchyFileYAML) {
2466 if (str->instantiation)
2467 instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2468 builder.getStringAttr("instance"));
2469 else
2470 instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2471 builder.getStringAttr("unsupported"));
2472 instanceOp->setAttr("firrtl.grandcentral.yaml.name", elem.elemName);
2473 instanceOp->setAttr("firrtl.grandcentral.yaml.dimensions",
2474 builder.getI32ArrayAttr(str->dimensions));
2475 instanceOp->setAttr(
2476 "firrtl.grandcentral.yaml.symbol",
2477 FlatSymbolRefAttr::get(builder.getContext(), str->str));
2478 }
2479 continue;
2480 }
2481
2482 auto tpe = std::get<Type>(elem.elemType);
2483 builder.create<sv::InterfaceSignalOp>(uloc, elem.elemName.getValue(),
2484 tpe);
2485 }
2486 }
2487
2488 ++numViews;
2489
2490 if (maybeHierarchyFileYAML.has_value())
2491 interfaceYAMLMap[maybeHierarchyFileYAML.value()].push_back(topIface);
2492
2493 // Instantiate the interface inside the companion.
2494 builder.setInsertionPointToStart(companionModule.getBodyBlock());
2495 builder.create<sv::InterfaceInstanceOp>(
2496 getOperation().getLoc(), topIface.getInterfaceType(),
2497 companionIDMap.lookup(bundle.getID()).name,
2498 hw::InnerSymAttr::get(builder.getStringAttr(symbolName)));
2499
2500 // If no extraction information was present, then just leave the interface
2501 // instantiated in the companion. Otherwise, make it a bind.
2502 if (!maybeExtractInfo)
2503 continue;
2504
2505 // If the interface is associated with a companion that is instantiated
2506 // above the DUT (e.g.., in the test harness), then don't extract it.
2507 if (!instanceInfo->allInstancesUnderEffectiveDut(
2508 companionIDMap[bundle.getID()].companion))
2509 continue;
2510 }
2511
2512 for (auto view : views) {
2513 auto bundle = view.getAugmentedType();
2514
2515 assert(!bundle.isRoot() && "'id' found in firrtl.view");
2516
2517 if (!bundle.getDefName()) {
2518 view.emitOpError("missing 'defName' at top-level");
2519 removalError = true;
2520 continue;
2521 }
2522 if (!bundle.getElements()) {
2523 view.emitOpError("missing 'elements' at top-level");
2524 removalError = true;
2525 continue;
2526 }
2527
2528 auto viewParentMod = view->getParentOfType<FModuleLike>();
2529 auto symbolName = getModuleNamespace(viewParentMod)
2530 .newName("__" + getInterfaceName(bundle) + "__");
2531
2532 // Recursively walk the AugmentedBundleType to generate interfaces and XMRs.
2533 // Error out if this returns None (indicating that the view is
2534 // malformed in some way). A good error message is generated inside
2535 // `traverseViewBundle` or the functions it calls.
2536 auto instanceSymbol =
2537 hw::InnerRefAttr::get(SymbolTable::getSymbolName(viewParentMod),
2538 StringAttr::get(&getContext(), symbolName));
2539 VerbatimBuilder::Base verbatimData;
2540 VerbatimBuilder verbatim(verbatimData);
2541 verbatim += instanceSymbol;
2542 // List of interface elements.
2543
2544 SmallVector<VerbatimXMRbuilder> xmrElems;
2545 SmallVector<InterfaceElemsBuilder> interfaceBuilder;
2546
2547 size_t index = 0;
2548 auto ifaceName = traverseViewBundle(bundle, verbatim, xmrElems,
2549 interfaceBuilder, view, index);
2550 if (!ifaceName) {
2551 removalError = true;
2552 continue;
2553 }
2554 if (index != view.getNumOperands()) {
2555 assert(index < view.getNumOperands() &&
2556 "this should error while consuming");
2557 removalError = true;
2558 view.emitOpError() << "has too many operands: " << view.getNumOperands()
2559 << " operands but only " << index << " were needed";
2560 continue;
2561 }
2562
2563 if (interfaceBuilder.empty())
2564 continue;
2565 ImplicitLocOpBuilder viewBuilder(view.getLoc(), view);
2566 viewBuilder.setInsertionPointAfter(view);
2567
2568 // Generate gathered XMR's.
2569 for (auto xmrElem : xmrElems)
2570 viewBuilder.create<sv::VerbatimOp>(xmrElem.str, xmrElem.val,
2571 xmrElem.syms);
2572 numXMRs += xmrElems.size();
2573
2574 sv::InterfaceOp topIface;
2575 auto containingOutputFileAttr =
2576 viewParentMod->getAttrOfType<hw::OutputFileAttr>("output_file");
2577 auto yamlPath = view.getYamlFileAttr();
2578 for (const auto &ifaceBuilder : interfaceBuilder) {
2579 auto builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
2580 auto loc = getOperation().getLoc();
2581 sv::InterfaceOp iface =
2582 builder.create<sv::InterfaceOp>(loc, ifaceBuilder.iFaceName);
2583 if (!topIface)
2584 topIface = iface;
2585 ++numInterfaces;
2586
2587 // Propagate output_file information from parent to the interface.
2588 // Require/expect "AssignOutputDirs" ran before this.
2589 if (containingOutputFileAttr)
2590 iface->setAttr("output_file", containingOutputFileAttr);
2591
2592 iface.setCommentAttr(builder.getStringAttr("VCS coverage exclude_file"));
2593 builder.setInsertionPointToEnd(
2594 cast<sv::InterfaceOp>(iface).getBodyBlock());
2595 interfaceMap[FlatSymbolRefAttr::get(builder.getContext(),
2596 ifaceBuilder.iFaceName)] = iface;
2597 for (auto elem : ifaceBuilder.elementsList) {
2598
2599 auto uloc = builder.getUnknownLoc();
2600
2601 auto description = elem.description;
2602
2603 if (description) {
2604 auto descriptionOp = builder.create<sv::VerbatimOp>(
2605 uloc, ("// " + cleanupDescription(description.getValue())));
2606
2607 // If we need to generate a YAML representation of this interface,
2608 // then add an attribute indicating that this `sv::VerbatimOp` is
2609 // actually a description.
2610 if (yamlPath)
2611 descriptionOp->setAttr("firrtl.grandcentral.yaml.type",
2612 builder.getStringAttr("description"));
2613 }
2614 if (auto *str = std::get_if<VerbatimType>(&elem.elemType)) {
2615 auto instanceOp = builder.create<sv::VerbatimOp>(
2616 uloc, str->toStr(elem.elemName.getValue()));
2617
2618 // If we need to generate a YAML representation of the interface, then
2619 // add attributes that describe what this `sv::VerbatimOp` is.
2620 if (yamlPath) {
2621 if (str->instantiation)
2622 instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2623 builder.getStringAttr("instance"));
2624 else
2625 instanceOp->setAttr("firrtl.grandcentral.yaml.type",
2626 builder.getStringAttr("unsupported"));
2627 instanceOp->setAttr("firrtl.grandcentral.yaml.name", elem.elemName);
2628 instanceOp->setAttr("firrtl.grandcentral.yaml.dimensions",
2629 builder.getI32ArrayAttr(str->dimensions));
2630 instanceOp->setAttr(
2631 "firrtl.grandcentral.yaml.symbol",
2632 FlatSymbolRefAttr::get(builder.getContext(), str->str));
2633 }
2634 continue;
2635 }
2636
2637 auto tpe = std::get<Type>(elem.elemType);
2638 builder.create<sv::InterfaceSignalOp>(uloc, elem.elemName.getValue(),
2639 tpe);
2640 }
2641 }
2642
2643 ++numViews;
2644
2645 if (yamlPath)
2646 interfaceYAMLMap[yamlPath].push_back(topIface);
2647
2648 // Instantiate the interface before the view and the XMR's we inserted
2649 // above.
2650 viewBuilder.setInsertionPoint(view);
2651 viewBuilder.create<sv::InterfaceInstanceOp>(
2652 topIface.getInterfaceType(), view.getName(),
2653 hw::InnerSymAttr::get(builder.getStringAttr(symbolName)));
2654
2655 view.erase();
2656 }
2657
2658 for (auto &[yamlPath, intfs] : interfaceYAMLMap)
2659 emitHierarchyYamlFile(yamlPath.getValue(), intfs);
2660
2661 // Signal pass failure if any errors were found while examining circuit
2662 // annotations.
2663 if (removalError)
2664 return signalPassFailure();
2665 markAnalysesPreserved<NLATable>();
2666}
2667
2668void GrandCentralPass::emitHierarchyYamlFile(
2669 StringRef yamlPath, SmallVectorImpl<sv::InterfaceOp> &intfs) {
2670 CircuitOp circuitOp = getOperation();
2671
2672 std::string yamlString;
2673 llvm::raw_string_ostream stream(yamlString);
2674 ::yaml::Context yamlContext({interfaceMap});
2675 llvm::yaml::Output yout(stream);
2676 yamlize(yout, intfs, true, yamlContext);
2677
2678 auto builder = OpBuilder::atBlockBegin(circuitOp.getBodyBlock());
2679 builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), yamlString)
2680 ->setAttr("output_file", hw::OutputFileAttr::getFromFilename(
2681 &getContext(), yamlPath,
2682 /*excludeFromFileList=*/true));
2683 LLVM_DEBUG({ llvm::dbgs() << "Generated YAML:" << yamlString << "\n"; });
2684}
2685
2686//===----------------------------------------------------------------------===//
2687// Pass Creation
2688//===----------------------------------------------------------------------===//
2689
2690std::unique_ptr<mlir::Pass>
2692 auto pass = std::make_unique<GrandCentralPass>();
2693 pass->companionMode = companionMode;
2694 return pass;
2695}
assert(baseType &&"element must be base type")
MlirType elementType
Definition CHIRRTL.cpp:29
static std::optional< DictionaryAttr > parseAugmentedType(ApplyState &state, DictionaryAttr augmentedType, DictionaryAttr root, 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...
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.
igraph::ModuleOpInterface getDut()
Return the design-under-test if one is defined for the circuit, otherwise return null.
bool allInstancesUnderEffectiveDut(igraph::ModuleOpInterface op)
Return true if all instances are under (or transitively under) the effective design-under-test.
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.
auto getModule()
Get the module that this node is tracking.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
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
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)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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)