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