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