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