CIRCT  18.0.0git
EmitOMIR.cpp
Go to the documentation of this file.
1 //===- EmitOMIR.cpp ---------------------------------------------*- 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 //
9 // This file defines the EmitOMIR pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
25 #include "circt/Dialect/SV/SVOps.h"
26 #include "mlir/IR/ImplicitLocOpBuilder.h"
27 #include "llvm/ADT/TypeSwitch.h"
28 #include "llvm/Support/Debug.h"
29 #include "llvm/Support/JSON.h"
30 #include <functional>
31 
32 #define DEBUG_TYPE "omir"
33 
34 using namespace circt;
35 using namespace firrtl;
36 using mlir::LocationAttr;
37 using mlir::UnitAttr;
38 
39 //===----------------------------------------------------------------------===//
40 // Utilities
41 //===----------------------------------------------------------------------===//
42 
43 namespace {
44 /// Information concerning a tracker in the IR.
45 struct Tracker {
46  /// The unique ID of this tracker.
47  IntegerAttr id;
48  /// The operation onto which this tracker was annotated.
49  Operation *op;
50  /// If this tracker is non-local, this is the corresponding anchor.
51  hw::HierPathOp nla;
52  /// If this is a port, then set the portIdx, else initialized to -1.
53  int portNo = -1;
54  /// If this is a field, the ID will be greater than 0, else it will be 0.
55  unsigned fieldID;
56 
57  // Returns true if the tracker has a non-zero field ID.
58  bool hasFieldID() { return fieldID > 0; }
59 };
60 
61 class EmitOMIRPass : public EmitOMIRBase<EmitOMIRPass> {
62 public:
63  using EmitOMIRBase::outputFilename;
64 
65 private:
66  void runOnOperation() override;
67  void makeTrackerAbsolute(Tracker &tracker);
68 
69  void emitSourceInfo(Location input, SmallString<64> &into);
70  void emitOMNode(Attribute node, llvm::json::OStream &jsonStream);
71  void emitOMField(StringAttr fieldName, DictionaryAttr field,
72  llvm::json::OStream &jsonStream);
73  void emitOptionalRTLPorts(DictionaryAttr node,
74  llvm::json::OStream &jsonStream);
75  void emitValue(Attribute node, llvm::json::OStream &jsonStream,
76  bool dutInstance);
77  void emitTrackedTarget(DictionaryAttr node, llvm::json::OStream &jsonStream,
78  bool dutInstance);
79 
80  SmallString<8> addSymbolImpl(Attribute symbol) {
81  unsigned id;
82  auto it = symbolIndices.find(symbol);
83  if (it != symbolIndices.end()) {
84  id = it->second;
85  } else {
86  id = symbols.size();
87  symbols.push_back(symbol);
88  symbolIndices.insert({symbol, id});
89  }
90  SmallString<8> str;
91  ("{{" + Twine(id) + "}}").toVector(str);
92  return str;
93  }
94  SmallString<8> addSymbol(hw::InnerRefAttr symbol) {
95  return addSymbolImpl(symbol);
96  }
97  SmallString<8> addSymbol(FlatSymbolRefAttr symbol) {
98  return addSymbolImpl(symbol);
99  }
100  SmallString<8> addSymbol(StringAttr symbolName) {
101  return addSymbol(FlatSymbolRefAttr::get(symbolName));
102  }
103  SmallString<8> addSymbol(Operation *op) {
104  return addSymbol(SymbolTable::getSymbolName(op));
105  }
106 
107  /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
108  /// to that operation.
109  hw::InnerRefAttr getInnerRefTo(Operation *op);
110  /// Obtain an inner reference to a module port, possibly adding an `inner_sym`
111  /// to that port.
112  hw::InnerRefAttr getInnerRefTo(FModuleLike module, size_t portIdx);
113 
114  // Obtain the result type of an Operation.
115  FIRRTLType getTypeOf(Operation *op);
116  // Obtain the type of a module port.
117  FIRRTLType getTypeOf(FModuleLike mod, size_t portIdx);
118 
119  // Constructs a reference to a field from a FIRRTLType with a fieldID.
120  void addFieldID(FIRRTLType type, unsigned fieldID,
121  SmallVectorImpl<char> &result);
122 
123  /// Get the cached namespace for a module.
124  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
125  return moduleNamespaces.try_emplace(module, module).first->second;
126  }
127 
128  /// Whether any errors have occurred in the current `runOnOperation`.
129  bool anyFailures;
130  CircuitNamespace *circuitNamespace;
131  InstanceGraph *instanceGraph;
132  InstancePathCache *instancePaths;
133  /// OMIR target trackers gathered in the current operation, by tracker ID.
134  DenseMap<Attribute, Tracker> trackers;
135  /// The list of symbols to be interpolated in the verbatim JSON. This gets
136  /// populated as the JSON is constructed and module and instance names are
137  /// collected.
138  SmallVector<Attribute> symbols;
140  /// Temporary `firrtl.hierpath` operations to be deleted at the end of the
141  /// pass. Vector elements are unique.
142  SmallVector<hw::HierPathOp> removeTempNLAs;
143  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
144  /// Lookup table of instances by name and parent module.
145  DenseMap<hw::InnerRefAttr, InstanceOp> instancesByName;
146  /// Record to remove any temporary symbols added to instances.
147  DenseSet<Operation *> tempSymInstances;
148  /// The Design Under Test module.
149  StringAttr dutModuleName;
150 
151  /// Cached NLA table analysis.
152  NLATable *nlaTable;
153 };
154 } // namespace
155 
156 /// Check if an `OMNode` is an `OMSRAM` and requires special treatment of its
157 /// instance path field. This returns the ID of the tracker stored in the
158 /// `instancePath` or `finalPath` field if the node has an array field `omType`
159 /// that contains a `OMString:OMSRAM` entry.
160 static IntegerAttr isOMSRAM(Attribute &node) {
161  auto dict = dyn_cast<DictionaryAttr>(node);
162  if (!dict)
163  return {};
164  auto idAttr = dict.getAs<StringAttr>("id");
165  if (!idAttr)
166  return {};
167  IntegerAttr id;
168  if (auto infoAttr = dict.getAs<DictionaryAttr>("fields")) {
169  auto finalPath = infoAttr.getAs<DictionaryAttr>("finalPath");
170  // The following is used prior to an upstream bump in Chisel.
171  if (!finalPath)
172  finalPath = infoAttr.getAs<DictionaryAttr>("instancePath");
173  if (finalPath)
174  if (auto v = finalPath.getAs<DictionaryAttr>("value"))
175  if (v.getAs<UnitAttr>("omir.tracker"))
176  id = v.getAs<IntegerAttr>("id");
177  if (auto omTy = infoAttr.getAs<DictionaryAttr>("omType"))
178  if (auto valueArr = omTy.getAs<ArrayAttr>("value"))
179  for (auto attr : valueArr)
180  if (auto str = dyn_cast<StringAttr>(attr))
181  if (str.getValue().equals("OMString:OMSRAM"))
182  return id;
183  }
184  return {};
185 }
186 
187 //===----------------------------------------------------------------------===//
188 // Code related to handling OMIR annotations
189 //===----------------------------------------------------------------------===//
190 
191 /// Recursively walk Object Model IR and convert FIRRTL targets to identifiers
192 /// while scattering trackers into the newAnnotations argument.
193 ///
194 /// Object Model IR consists of a type hierarchy built around recursive arrays
195 /// and dictionaries whose leaves are "string-encoded types". This is an Object
196 /// Model-specific construct that puts type information alongside a value.
197 /// Concretely, these look like:
198 ///
199 /// 'OM' type ':' value
200 ///
201 /// This function is only concerned with unpacking types whose values are FIRRTL
202 /// targets. This is because these need to be kept up-to-date with
203 /// modifications made to the circuit whereas other types are just passing
204 /// through CIRCT.
205 ///
206 /// At a later time this understanding may be expanded or Object Model IR may
207 /// become its own Dialect. At this time, this function is trying to do as
208 /// minimal work as possible to just validate that the OMIR looks okay without
209 /// doing lots of unnecessary unpacking/repacking of string-encoded types.
210 static std::optional<Attribute> scatterOMIR(Attribute original,
211  ApplyState &state) {
212  auto *ctx = original.getContext();
213 
214  // Convert a string-encoded type to a dictionary that includes the type
215  // information and an identifier derived from the current annotationID. Then
216  // increment the annotationID. Return the constructed dictionary.
217  auto addID = [&](StringRef tpe, StringRef path,
218  IntegerAttr id) -> DictionaryAttr {
219  NamedAttrList fields;
220  fields.append("id", id);
221  fields.append("omir.tracker", UnitAttr::get(ctx));
222  fields.append("path", StringAttr::get(ctx, path));
223  fields.append("type", StringAttr::get(ctx, tpe));
224  return DictionaryAttr::getWithSorted(ctx, fields);
225  };
226 
227  return TypeSwitch<Attribute, std::optional<Attribute>>(original)
228  // Most strings in the Object Model are actually string-encoded types.
229  // These are types which look like: "<type>:<value>". This code will
230  // examine all strings, parse them into type and value, and then either
231  // store them in their unpacked state (and possibly scatter trackers into
232  // the circuit), store them in their packed state (because CIRCT is not
233  // expected to care about them right now), or error if we see them
234  // (because they should not exist and are expected to serialize to a
235  // different format).
236  .Case<StringAttr>([&](StringAttr str) -> std::optional<Attribute> {
237  // Unpack the string into type and value.
238  StringRef tpe, value;
239  std::tie(tpe, value) = str.getValue().split(":");
240 
241  // These are string-encoded types that are targets in the circuit.
242  // These require annotations to be scattered for them. Replace their
243  // target with an ID and scatter a tracker.
244  if (tpe == "OMReferenceTarget" || tpe == "OMMemberReferenceTarget" ||
245  tpe == "OMMemberInstanceTarget" || tpe == "OMInstanceTarget" ||
246  tpe == "OMDontTouchedReferenceTarget") {
247  auto idAttr = state.newID();
248  NamedAttrList tracker;
249  tracker.append("class", StringAttr::get(ctx, omirTrackerAnnoClass));
250  tracker.append("id", idAttr);
251  tracker.append("target", StringAttr::get(ctx, value));
252  tracker.append("type", StringAttr::get(ctx, tpe));
253 
254  state.addToWorklistFn(DictionaryAttr::get(ctx, tracker));
255 
256  return addID(tpe, value, idAttr);
257  }
258 
259  // The following are types that may exist, but we do not unbox them. At
260  // a later time, we may want to change this behavior and unbox these if
261  // we wind up building out an Object Model dialect:
263  return str;
264 
265  // The following types are not expected to exist because they have
266  // serializations to JSON types or are removed during serialization.
267  // Hence, any of the following types are NOT expected to exist and we
268  // error if we see them. These are explicitly specified as opposed to
269  // being handled in the "unknown" catch-all case below because we want
270  // to provide a good error message that a user may be doing something
271  // very weird.
272  if (tpe == "OMMap" || tpe == "OMArray" || tpe == "OMBoolean" ||
273  tpe == "OMInt" || tpe == "OMDouble" || tpe == "OMFrozenTarget") {
274  auto diag =
275  mlir::emitError(state.circuit.getLoc())
276  << "found known string-encoded OMIR type \"" << tpe
277  << "\", but this type should not be seen as it has a defined "
278  "serialization format that does NOT use a string-encoded type";
279  diag.attachNote()
280  << "the problematic OMIR is reproduced here: " << original;
281  return std::nullopt;
282  }
283 
284  // This is a catch-all for any unknown types.
285  auto diag = mlir::emitError(state.circuit.getLoc())
286  << "found unknown string-encoded OMIR type \"" << tpe
287  << "\" (Did you misspell it? Is CIRCT missing an Object "
288  "Model OMIR type?)";
289  diag.attachNote() << "the problematic OMIR is reproduced here: "
290  << original;
291  return std::nullopt;
292  })
293  // For an array, just recurse into each element and rewrite the array with
294  // the results.
295  .Case<ArrayAttr>([&](ArrayAttr arr) -> std::optional<Attribute> {
296  SmallVector<Attribute> newArr;
297  for (auto element : arr) {
298  auto newElement = scatterOMIR(element, state);
299  if (!newElement)
300  return std::nullopt;
301  newArr.push_back(*newElement);
302  }
303  return ArrayAttr::get(ctx, newArr);
304  })
305  // For a dictionary, recurse into each value and rewrite the key/value
306  // pairs.
307  .Case<DictionaryAttr>(
308  [&](DictionaryAttr dict) -> std::optional<Attribute> {
309  NamedAttrList newAttrs;
310  for (auto pairs : dict) {
311  auto maybeValue = scatterOMIR(pairs.getValue(), state);
312  if (!maybeValue)
313  return std::nullopt;
314  newAttrs.append(pairs.getName(), *maybeValue);
315  }
316  return DictionaryAttr::get(ctx, newAttrs);
317  })
318  // These attributes are all expected. They are OMIR types, but do not
319  // have string-encodings (hence why these should error if we see them as
320  // strings).
321  .Case</* OMBoolean */ BoolAttr, /* OMDouble */ FloatAttr,
322  /* OMInt */ IntegerAttr>(
323  [](auto passThrough) { return passThrough; })
324  // Error if we see anything else.
325  .Default([&](auto) -> std::optional<Attribute> {
326  auto diag = mlir::emitError(state.circuit.getLoc())
327  << "found unexpected MLIR attribute \"" << original
328  << "\" while trying to scatter OMIR";
329  return std::nullopt;
330  });
331 }
332 
333 /// Convert an Object Model Field into an optional pair of a string key and a
334 /// dictionary attribute. Expand internal source locator strings to location
335 /// attributes. Scatter any FIRRTL targets into the circuit. If this is an
336 /// illegal Object Model Field return None.
337 ///
338 /// Each Object Model Field consists of three mandatory members with
339 /// the following names and types:
340 ///
341 /// - "info": Source Locator String
342 /// - "name": String
343 /// - "value": Object Model IR
344 ///
345 /// The key is the "name" and the dictionary consists of the "info" and "value"
346 /// members. Each value is recursively traversed to scatter any FIRRTL targets
347 /// that may be used inside it.
348 ///
349 /// This conversion from an object (dictionary) to key--value pair is safe
350 /// because each Object Model Field in an Object Model Node must have a unique
351 /// "name". Anything else is illegal Object Model.
352 static std::optional<std::pair<StringRef, DictionaryAttr>>
353 scatterOMField(Attribute original, const Attribute root, unsigned index,
354  ApplyState &state) {
355  // The input attribute must be a dictionary.
356  DictionaryAttr dict = dyn_cast<DictionaryAttr>(original);
357  if (!dict) {
358  llvm::errs() << "OMField is not a dictionary, but should be: " << original
359  << "\n";
360  return std::nullopt;
361  }
362 
363  auto loc = state.circuit.getLoc();
364  auto *ctx = state.circuit.getContext();
365 
366  // Generate an arbitrary identifier to use for caching when using
367  // `maybeStringToLocation`.
368  StringAttr locatorFilenameCache = StringAttr::get(ctx, ".");
369  FileLineColLoc fileLineColLocCache;
370 
371  // Convert location from a string to a location attribute.
372  auto infoAttr = tryGetAs<StringAttr>(dict, root, "info", loc, omirAnnoClass);
373  if (!infoAttr)
374  return std::nullopt;
375  auto maybeLoc =
376  maybeStringToLocation(infoAttr.getValue(), false, locatorFilenameCache,
377  fileLineColLocCache, ctx);
378  mlir::LocationAttr infoLoc;
379  if (maybeLoc.first)
380  infoLoc = *maybeLoc.second;
381  else
382  infoLoc = UnknownLoc::get(ctx);
383 
384  // Extract the name attribute.
385  auto nameAttr = tryGetAs<StringAttr>(dict, root, "name", loc, omirAnnoClass);
386  if (!nameAttr)
387  return std::nullopt;
388 
389  // The value attribute is unstructured and just copied over.
390  auto valueAttr = tryGetAs<Attribute>(dict, root, "value", loc, omirAnnoClass);
391  if (!valueAttr)
392  return std::nullopt;
393  auto newValue = scatterOMIR(valueAttr, state);
394  if (!newValue)
395  return std::nullopt;
396 
397  NamedAttrList values;
398  // We add the index if one was provided. This can be used later to
399  // reconstruct the order of the original array.
400  values.append("index", IntegerAttr::get(IntegerType::get(ctx, 64), index));
401  values.append("info", infoLoc);
402  values.append("value", *newValue);
403 
404  return {{nameAttr.getValue(), DictionaryAttr::getWithSorted(ctx, values)}};
405 }
406 
407 /// Convert an Object Model Node to an optional dictionary, convert source
408 /// locator strings to location attributes, and scatter FIRRTL targets into the
409 /// circuit. If this is an illegal Object Model Node, then return None.
410 ///
411 /// An Object Model Node is expected to look like:
412 ///
413 /// - "info": Source Locator String
414 /// - "id": String-encoded integer ('OMID' ':' Integer)
415 /// - "fields": Array<Object>
416 ///
417 /// The "fields" member may be absent. If so, then construct an empty array.
418 static std::optional<DictionaryAttr>
419 scatterOMNode(Attribute original, const Attribute root, ApplyState &state) {
420 
421  auto loc = state.circuit.getLoc();
422 
423  /// The input attribute must be a dictionary.
424  DictionaryAttr dict = dyn_cast<DictionaryAttr>(original);
425  if (!dict) {
426  llvm::errs() << "OMNode is not a dictionary, but should be: " << original
427  << "\n";
428  return std::nullopt;
429  }
430 
431  NamedAttrList omnode;
432  auto *ctx = state.circuit.getContext();
433 
434  // Generate an arbitrary identifier to use for caching when using
435  // `maybeStringToLocation`.
436  StringAttr locatorFilenameCache = StringAttr::get(ctx, ".");
437  FileLineColLoc fileLineColLocCache;
438 
439  // Convert the location from a string to a location attribute.
440  auto infoAttr = tryGetAs<StringAttr>(dict, root, "info", loc, omirAnnoClass);
441  if (!infoAttr)
442  return std::nullopt;
443  auto maybeLoc =
444  maybeStringToLocation(infoAttr.getValue(), false, locatorFilenameCache,
445  fileLineColLocCache, ctx);
446  mlir::LocationAttr infoLoc;
447  if (maybeLoc.first)
448  infoLoc = *maybeLoc.second;
449  else
450  infoLoc = UnknownLoc::get(ctx);
451 
452  // Extract the OMID. Don't parse this, just leave it as a string.
453  auto idAttr = tryGetAs<StringAttr>(dict, root, "id", loc, omirAnnoClass);
454  if (!idAttr)
455  return std::nullopt;
456 
457  // Convert the fields from an ArrayAttr to a DictionaryAttr keyed by their
458  // "name". If no fields member exists, then just create an empty dictionary.
459  // Note that this is safe to construct because all fields must have unique
460  // "name" members relative to each other.
461  auto maybeFields = dict.getAs<ArrayAttr>("fields");
462  DictionaryAttr fields;
463  if (!maybeFields)
464  fields = DictionaryAttr::get(ctx);
465  else {
466  auto fieldAttr = maybeFields.getValue();
467  NamedAttrList fieldAttrs;
468  for (size_t i = 0, e = fieldAttr.size(); i != e; ++i) {
469  auto field = fieldAttr[i];
470  if (auto newField = scatterOMField(field, root, i, state)) {
471  fieldAttrs.append(newField->first, newField->second);
472  continue;
473  }
474  return std::nullopt;
475  }
476  fields = DictionaryAttr::get(ctx, fieldAttrs);
477  }
478 
479  omnode.append("fields", fields);
480  omnode.append("id", idAttr);
481  omnode.append("info", infoLoc);
482 
483  return DictionaryAttr::getWithSorted(ctx, omnode);
484 }
485 
486 /// Main entry point to handle scattering of an OMIRAnnotation. Return the
487 /// modified optional attribute on success and None on failure. Any scattered
488 /// annotations will be added to the reference argument `newAnnotations`.
489 LogicalResult circt::firrtl::applyOMIR(const AnnoPathValue &target,
490  DictionaryAttr anno, ApplyState &state) {
491 
492  auto loc = state.circuit.getLoc();
493 
494  auto nodes = tryGetAs<ArrayAttr>(anno, anno, "nodes", loc, omirAnnoClass);
495  if (!nodes)
496  return failure();
497 
498  SmallVector<Attribute> newNodes;
499  for (auto node : nodes) {
500  auto newNode = scatterOMNode(node, anno, state);
501  if (!newNode)
502  return failure();
503  newNodes.push_back(*newNode);
504  }
505 
506  auto *ctx = state.circuit.getContext();
507 
508  NamedAttrList newAnnotation;
509  newAnnotation.append("class", StringAttr::get(ctx, omirAnnoClass));
510  newAnnotation.append("nodes", ArrayAttr::get(ctx, newNodes));
511 
512  AnnotationSet annotations(state.circuit);
513  annotations.addAnnotations(DictionaryAttr::get(ctx, newAnnotation));
514  annotations.applyToOperation(state.circuit);
515 
516  return success();
517 }
518 
519 //===----------------------------------------------------------------------===//
520 // Pass Implementation
521 //===----------------------------------------------------------------------===//
522 
523 void EmitOMIRPass::runOnOperation() {
524  MLIRContext *context = &getContext();
525  anyFailures = false;
526  circuitNamespace = nullptr;
527  instanceGraph = nullptr;
528  instancePaths = nullptr;
529  trackers.clear();
530  symbols.clear();
531  symbolIndices.clear();
532  removeTempNLAs.clear();
533  moduleNamespaces.clear();
534  instancesByName.clear();
535  CircuitOp circuitOp = getOperation();
536 
537  // Gather the relevant annotations from the circuit. On the one hand these are
538  // all the actual `OMIRAnnotation`s that need processing and emission, as well
539  // as an optional `OMIRFileAnnotation` that overrides the default OMIR output
540  // file. Also while we're at it, keep track of all OMIR nodes that qualify as
541  // an SRAM and that require their trackers to be turned into NLAs starting at
542  // the root of the hierarchy.
543  SmallVector<ArrayRef<Attribute>> annoNodes;
544  DenseSet<Attribute> sramIDs;
545  std::optional<StringRef> outputFilename;
546 
547  AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
548  if (anno.isClass(omirFileAnnoClass)) {
549  auto pathAttr = anno.getMember<StringAttr>("filename");
550  if (!pathAttr) {
551  circuitOp.emitError(omirFileAnnoClass)
552  << " annotation missing `filename` string attribute";
553  anyFailures = true;
554  return true;
555  }
556  LLVM_DEBUG(llvm::dbgs() << "- OMIR path: " << pathAttr << "\n");
557  outputFilename = pathAttr.getValue();
558  return true;
559  }
560  if (anno.isClass(omirAnnoClass)) {
561  auto nodesAttr = anno.getMember<ArrayAttr>("nodes");
562  if (!nodesAttr) {
563  circuitOp.emitError(omirAnnoClass)
564  << " annotation missing `nodes` array attribute";
565  anyFailures = true;
566  return true;
567  }
568  LLVM_DEBUG(llvm::dbgs() << "- OMIR: " << nodesAttr << "\n");
569  annoNodes.push_back(nodesAttr.getValue());
570  for (auto node : nodesAttr) {
571  if (auto id = isOMSRAM(node)) {
572  LLVM_DEBUG(llvm::dbgs() << " - is SRAM with tracker " << id << "\n");
573  sramIDs.insert(id);
574  }
575  }
576  return true;
577  }
578  return false;
579  });
580  if (anyFailures)
581  return signalPassFailure();
582 
583  // If an OMIR output filename has been specified as a pass parameter, override
584  // whatever the annotations have configured. If neither are specified we just
585  // bail.
586  if (!this->outputFilename.empty())
587  outputFilename = this->outputFilename;
588  if (!outputFilename) {
589  LLVM_DEBUG(llvm::dbgs() << "Not emitting OMIR because no annotation or "
590  "pass parameter specified an output file\n");
591  markAllAnalysesPreserved();
592  return;
593  }
594  // Establish some of the analyses we need throughout the pass.
595  CircuitNamespace currentCircuitNamespace(circuitOp);
596  InstanceGraph &currentInstanceGraph = getAnalysis<InstanceGraph>();
597  nlaTable = &getAnalysis<NLATable>();
598  InstancePathCache currentInstancePaths(currentInstanceGraph);
599  circuitNamespace = &currentCircuitNamespace;
600  instanceGraph = &currentInstanceGraph;
601  instancePaths = &currentInstancePaths;
602  dutModuleName = {};
603 
604  // Traverse the IR and collect all tracker annotations that were previously
605  // scattered into the circuit.
606  circuitOp.walk([&](Operation *op) {
607  if (auto instOp = dyn_cast<InstanceOp>(op)) {
608  // This instance does not have a symbol, but we are adding one. Remove it
609  // after the pass.
611  tempSymInstances.insert(instOp);
612 
613  instancesByName.insert({getInnerRefTo(op), instOp});
614  }
615  auto setTracker = [&](int portNo, Annotation anno) {
616  if (!anno.isClass(omirTrackerAnnoClass))
617  return false;
618  Tracker tracker;
619  tracker.op = op;
620  tracker.id = anno.getMember<IntegerAttr>("id");
621  tracker.portNo = portNo;
622  tracker.fieldID = anno.getFieldID();
623  if (!tracker.id) {
624  op->emitError(omirTrackerAnnoClass)
625  << " annotation missing `id` integer attribute";
626  anyFailures = true;
627  return true;
628  }
629  if (auto nlaSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
630  auto tmp = nlaTable->getNLA(nlaSym.getAttr());
631  if (!tmp) {
632  op->emitError("missing annotation ") << nlaSym.getValue();
633  anyFailures = true;
634  return true;
635  }
636  tracker.nla = cast<hw::HierPathOp>(tmp);
637  }
638  if (sramIDs.erase(tracker.id))
639  makeTrackerAbsolute(tracker);
640  if (auto [it, inserted] = trackers.try_emplace(tracker.id, tracker);
641  !inserted) {
642  auto diag = op->emitError(omirTrackerAnnoClass)
643  << " annotation with same ID already found, must resolve "
644  "to single target";
645  diag.attachNote(it->second.op->getLoc())
646  << "tracker with same ID already found here";
647  anyFailures = true;
648  return true;
649  }
650  return true;
651  };
652  AnnotationSet::removePortAnnotations(op, setTracker);
653  AnnotationSet::removeAnnotations(
654  op, std::bind(setTracker, -1, std::placeholders::_1));
655  if (auto modOp = dyn_cast<FModuleOp>(op)) {
656  AnnotationSet annos(modOp.getAnnotations());
657  if (!annos.hasAnnotation(dutAnnoClass))
658  return;
659  dutModuleName = modOp.getNameAttr();
660  }
661  });
662 
663  // Build the output JSON.
664  std::string jsonBuffer;
665  llvm::raw_string_ostream jsonOs(jsonBuffer);
666  llvm::json::OStream json(jsonOs, 2);
667  json.array([&] {
668  for (auto nodes : annoNodes) {
669  for (auto node : nodes) {
670  emitOMNode(node, json);
671  if (anyFailures)
672  return;
673  }
674  }
675  });
676  if (anyFailures)
677  return signalPassFailure();
678 
679  // Drop temporary (and sometimes invalid) NLA's created during the pass:
680  for (auto nla : removeTempNLAs) {
681  LLVM_DEBUG(llvm::dbgs() << "Removing '" << nla << "'\n");
682  nlaTable->erase(nla);
683  nla.erase();
684  }
685  removeTempNLAs.clear();
686 
687  // Remove the temp symbol from instances.
688  for (auto *op : tempSymInstances)
689  cast<InstanceOp>(op).setInnerSymbolAttr({});
690  tempSymInstances.clear();
691 
692  // Emit the OMIR JSON as a verbatim op.
693  auto builder = circuitOp.getBodyBuilder();
694  auto verbatimOp =
695  builder.create<sv::VerbatimOp>(builder.getUnknownLoc(), jsonBuffer);
696  auto fileAttr = hw::OutputFileAttr::getFromFilename(
697  context, *outputFilename, /*excludeFromFilelist=*/true, false);
698  verbatimOp->setAttr("output_file", fileAttr);
699  verbatimOp.setSymbolsAttr(ArrayAttr::get(context, symbols));
700 
701  markAnalysesPreserved<NLATable>();
702 }
703 
704 /// Make a tracker absolute by adding an NLA to it which starts at the root
705 /// module of the circuit. Generates an error if any module along the path is
706 /// instantiated multiple times.
707 void EmitOMIRPass::makeTrackerAbsolute(Tracker &tracker) {
708  auto builder = OpBuilder::atBlockBegin(getOperation().getBodyBlock());
709 
710  // Pick a name for the NLA that doesn't collide with anything.
711  StringAttr opName;
712  if (auto module = dyn_cast<FModuleLike>(tracker.op))
713  opName = module.getModuleNameAttr();
714  else
715  opName = tracker.op->getAttrOfType<StringAttr>("name");
716  auto nlaName = circuitNamespace->newName("omir_nla_" + opName.getValue());
717 
718  // Get all the paths instantiating this module. If there is an NLA already
719  // attached to this tracker, we use it as a base to disambiguate the path to
720  // the memory.
721  igraph::ModuleOpInterface mod;
722  if (tracker.nla)
723  mod = instanceGraph->lookup(tracker.nla.root())
724  ->getModule<igraph::ModuleOpInterface>();
725  else
726  mod = tracker.op->getParentOfType<FModuleOp>();
727 
728  // Get all the paths instantiating this module.
729  auto paths = instancePaths->getAbsolutePaths(mod);
730  if (paths.empty()) {
731  tracker.op->emitError("OMIR node targets uninstantiated component `")
732  << opName.getValue() << "`";
733  anyFailures = true;
734  return;
735  }
736  if (paths.size() > 1) {
737  auto diag = tracker.op->emitError("OMIR node targets ambiguous component `")
738  << opName.getValue() << "`";
739  diag.attachNote(tracker.op->getLoc())
740  << "may refer to the following paths:";
741  for (auto path : paths)
742  path.print(diag.attachNote(tracker.op->getLoc()) << "- ");
743  anyFailures = true;
744  return;
745  }
746 
747  // Assemble the module and name path for the NLA. Also attach an NLA reference
748  // annotation to each instance participating in the path.
749  SmallVector<Attribute> namepath;
750  auto addToPath = [&](Operation *op) {
751  namepath.push_back(getInnerRefTo(op));
752  };
753  // Add the path up to where the NLA starts.
754  for (auto inst : paths[0])
755  addToPath(inst);
756  // Add the path from the NLA to the op.
757  if (tracker.nla) {
758  auto path = tracker.nla.getNamepath().getValue();
759  for (auto attr : path.drop_back()) {
760  auto ref = attr.cast<hw::InnerRefAttr>();
761  // Find the instance referenced by the NLA.
762  auto *node = instanceGraph->lookup(ref.getModule());
763  auto it = llvm::find_if(*node, [&](igraph::InstanceRecord *record) {
764  return getInnerSymName(record->getInstance<InstanceOp>()) ==
765  ref.getName();
766  });
767  assert(it != node->end() &&
768  "Instance referenced by NLA does not exist in module");
769  addToPath((*it)->getInstance());
770  }
771  }
772 
773  // TODO: Don't create NLA if namepath is empty
774  // (care needed to ensure this will be handled correctly elsewhere)
775 
776  // Add the op itself.
777  if (auto module = dyn_cast<FModuleLike>(tracker.op))
778  namepath.push_back(FlatSymbolRefAttr::get(module.getModuleNameAttr()));
779  else
780  namepath.push_back(getInnerRefTo(tracker.op));
781 
782  // Add the NLA to the tracker and mark it to be deleted later.
783  tracker.nla = builder.create<hw::HierPathOp>(builder.getUnknownLoc(),
784  builder.getStringAttr(nlaName),
785  builder.getArrayAttr(namepath));
786  nlaTable->addNLA(tracker.nla);
787 
788  removeTempNLAs.push_back(tracker.nla);
789 }
790 
791 /// Emit a source locator into a string, for inclusion in the `info` field of
792 /// `OMNode` and `OMField`.
793 void EmitOMIRPass::emitSourceInfo(Location input, SmallString<64> &into) {
794  into.clear();
795  input->walk([&](Location loc) {
796  if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
797  into.append(into.empty() ? "@[" : " ");
798  (Twine(fileLoc.getFilename()) + " " + Twine(fileLoc.getLine()) + ":" +
799  Twine(fileLoc.getColumn()))
800  .toVector(into);
801  }
802  return WalkResult::advance();
803  });
804  if (!into.empty())
805  into.append("]");
806  else
807  into.append("UnlocatableSourceInfo");
808 }
809 
810 /// Emit an entire `OMNode` as JSON.
811 void EmitOMIRPass::emitOMNode(Attribute node, llvm::json::OStream &jsonStream) {
812  auto dict = dyn_cast<DictionaryAttr>(node);
813  if (!dict) {
814  getOperation()
815  .emitError("OMNode must be a dictionary")
816  .attachNote(getOperation().getLoc())
817  << node;
818  anyFailures = true;
819  return;
820  }
821 
822  // Extract the `info` field and serialize the location.
823  SmallString<64> info;
824  if (auto infoAttr = dict.getAs<LocationAttr>("info"))
825  emitSourceInfo(infoAttr, info);
826  if (anyFailures)
827  return;
828 
829  // Extract the `id` field.
830  auto idAttr = dict.getAs<StringAttr>("id");
831  if (!idAttr) {
832  getOperation()
833  .emitError("OMNode missing `id` string field")
834  .attachNote(getOperation().getLoc())
835  << dict;
836  anyFailures = true;
837  return;
838  }
839 
840  // Extract and order the fields of this node.
841  SmallVector<std::tuple<unsigned, StringAttr, DictionaryAttr>> orderedFields;
842  auto fieldsDict = dict.getAs<DictionaryAttr>("fields");
843  if (fieldsDict) {
844  for (auto nameAndField : fieldsDict.getValue()) {
845  auto fieldDict = dyn_cast<DictionaryAttr>(nameAndField.getValue());
846  if (!fieldDict) {
847  getOperation()
848  .emitError("OMField must be a dictionary")
849  .attachNote(getOperation().getLoc())
850  << nameAndField.getValue();
851  anyFailures = true;
852  return;
853  }
854 
855  unsigned index = 0;
856  if (auto indexAttr = fieldDict.getAs<IntegerAttr>("index"))
857  index = indexAttr.getValue().getLimitedValue();
858 
859  orderedFields.push_back({index, nameAndField.getName(), fieldDict});
860  }
861  llvm::sort(orderedFields,
862  [](auto a, auto b) { return std::get<0>(a) < std::get<0>(b); });
863  }
864 
865  jsonStream.object([&] {
866  jsonStream.attribute("info", info);
867  jsonStream.attribute("id", idAttr.getValue());
868  jsonStream.attributeArray("fields", [&] {
869  for (auto &orderedField : orderedFields) {
870  emitOMField(std::get<1>(orderedField), std::get<2>(orderedField),
871  jsonStream);
872  if (anyFailures)
873  return;
874  }
875  if (auto node = fieldsDict.getAs<DictionaryAttr>("containingModule"))
876  if (auto value = node.getAs<DictionaryAttr>("value"))
877  emitOptionalRTLPorts(value, jsonStream);
878  });
879  });
880 }
881 
882 /// Emit a single `OMField` as JSON. This expects the field's name to be
883 /// provided from the outside, for example as the field name that this attribute
884 /// has in the surrounding dictionary.
885 void EmitOMIRPass::emitOMField(StringAttr fieldName, DictionaryAttr field,
886  llvm::json::OStream &jsonStream) {
887  // Extract the `info` field and serialize the location.
888  auto infoAttr = field.getAs<LocationAttr>("info");
889  SmallString<64> info;
890  if (infoAttr)
891  emitSourceInfo(infoAttr, info);
892  if (anyFailures)
893  return;
894 
895  jsonStream.object([&] {
896  jsonStream.attribute("info", info);
897  jsonStream.attribute("name", fieldName.strref());
898  jsonStream.attributeBegin("value");
899  emitValue(field.get("value"), jsonStream,
900  fieldName.strref().equals("dutInstance"));
901  jsonStream.attributeEnd();
902  });
903 }
904 
905 // If the given `node` refers to a valid tracker in the IR, gather the
906 // additional port metadata of the module it refers to. Then emit this port
907 // metadata as a `ports` array field for the surrounding `OMNode`.
908 void EmitOMIRPass::emitOptionalRTLPorts(DictionaryAttr node,
909  llvm::json::OStream &jsonStream) {
910  // First make sure we actually have a valid tracker. If not, just silently
911  // abort and don't emit any port metadata.
912  auto idAttr = node.getAs<IntegerAttr>("id");
913  auto trackerIt = trackers.find(idAttr);
914  if (!idAttr || !node.getAs<UnitAttr>("omir.tracker") ||
915  trackerIt == trackers.end())
916  return;
917  auto tracker = trackerIt->second;
918 
919  // Lookup the module the tracker refers to. If it points at something *within*
920  // a module, go dig up the surrounding module. This is roughly what
921  // `Target.referringModule(...)` does on the Scala side.
922  auto module = dyn_cast<FModuleLike>(tracker.op);
923  if (!module)
924  module = tracker.op->getParentOfType<FModuleLike>();
925  if (!module) {
926  LLVM_DEBUG(llvm::dbgs() << "Not emitting RTL ports since tracked operation "
927  "does not have a FModuleLike parent: "
928  << *tracker.op << "\n");
929  return;
930  }
931  LLVM_DEBUG(llvm::dbgs() << "Emitting RTL ports for module `"
932  << module.getModuleName() << "`\n");
933 
934  // Emit the JSON.
935  SmallString<64> buf;
936  jsonStream.object([&] {
937  buf.clear();
938  emitSourceInfo(module.getLoc(), buf);
939  jsonStream.attribute("info", buf);
940  jsonStream.attribute("name", "ports");
941  jsonStream.attributeArray("value", [&] {
942  for (const auto &port : llvm::enumerate(module.getPorts())) {
943  auto portType = type_dyn_cast<FIRRTLBaseType>(port.value().type);
944  if (!portType || portType.getBitWidthOrSentinel() == 0)
945  continue;
946  jsonStream.object([&] {
947  // Emit the `ref` field.
948  buf.assign("OMDontTouchedReferenceTarget:~");
949  if (module.getModuleNameAttr() == dutModuleName) {
950  // If module is DUT, then root the target relative to the DUT.
951  buf.append(module.getModuleName());
952  } else {
953  buf.append(getOperation().getName());
954  }
955  buf.push_back('|');
956  buf.append(addSymbol(module));
957  buf.push_back('>');
958  buf.append(addSymbol(getInnerRefTo(module, port.index())));
959  jsonStream.attribute("ref", buf);
960 
961  // Emit the `direction` field.
962  buf.assign("OMString:");
963  buf.append(port.value().isOutput() ? "Output" : "Input");
964  jsonStream.attribute("direction", buf);
965 
966  // Emit the `width` field.
967  buf.assign("OMBigInt:");
968  Twine::utohexstr(portType.getBitWidthOrSentinel()).toVector(buf);
969  jsonStream.attribute("width", buf);
970  });
971  }
972  });
973  });
974 }
975 
976 void EmitOMIRPass::emitValue(Attribute node, llvm::json::OStream &jsonStream,
977  bool dutInstance) {
978  // Handle the null case.
979  if (!node || isa<UnitAttr>(node))
980  return jsonStream.value(nullptr);
981 
982  // Handle the trivial cases where the OMIR serialization simply uses the
983  // builtin JSON types.
984  if (auto attr = dyn_cast<BoolAttr>(node))
985  return jsonStream.value(attr.getValue()); // OMBoolean
986  if (auto attr = dyn_cast<IntegerAttr>(node)) {
987  // CAVEAT: We expect these integers to come from an OMIR file that is
988  // initially read in from JSON, where they are i32 or i64, so this should
989  // yield a valid value. However, a user could cook up an arbitrary precision
990  // integer attr in MLIR input and then subtly break the JSON spec.
991  SmallString<16> val;
992  attr.getValue().toStringSigned(val);
993  return jsonStream.rawValue(val); // OMInt
994  }
995  if (auto attr = dyn_cast<FloatAttr>(node)) {
996  // CAVEAT: We expect these floats to come from an OMIR file that is
997  // initially read in from JSON, where they are f32 or f64, so this should
998  // yield a valid value. However, a user could cook up an arbitrary precision
999  // float attr in MLIR input and then subtly break the JSON spec.
1000  SmallString<16> val;
1001  attr.getValue().toString(val);
1002  return jsonStream.rawValue(val); // OMDouble
1003  }
1004 
1005  // Handle aggregate types.
1006  if (auto attr = dyn_cast<ArrayAttr>(node)) {
1007  jsonStream.array([&] {
1008  for (auto element : attr.getValue()) {
1009  emitValue(element, jsonStream, dutInstance);
1010  if (anyFailures)
1011  return;
1012  }
1013  });
1014  return;
1015  }
1016  if (auto attr = dyn_cast<DictionaryAttr>(node)) {
1017  // Handle targets that have a corresponding tracker annotation in the IR.
1018  if (attr.getAs<UnitAttr>("omir.tracker"))
1019  return emitTrackedTarget(attr, jsonStream, dutInstance);
1020 
1021  // Handle regular dictionaries.
1022  jsonStream.object([&] {
1023  for (auto field : attr.getValue()) {
1024  jsonStream.attributeBegin(field.getName());
1025  emitValue(field.getValue(), jsonStream, dutInstance);
1026  jsonStream.attributeEnd();
1027  if (anyFailures)
1028  return;
1029  }
1030  });
1031  return;
1032  }
1033 
1034  // The remaining types are all simple string-encoded pass-through cases.
1035  if (auto attr = dyn_cast<StringAttr>(node)) {
1036  StringRef val = attr.getValue();
1037  if (isOMIRStringEncodedPassthrough(val.split(":").first))
1038  return jsonStream.value(val);
1039  }
1040 
1041  // If we get here, we don't know how to serialize the given MLIR attribute as
1042  // a OMIR value.
1043  jsonStream.value("<unsupported value>");
1044  getOperation().emitError("unsupported attribute for OMIR serialization: `")
1045  << node << "`";
1046  anyFailures = true;
1047 }
1048 
1049 void EmitOMIRPass::emitTrackedTarget(DictionaryAttr node,
1050  llvm::json::OStream &jsonStream,
1051  bool dutInstance) {
1052  // Extract the `id` field.
1053  auto idAttr = node.getAs<IntegerAttr>("id");
1054  if (!idAttr) {
1055  getOperation()
1056  .emitError("tracked OMIR target missing `id` string field")
1057  .attachNote(getOperation().getLoc())
1058  << node;
1059  anyFailures = true;
1060  return jsonStream.value("<error>");
1061  }
1062 
1063  // Extract the `type` field.
1064  auto typeAttr = node.getAs<StringAttr>("type");
1065  if (!typeAttr) {
1066  getOperation()
1067  .emitError("tracked OMIR target missing `type` string field")
1068  .attachNote(getOperation().getLoc())
1069  << node;
1070  anyFailures = true;
1071  return jsonStream.value("<error>");
1072  }
1073  StringRef type = typeAttr.getValue();
1074 
1075  // Find the tracker for this target, and handle the case where the tracker has
1076  // been deleted.
1077  auto trackerIt = trackers.find(idAttr);
1078  if (trackerIt == trackers.end()) {
1079  // Some of the target types indicate removal of the target through an
1080  // `OMDeleted` node.
1081  if (type == "OMReferenceTarget" || type == "OMMemberReferenceTarget" ||
1082  type == "OMMemberInstanceTarget")
1083  return jsonStream.value("OMDeleted:");
1084 
1085  // The remaining types produce an error upon removal of the target.
1086  auto diag = getOperation().emitError("tracked OMIR target of type `")
1087  << type << "` was deleted";
1088  diag.attachNote(getOperation().getLoc())
1089  << "`" << type << "` should never be deleted";
1090  if (auto path = node.getAs<StringAttr>("path"))
1091  diag.attachNote(getOperation().getLoc())
1092  << "original path: `" << path.getValue() << "`";
1093  anyFailures = true;
1094  return jsonStream.value("<error>");
1095  }
1096  auto tracker = trackerIt->second;
1097 
1098  // In case this is an `OMMemberTarget`, handle the case where the component
1099  // used to be a "reference target" (wire, register, memory, node) when the
1100  // OMIR was read in, but has been change to an "instance target" during the
1101  // execution of the compiler. This mainly occurs during mapping of
1102  // `firrtl.mem` operations to a corresponding `firrtl.instance`.
1103  if (type == "OMMemberReferenceTarget" && isa<InstanceOp, MemOp>(tracker.op))
1104  type = "OMMemberInstanceTarget";
1105 
1106  // Serialize the target circuit first.
1107  SmallString<64> target(type);
1108  target.append(":~");
1109  target.append(getOperation().getName());
1110  target.push_back('|');
1111 
1112  // Serialize the local or non-local module/instance hierarchy path.
1113  if (tracker.nla) {
1114  bool notFirst = false;
1115  hw::InnerRefAttr instName;
1116  for (auto nameRef : tracker.nla.getNamepath()) {
1117  StringAttr modName;
1118  if (auto innerRef = nameRef.dyn_cast<hw::InnerRefAttr>())
1119  modName = innerRef.getModule();
1120  else if (auto ref = dyn_cast<FlatSymbolRefAttr>(nameRef))
1121  modName = ref.getAttr();
1122  if (!dutInstance && modName == dutModuleName) {
1123  // Check if the DUT module occurs in the instance path.
1124  // Print the path relative to the DUT, if the nla is inside the DUT.
1125  // Keep the path for dutInstance relative to test harness. (SFC
1126  // implementation in TestHarnessOMPhase.scala)
1127  target = type;
1128  target.append(":~");
1129  target.append(dutModuleName);
1130  target.push_back('|');
1131  notFirst = false;
1132  instName = {};
1133  }
1134 
1135  Operation *module = nlaTable->getModule(modName);
1136  assert(module);
1137  if (notFirst)
1138  target.push_back('/');
1139  notFirst = true;
1140  if (instName) {
1141  target.append(addSymbol(instName));
1142  target.push_back(':');
1143  }
1144  target.append(addSymbol(module));
1145 
1146  if (auto innerRef = nameRef.dyn_cast<hw::InnerRefAttr>()) {
1147  // Find an instance with the given name in this module. Ensure it has a
1148  // symbol that we can refer to.
1149  auto instOp = instancesByName.lookup(innerRef);
1150  if (!instOp)
1151  continue;
1152  LLVM_DEBUG(llvm::dbgs() << "Marking NLA-participating instance "
1153  << innerRef.getName() << " in module "
1154  << modName << " as dont-touch\n");
1155  tempSymInstances.erase(instOp);
1156  instName = getInnerRefTo(instOp);
1157  }
1158  }
1159  } else {
1160  FModuleOp module = dyn_cast<FModuleOp>(tracker.op);
1161  if (!module)
1162  module = tracker.op->getParentOfType<FModuleOp>();
1163  assert(module);
1164  if (module.getNameAttr() == dutModuleName) {
1165  // If module is DUT, then root the target relative to the DUT.
1166  target = type;
1167  target.append(":~");
1168  target.append(dutModuleName);
1169  target.push_back('|');
1170  }
1171  target.append(addSymbol(module));
1172  }
1173 
1174  // Serialize any potential component *inside* the module that this target may
1175  // specifically refer to.
1176  hw::InnerRefAttr componentName;
1177  FIRRTLType componentType;
1178  if (isa<WireOp, RegOp, RegResetOp, InstanceOp, NodeOp, MemOp>(tracker.op)) {
1179  tempSymInstances.erase(tracker.op);
1180  componentName = getInnerRefTo(tracker.op);
1181  LLVM_DEBUG(llvm::dbgs() << "Marking OMIR-targeted " << componentName
1182  << " as dont-touch\n");
1183 
1184  // If the target refers to a field, get the type of the component so we can
1185  // extract the field, or fail if we don't know how to get the type.
1186  if (tracker.hasFieldID()) {
1187  if (isa<WireOp, RegOp, RegResetOp, NodeOp>(tracker.op)) {
1188  componentType = getTypeOf(tracker.op);
1189  } else {
1190  tracker.op->emitError("does not support OMIR targeting fields");
1191  anyFailures = true;
1192  return jsonStream.value("<error>");
1193  }
1194  }
1195  } else if (auto mod = dyn_cast<FModuleLike>(tracker.op)) {
1196  if (tracker.portNo >= 0) {
1197  componentName = getInnerRefTo(mod, tracker.portNo);
1198 
1199  // If the target refers to a field, get the type of the port.
1200  if (tracker.hasFieldID())
1201  componentType = getTypeOf(mod, tracker.portNo);
1202  }
1203  } else if (!isa<FModuleLike>(tracker.op)) {
1204  tracker.op->emitError("invalid target for `") << type << "` OMIR";
1205  anyFailures = true;
1206  return jsonStream.value("<error>");
1207  }
1208  if (componentName) {
1209  // Check if the targeted component is going to be emitted as an instance.
1210  // This is trivially the case for `InstanceOp`s, but also for `MemOp`s that
1211  // get converted to an instance during lowering to HW dialect and generator
1212  // callout.
1213  [&] {
1214  if (type == "OMMemberInstanceTarget") {
1215  if (auto instOp = dyn_cast<InstanceOp>(tracker.op)) {
1216  target.push_back('/');
1217  target.append(addSymbol(componentName));
1218  target.push_back(':');
1219  target.append(addSymbol(instOp.getModuleNameAttr()));
1220  return;
1221  }
1222  if (auto memOp = dyn_cast<MemOp>(tracker.op)) {
1223  target.push_back('/');
1224  target.append(addSymbol(componentName));
1225  target.push_back(':');
1226  target.append(memOp.getSummary().getFirMemoryName());
1227  return;
1228  }
1229  }
1230  target.push_back('>');
1231  target.append(addSymbol(componentName));
1232  addFieldID(componentType, tracker.fieldID, target);
1233  }();
1234  }
1235 
1236  jsonStream.value(target);
1237 }
1238 
1239 hw::InnerRefAttr EmitOMIRPass::getInnerRefTo(Operation *op) {
1241  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
1242  return getModuleNamespace(module);
1243  });
1244 }
1245 
1246 hw::InnerRefAttr EmitOMIRPass::getInnerRefTo(FModuleLike module,
1247  size_t portIdx) {
1248  return ::getInnerRefTo(module, portIdx,
1249  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
1250  return getModuleNamespace(mod);
1251  });
1252 }
1253 
1254 FIRRTLType EmitOMIRPass::getTypeOf(Operation *op) {
1255  if (auto fop = dyn_cast<Forceable>(op))
1256  return fop.getDataType();
1257  assert(op->getNumResults() == 1 &&
1258  isa<FIRRTLType>(op->getResult(0).getType()) &&
1259  "op must have a single FIRRTLType result");
1260  return type_cast<FIRRTLType>(op->getResult(0).getType());
1261 }
1262 
1263 FIRRTLType EmitOMIRPass::getTypeOf(FModuleLike mod, size_t portIdx) {
1264  Type portType = mod.getPortType(portIdx);
1265  assert(isa<FIRRTLType>(portType) && "port must have a FIRRTLType");
1266  return type_cast<FIRRTLType>(portType);
1267 }
1268 
1269 // Constructs a reference to a field of an aggregate FIRRTLType with a fieldID,
1270 // and appends it to result. If fieldID is 0, meaning it does not reference a
1271 // field of an aggregate FIRRTLType, this is a no-op.
1272 void EmitOMIRPass::addFieldID(FIRRTLType type, unsigned fieldID,
1273  SmallVectorImpl<char> &result) {
1274  while (fieldID)
1276  .Case<FVectorType>([&](FVectorType vector) {
1277  size_t index = vector.getIndexForFieldID(fieldID);
1278  type = vector.getElementType();
1279  fieldID -= vector.getFieldID(index);
1280  result.push_back('[');
1281  Twine(index).toVector(result);
1282  result.push_back(']');
1283  })
1284  .Case<BundleType>([&](BundleType bundle) {
1285  size_t index = bundle.getIndexForFieldID(fieldID);
1286  StringRef name = bundle.getElement(index).name;
1287  type = bundle.getElementType(index);
1288  fieldID -= bundle.getFieldID(index);
1289  result.push_back('.');
1290  result.append(name.begin(), name.end());
1291  });
1292 }
1293 
1294 //===----------------------------------------------------------------------===//
1295 // Pass Infrastructure
1296 //===----------------------------------------------------------------------===//
1297 
1298 std::unique_ptr<mlir::Pass>
1299 circt::firrtl::createEmitOMIRPass(StringRef outputFilename) {
1300  auto pass = std::make_unique<EmitOMIRPass>();
1301  if (!outputFilename.empty())
1302  pass->outputFilename = outputFilename.str();
1303  return pass;
1304 }
lowerAnnotationsNoRefTypePorts FirtoolPreserveValuesMode value
Definition: Firtool.cpp:95
assert(baseType &&"element must be base type")
static std::optional< DictionaryAttr > scatterOMNode(Attribute original, const Attribute root, ApplyState &state)
Convert an Object Model Node to an optional dictionary, convert source locator strings to location at...
Definition: EmitOMIR.cpp:419
static IntegerAttr isOMSRAM(Attribute &node)
Check if an OMNode is an OMSRAM and requires special treatment of its instance path field.
Definition: EmitOMIR.cpp:160
static std::optional< std::pair< StringRef, DictionaryAttr > > scatterOMField(Attribute original, const Attribute root, unsigned index, ApplyState &state)
Convert an Object Model Field into an optional pair of a string key and a dictionary attribute.
Definition: EmitOMIR.cpp:353
static std::optional< Attribute > scatterOMIR(Attribute original, ApplyState &state)
Recursively walk Object Model IR and convert FIRRTL targets to identifiers while scattering trackers ...
Definition: EmitOMIR.cpp:210
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
Builder builder
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.
This class provides a read-only projection of an annotation.
unsigned getFieldID() const
Get the field id this attribute targets.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
Definition: FIRRTLTypes.h:515
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
Definition: FIRRTLTypes.h:525
This graph tracks modules and where they are instantiated.
This table tracks nlas and what modules participate in them.
Definition: NLATable.h:29
static StringRef getInnerSymbolAttrName()
Return the name of the attribute used for inner symbol names.
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
auto getInstance()
Get the instance-like op that this is tracking.
Definition: InstanceGraph.h:59
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
LogicalResult applyOMIR(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
Main entry point to handle scattering of an OMIRAnnotation.
Definition: EmitOMIR.cpp:489
std::unique_ptr< mlir::Pass > createEmitOMIRPass(mlir::StringRef outputFilename="")
std::pair< bool, std::optional< mlir::LocationAttr > > maybeStringToLocation(llvm::StringRef spelling, bool skipParsing, mlir::StringAttr &locatorFilenameCache, FileLineColLoc &fileLineColLocCache, MLIRContext *context)
constexpr const char * dutAnnoClass
constexpr const char * omirAnnoClass
constexpr const char * omirTrackerAnnoClass
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
bool isOMIRStringEncodedPassthrough(StringRef type)
Check if an OMIR type is a string-encoded value that the FIRRTL dialect simply passes through as a st...
constexpr const char * omirFileAnnoClass
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition: FIRRTLOps.h:108
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & errs()
Definition: Utility.h:33
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
State threaded through functions for resolving and applying annotations.
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
A data structure that caches and provides absolute paths to module instances in the IR.