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