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