CIRCT  19.0.0git
FIRRTLAnnotationHelper.cpp
Go to the documentation of this file.
1 //===- FIRRTLAnnotationHelper.cpp - FIRRTL Annotation Lookup ----*- 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 implements helpers mapping annotations to operations.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "mlir/IR/ImplicitLocOpBuilder.h"
17 #include "llvm/Support/Debug.h"
18 
19 #define DEBUG_TYPE "lower-annos"
20 
21 using namespace circt;
22 using namespace firrtl;
23 using namespace chirrtl;
24 
25 using llvm::StringRef;
26 
27 // Some types have been expanded so the first layer of aggregate path is
28 // a return value.
29 static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &ref) {
30  if (auto mem = dyn_cast<MemOp>(ref.getOp()))
31  for (size_t p = 0, pe = mem.getPortNames().size(); p < pe; ++p)
32  if (mem.getPortNameStr(p) == field) {
33  ref = PortAnnoTarget(mem, p);
34  return success();
35  }
36  ref.getOp()->emitError("Cannot find port with name ") << field;
37  return failure();
38 }
39 
40 /// Try to resolve an non-array aggregate name from a target given the type and
41 /// operation of the resolved target. This needs to deal with places where we
42 /// represent bundle returns as split into constituent parts.
43 static FailureOr<unsigned> findBundleElement(Operation *op, Type type,
44  StringRef field) {
45  auto bundle = type_dyn_cast<BundleType>(type);
46  if (!bundle) {
47  op->emitError("field access '")
48  << field << "' into non-bundle type '" << type << "'";
49  return failure();
50  }
51  auto idx = bundle.getElementIndex(field);
52  if (!idx) {
53  op->emitError("cannot resolve field '")
54  << field << "' in subtype '" << bundle << "'";
55  return failure();
56  }
57  return *idx;
58 }
59 
60 /// Try to resolve an array index from a target given the type of the resolved
61 /// target.
62 static FailureOr<unsigned> findVectorElement(Operation *op, Type type,
63  StringRef indexStr) {
64  size_t index;
65  if (indexStr.getAsInteger(10, index)) {
66  op->emitError("Cannot convert '") << indexStr << "' to an integer";
67  return failure();
68  }
69  auto vec = type_dyn_cast<FVectorType>(type);
70  if (!vec) {
71  op->emitError("index access '")
72  << index << "' into non-vector type '" << type << "'";
73  return failure();
74  }
75  return index;
76 }
77 
79  ArrayRef<TargetToken> tokens) {
80  if (tokens.empty())
81  return 0;
82 
83  auto *op = ref.getOp();
84  auto fieldIdx = 0;
85  // The first field for some ops refers to expanded return values.
86  if (isa<MemOp>(op)) {
87  if (failed(updateExpandedPort(tokens.front().name, ref)))
88  return {};
89  tokens = tokens.drop_front();
90  }
91 
92  auto type = ref.getType();
93  if (!type)
94  return op->emitError(tokens.front().isIndex ? "index" : "field")
95  << " access in annotation not supported for this operation";
96 
97  for (auto token : tokens) {
98  if (token.isIndex) {
99  auto result = findVectorElement(op, type, token.name);
100  if (failed(result))
101  return failure();
102  auto vector = type_cast<FVectorType>(type);
103  type = vector.getElementType();
104  fieldIdx += vector.getFieldID(*result);
105  } else {
106  auto result = findBundleElement(op, type, token.name);
107  if (failed(result))
108  return failure();
109  auto bundle = type_cast<BundleType>(type);
110  type = bundle.getElementType(*result);
111  fieldIdx += bundle.getFieldID(*result);
112  }
113  }
114  return fieldIdx;
115 }
116 
117 void TokenAnnoTarget::toVector(SmallVectorImpl<char> &out) const {
118  out.push_back('~');
119  out.append(circuit.begin(), circuit.end());
120  out.push_back('|');
121  for (auto modInstPair : instances) {
122  out.append(modInstPair.first.begin(), modInstPair.first.end());
123  out.push_back('/');
124  out.append(modInstPair.second.begin(), modInstPair.second.end());
125  out.push_back(':');
126  }
127  out.append(module.begin(), module.end());
128  if (name.empty())
129  return;
130  out.push_back('>');
131  out.append(name.begin(), name.end());
132  for (auto comp : component) {
133  out.push_back(comp.isIndex ? '[' : '.');
134  out.append(comp.name.begin(), comp.name.end());
135  if (comp.isIndex)
136  out.push_back(']');
137  }
138 }
139 
140 std::string firrtl::canonicalizeTarget(StringRef target) {
141 
142  if (target.empty())
143  return target.str();
144 
145  // If this is a normal Target (not a Named), erase that field in the JSON
146  // object and return that Target.
147  if (target[0] == '~')
148  return target.str();
149 
150  // This is a legacy target using the firrtl.annotations.Named type. This
151  // can be trivially canonicalized to a non-legacy target, so we do it with
152  // the following three mappings:
153  // 1. CircuitName => CircuitTarget, e.g., A -> ~A
154  // 2. ModuleName => ModuleTarget, e.g., A.B -> ~A|B
155  // 3. ComponentName => ReferenceTarget, e.g., A.B.C -> ~A|B>C
156  std::string newTarget = ("~" + target).str();
157  auto n = newTarget.find('.');
158  if (n != std::string::npos)
159  newTarget[n] = '|';
160  n = newTarget.find('.');
161  if (n != std::string::npos)
162  newTarget[n] = '>';
163  return newTarget;
164 }
165 
166 std::optional<AnnoPathValue>
167 firrtl::resolveEntities(TokenAnnoTarget path, CircuitOp circuit,
168  SymbolTable &symTbl, CircuitTargetCache &cache) {
169  // Validate circuit name.
170  if (!path.circuit.empty() && circuit.getName() != path.circuit) {
171  mlir::emitError(circuit.getLoc())
172  << "circuit name doesn't match annotation '" << path.circuit << '\'';
173  return {};
174  }
175  // Circuit only target.
176  if (path.module.empty()) {
177  assert(path.name.empty() && path.instances.empty() &&
178  path.component.empty());
179  return AnnoPathValue(circuit);
180  }
181 
182  // Resolve all instances for non-local paths.
183  SmallVector<InstanceOp> instances;
184  for (auto p : path.instances) {
185  auto mod = symTbl.lookup<FModuleOp>(p.first);
186  if (!mod) {
187  mlir::emitError(circuit.getLoc())
188  << "module doesn't exist '" << p.first << '\'';
189  return {};
190  }
191  auto resolved = cache.lookup(mod, p.second);
192  if (!resolved || !isa<InstanceOp>(resolved.getOp())) {
193  mlir::emitError(circuit.getLoc()) << "cannot find instance '" << p.second
194  << "' in '" << mod.getName() << "'";
195  return {};
196  }
197  instances.push_back(cast<InstanceOp>(resolved.getOp()));
198  }
199  // The final module is where the named target is (or is the named target).
200  auto mod = symTbl.lookup<FModuleLike>(path.module);
201  if (!mod) {
202  mlir::emitError(circuit.getLoc())
203  << "module doesn't exist '" << path.module << '\'';
204  return {};
205  }
206 
207  // ClassOps may not participate in annotation targeting. Neither the class
208  // itself, nor any "named thing" defined under it, may be targeted by an anno.
209  if (isa<ClassOp>(mod)) {
210  mlir::emitError(mod.getLoc()) << "annotations cannot target classes";
211  return {};
212  }
213 
214  AnnoTarget ref;
215  if (path.name.empty()) {
216  assert(path.component.empty());
217  ref = OpAnnoTarget(mod);
218  } else {
219  ref = cache.lookup(mod, path.name);
220  if (!ref) {
221  mlir::emitError(circuit.getLoc()) << "cannot find name '" << path.name
222  << "' in " << mod.getModuleName();
223  return {};
224  }
225  // AnnoTarget::getType() is not safe (CHIRRTL ops crash, null if instance),
226  // avoid. For now, only references in ports can be targets, check that.
227  // TODO: containsReference().
228  if (ref.isa<PortAnnoTarget>() && isa<RefType>(ref.getType())) {
229  mlir::emitError(circuit.getLoc())
230  << "cannot target reference-type '" << path.name << "' in "
231  << mod.getModuleName();
232  return {};
233  }
234  }
235 
236  // If the reference is pointing to an instance op, we have to move the target
237  // to the module. This is done both because it is logical to have one
238  // representation (this effectively canonicalizes a reference target on an
239  // instance into an instance target) and because the SFC has a pass that does
240  // this conversion. E.g., this is converting (where "bar" is an instance):
241  // ~Foo|Foo>bar
242  // Into:
243  // ~Foo|Foo/bar:Bar
244  ArrayRef<TargetToken> component(path.component);
245  if (auto instance = dyn_cast<InstanceOp>(ref.getOp())) {
246  instances.push_back(instance);
247  auto target = cast<FModuleLike>(instance.getReferencedOperation(symTbl));
248  if (component.empty()) {
249  ref = OpAnnoTarget(target);
250  } else if (component.front().isIndex) {
251  mlir::emitError(circuit.getLoc())
252  << "illegal target '" << path.str() << "' indexes into an instance";
253  return {};
254  } else {
255  auto field = component.front().name;
256  ref = AnnoTarget();
257  for (size_t p = 0, pe = getNumPorts(target); p < pe; ++p)
258  if (target.getPortName(p) == field) {
259  ref = PortAnnoTarget(target, p);
260  break;
261  }
262  if (!ref) {
263  mlir::emitError(circuit.getLoc())
264  << "cannot find port '" << field << "' in module "
265  << target.getModuleName();
266  return {};
267  }
268  // TODO: containsReference().
269  if (isa<RefType>(ref.getType())) {
270  mlir::emitError(circuit.getLoc())
271  << "annotation cannot target reference-type port '" << field
272  << "' in module " << target.getModuleName();
273  return {};
274  }
275  component = component.drop_front();
276  }
277  }
278 
279  // If we have aggregate specifiers, resolve those now. This call can update
280  // the ref to target a port of a memory.
281  auto result = findFieldID(ref, component);
282  if (failed(result))
283  return {};
284  auto fieldIdx = *result;
285 
286  return AnnoPathValue(instances, ref, fieldIdx);
287 }
288 
289 /// split a target string into it constituent parts. This is the primary parser
290 /// for targets.
291 std::optional<TokenAnnoTarget> firrtl::tokenizePath(StringRef origTarget) {
292  // An empty string is not a legal target.
293  if (origTarget.empty())
294  return {};
295  StringRef target = origTarget;
296  TokenAnnoTarget retval;
297  std::tie(retval.circuit, target) = target.split('|');
298  if (!retval.circuit.empty() && retval.circuit[0] == '~')
299  retval.circuit = retval.circuit.drop_front();
300  while (target.count(':')) {
301  StringRef nla;
302  std::tie(nla, target) = target.split(':');
303  StringRef inst, mod;
304  std::tie(mod, inst) = nla.split('/');
305  retval.instances.emplace_back(mod, inst);
306  }
307  // remove aggregate
308  auto targetBase =
309  target.take_until([](char c) { return c == '.' || c == '['; });
310  auto aggBase = target.drop_front(targetBase.size());
311  std::tie(retval.module, retval.name) = targetBase.split('>');
312  while (!aggBase.empty()) {
313  if (aggBase[0] == '.') {
314  aggBase = aggBase.drop_front();
315  StringRef field = aggBase.take_front(aggBase.find_first_of("[."));
316  aggBase = aggBase.drop_front(field.size());
317  retval.component.push_back({field, false});
318  } else if (aggBase[0] == '[') {
319  aggBase = aggBase.drop_front();
320  StringRef index = aggBase.take_front(aggBase.find_first_of(']'));
321  aggBase = aggBase.drop_front(index.size() + 1);
322  retval.component.push_back({index, true});
323  } else {
324  return {};
325  }
326  }
327 
328  return retval;
329 }
330 
331 std::optional<AnnoPathValue> firrtl::resolvePath(StringRef rawPath,
332  CircuitOp circuit,
333  SymbolTable &symTbl,
334  CircuitTargetCache &cache) {
335  auto pathStr = canonicalizeTarget(rawPath);
336  StringRef path{pathStr};
337 
338  auto tokens = tokenizePath(path);
339  if (!tokens) {
340  mlir::emitError(circuit.getLoc())
341  << "Cannot tokenize annotation path " << rawPath;
342  return {};
343  }
344 
345  return resolveEntities(*tokens, circuit, symTbl, cache);
346 }
347 
348 InstanceOp firrtl::addPortsToModule(FModuleLike mod, InstanceOp instOnPath,
349  FIRRTLType portType, Direction dir,
350  StringRef newName,
351  InstancePathCache &instancePathcache,
352  CircuitTargetCache *targetCaches) {
353  // To store the cloned version of `instOnPath`.
354  InstanceOp clonedInstOnPath;
355  // Get a new port name from the Namespace.
356  auto portName = StringAttr::get(mod.getContext(), newName);
357  // The port number for the new port.
358  unsigned portNo = getNumPorts(mod);
359  PortInfo portInfo = {portName, portType, dir, {}, mod.getLoc()};
360  mod.insertPorts({{portNo, portInfo}});
361  // Now update all the instances of `mod`.
362  for (auto *use : instancePathcache.instanceGraph.lookup(mod)->uses()) {
363  InstanceOp useInst = cast<InstanceOp>(use->getInstance());
364  auto clonedInst = useInst.cloneAndInsertPorts({{portNo, portInfo}});
365  if (useInst == instOnPath)
366  clonedInstOnPath = clonedInst;
367  // Update all occurences of old instance.
368  instancePathcache.replaceInstance(useInst, clonedInst);
369  if (targetCaches)
370  targetCaches->replaceOp(useInst, clonedInst);
371  useInst->replaceAllUsesWith(clonedInst.getResults().drop_back());
372  useInst->erase();
373  }
374  return clonedInstOnPath;
375 }
376 
377 //===----------------------------------------------------------------------===//
378 // AnnoTargetCache
379 //===----------------------------------------------------------------------===//
380 
381 void AnnoTargetCache::gatherTargets(FModuleLike mod) {
382  // Add ports
383  for (const auto &p : llvm::enumerate(mod.getPorts()))
384  insertPort(mod, p.index());
385 
386  // And named things
387  mod.walk([&](Operation *op) { insertOp(op); });
388 }
389 
390 //===----------------------------------------------------------------------===//
391 // HierPathOpCache
392 //===----------------------------------------------------------------------===//
393 
394 HierPathCache::HierPathCache(Operation *op, SymbolTable &symbolTable)
395  : builder(OpBuilder::atBlockBegin(&op->getRegion(0).front())),
396  symbolTable(symbolTable) {
397 
398  // Populate the cache with any symbols preexisting.
399  for (auto &region : op->getRegions())
400  for (auto &block : region.getBlocks())
401  for (auto path : block.getOps<hw::HierPathOp>())
402  cache[path.getNamepathAttr()] = path;
403 }
404 
405 hw::HierPathOp HierPathCache::getOpFor(ArrayAttr attr) {
406  auto &op = cache[attr];
407  if (!op) {
408  op = builder.create<hw::HierPathOp>(UnknownLoc::get(builder.getContext()),
409  "nla", attr);
410  symbolTable.insert(op);
411  op.setVisibility(SymbolTable::Visibility::Private);
412  }
413  return op;
414 }
415 
416 //===----------------------------------------------------------------------===//
417 // Code related to handling Grand Central Data/Mem Taps annotations
418 //===----------------------------------------------------------------------===//
419 
420 static Value lowerInternalPathAnno(AnnoPathValue &srcTarget,
421  const AnnoPathValue &moduleTarget,
422  const AnnoPathValue &target,
423  StringAttr internalPathAttr,
424  FIRRTLBaseType targetType,
425  ApplyState &state) {
426  Value sendVal;
427  FModuleLike mod = cast<FModuleLike>(moduleTarget.ref.getOp());
428  InstanceOp modInstance;
429  if (!moduleTarget.instances.empty()) {
430  modInstance = moduleTarget.instances.back();
431  } else {
432  auto *node = state.instancePathCache.instanceGraph.lookup(mod);
433  if (!node->hasOneUse()) {
434  mod->emitOpError(
435  "cannot be used for DataTaps, it is instantiated multiple times");
436  return nullptr;
437  }
438  modInstance = cast<InstanceOp>((*node->uses().begin())->getInstance());
439  }
440  ImplicitLocOpBuilder builder(modInstance.getLoc(), modInstance);
441  builder.setInsertionPointAfter(modInstance);
442  auto portRefType = RefType::get(targetType);
443  SmallString<32> refName;
444  for (auto c : internalPathAttr.getValue()) {
445  switch (c) {
446  case '.':
447  case '[':
448  refName.push_back('_');
449  break;
450  case ']':
451  break;
452  default:
453  refName.push_back(c);
454  break;
455  }
456  }
457 
458  // Add RefType ports corresponding to this "internalPath" to the external
459  // module. This also updates all the instances of the external module.
460  // This removes and replaces the instance, and returns the updated
461  // instance.
462  if (!state.wiringProblemInstRefs.contains(modInstance)) {
463  modInstance =
464  addPortsToModule(mod, modInstance, portRefType, Direction::Out, refName,
465  state.instancePathCache, &state.targetCaches);
466  } else {
467  // As a current limitation, mixing legacy Wiring and Data Taps is forbidden
468  // to prevent invalidating Values used later
469  mod->emitOpError(
470  "cannot be used for both legacy Wiring and DataTaps simultaneously");
471  return nullptr;
472  }
473 
474  // Since the instance op generates the RefType output, no need of another
475  // RefSendOp. Store into an op to ensure we have stable reference,
476  // so future tapping won't invalidate this Value.
477  sendVal = modInstance.getResults().back();
478  sendVal =
479  builder
480  .create<mlir::UnrealizedConversionCastOp>(sendVal.getType(), sendVal)
481  ->getResult(0);
482 
483  // Now set the instance as the source for the final datatap xmr.
484  srcTarget = AnnoPathValue(modInstance);
485  if (auto extMod = dyn_cast<FExtModuleOp>((Operation *)mod)) {
486  // Set the internal path for the new port, creating the paths array
487  // if not already present.
488  SmallVector<Attribute> paths;
489  if (auto internalPaths = extMod.getInternalPaths())
490  llvm::append_range(paths, internalPaths->getValue());
491  else
492  paths.resize(extMod.getNumPorts(), builder.getAttr<InternalPathAttr>());
493  paths.back() = builder.getAttr<InternalPathAttr>(internalPathAttr);
494  extMod.setInternalPathsAttr(builder.getArrayAttr(paths));
495  } else if (auto intMod = dyn_cast<FModuleOp>((Operation *)mod)) {
496  auto builder = ImplicitLocOpBuilder::atBlockEnd(
497  intMod.getLoc(), &intMod.getBody().getBlocks().back());
498  auto pathStr = builder.create<VerbatimExprOp>(
499  portRefType.getType(), internalPathAttr.getValue(), ValueRange{});
500  auto sendPath = builder.create<RefSendOp>(pathStr);
501  emitConnect(builder, intMod.getArguments().back(), sendPath.getResult());
502  }
503 
504  if (!moduleTarget.instances.empty())
505  srcTarget.instances = moduleTarget.instances;
506  else {
507  auto path = state.instancePathCache
508  .getAbsolutePaths(modInstance->getParentOfType<FModuleOp>())
509  .back();
510  srcTarget.instances.append(path.begin(), path.end());
511  }
512  return sendVal;
513 }
514 
515 // Describes tap points into the design. This has the following structure:
516 // keys: Seq[DataTapKey]
517 // DataTapKey has multiple implementations:
518 // - ReferenceDataTapKey: (tapping a point which exists in the FIRRTL)
519 // sink: ReferenceTarget
520 // source: ReferenceTarget
521 // - DataTapModuleSignalKey: (tapping a point, by name, in a blackbox)
522 // module: IsModule
523 // internalPath: String
524 // sink: ReferenceTarget
525 // - DeletedDataTapKey: (not implemented here)
526 // sink: ReferenceTarget
527 // - LiteralDataTapKey: (not implemented here)
528 // literal: Literal
529 // sink: ReferenceTarget
530 // A Literal is a FIRRTL IR literal serialized to a string. For now, just
531 // store the string.
532 // TODO: Parse the literal string into a UInt or SInt literal.
533 LogicalResult circt::firrtl::applyGCTDataTaps(const AnnoPathValue &target,
534  DictionaryAttr anno,
535  ApplyState &state) {
536  auto *context = state.circuit.getContext();
537  auto loc = state.circuit.getLoc();
538 
539  // Process all the taps.
540  auto keyAttr = tryGetAs<ArrayAttr>(anno, anno, "keys", loc, dataTapsClass);
541  if (!keyAttr)
542  return failure();
543  for (size_t i = 0, e = keyAttr.size(); i != e; ++i) {
544  auto b = keyAttr[i];
545  auto path = ("keys[" + Twine(i) + "]").str();
546  auto bDict = cast<DictionaryAttr>(b);
547  auto classAttr =
548  tryGetAs<StringAttr>(bDict, anno, "class", loc, dataTapsClass, path);
549  if (!classAttr)
550  return failure();
551  // Can only handle ReferenceDataTapKey and DataTapModuleSignalKey
552  if (classAttr.getValue() != referenceKeyClass &&
553  classAttr.getValue() != internalKeyClass)
554  return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
555  "' with path '" +
556  (Twine(path) + ".class") +
557  "' contained an unknown/unimplemented "
558  "DataTapKey class '" +
559  classAttr.getValue() + "'.")
560  .attachNote()
561  << "The full Annotation is reproduced here: " << anno << "\n";
562 
563  auto sinkNameAttr =
564  tryGetAs<StringAttr>(bDict, anno, "sink", loc, dataTapsClass, path);
565  std::string wirePathStr;
566  if (sinkNameAttr)
567  wirePathStr = canonicalizeTarget(sinkNameAttr.getValue());
568  if (!wirePathStr.empty())
569  if (!tokenizePath(wirePathStr))
570  wirePathStr.clear();
571  std::optional<AnnoPathValue> wireTarget;
572  if (!wirePathStr.empty())
573  wireTarget = resolvePath(wirePathStr, state.circuit, state.symTbl,
574  state.targetCaches);
575  if (!wireTarget)
576  return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
577  "' with wire path '" + wirePathStr +
578  "' could not be resolved.");
579  if (!wireTarget->ref.getImpl().isOp())
580  return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
581  "' with path '" +
582  (Twine(path) + ".class") +
583  "' cannot specify a port for sink.");
584  // Extract the name of the wire, used for datatap.
585  auto tapName = StringAttr::get(
586  context, wirePathStr.substr(wirePathStr.find_last_of('>') + 1));
587  std::optional<AnnoPathValue> srcTarget;
588  Value sendVal;
589  if (classAttr.getValue() == internalKeyClass) {
590  // For DataTapModuleSignalKey, the source is encoded as a string, that
591  // should exist inside the specified module. This source string is used as
592  // a suffix to the instance name for the module inside a VerbatimExprOp.
593  // This verbatim represents an intermediate xmr, which is then used by a
594  // ref.send to be read remotely.
595  auto internalPathAttr = tryGetAs<StringAttr>(bDict, anno, "internalPath",
596  loc, dataTapsClass, path);
597  auto moduleAttr =
598  tryGetAs<StringAttr>(bDict, anno, "module", loc, dataTapsClass, path);
599  if (!internalPathAttr || !moduleAttr)
600  return failure();
601  auto moduleTargetStr = canonicalizeTarget(moduleAttr.getValue());
602  if (!tokenizePath(moduleTargetStr))
603  return failure();
604  std::optional<AnnoPathValue> moduleTarget = resolvePath(
605  moduleTargetStr, state.circuit, state.symTbl, state.targetCaches);
606  if (!moduleTarget)
607  return failure();
608  AnnoPathValue internalPathSrc;
609  auto targetType =
610  firrtl::type_cast<FIRRTLBaseType>(wireTarget->ref.getType());
611  if (wireTarget->fieldIdx)
612  targetType = firrtl::type_cast<FIRRTLBaseType>(
614  wireTarget->fieldIdx));
615  sendVal = lowerInternalPathAnno(internalPathSrc, *moduleTarget, target,
616  internalPathAttr, targetType, state);
617  if (!sendVal)
618  return failure();
619  srcTarget = internalPathSrc;
620  } else {
621  // Now handle ReferenceDataTapKey. Get the source from annotation.
622  auto sourceAttr =
623  tryGetAs<StringAttr>(bDict, anno, "source", loc, dataTapsClass, path);
624  if (!sourceAttr)
625  return failure();
626  auto sourcePathStr = canonicalizeTarget(sourceAttr.getValue());
627  if (!tokenizePath(sourcePathStr))
628  return failure();
629  LLVM_DEBUG(llvm::dbgs() << "\n Drill xmr path from :" << sourcePathStr
630  << " to " << wirePathStr);
631  srcTarget = resolvePath(sourcePathStr, state.circuit, state.symTbl,
632  state.targetCaches);
633  }
634  if (!srcTarget)
635  return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
636  "' source path could not be resolved.");
637 
638  auto wireModule =
639  cast<FModuleOp>(wireTarget->ref.getModule().getOperation());
640 
641  if (auto extMod = dyn_cast<FExtModuleOp>(srcTarget->ref.getOp())) {
642  // If the source is a port on extern module, then move the source to the
643  // instance port for the ext module.
644  auto portNo = srcTarget->ref.getImpl().getPortNo();
645  auto lastInst = srcTarget->instances.pop_back_val();
646  auto builder = ImplicitLocOpBuilder::atBlockEnd(lastInst.getLoc(),
647  lastInst->getBlock());
648  builder.setInsertionPointAfter(lastInst);
649  // Instance port cannot be used as an annotation target, so use a NodeOp.
650  auto node = builder.create<NodeOp>(lastInst.getResult(portNo));
652  srcTarget->ref = AnnoTarget(circt::firrtl::detail::AnnoTargetImpl(node));
653  }
654 
655  // The RefSend value can be either generated by the instance of an external
656  // module or a RefSendOp.
657  if (!sendVal) {
658  auto srcModule =
659  dyn_cast<FModuleOp>(srcTarget->ref.getModule().getOperation());
660 
661  ImplicitLocOpBuilder sendBuilder(srcModule.getLoc(), srcModule);
662  // Set the insertion point for the RefSend, it should be dominated by the
663  // srcTarget value. If srcTarget is a port, then insert the RefSend
664  // at the beggining of the module, else define the RefSend at the end of
665  // the block that contains the srcTarget Op.
666  if (srcTarget->ref.getImpl().isOp()) {
667  sendVal = srcTarget->ref.getImpl().getOp()->getResult(0);
668  sendBuilder.setInsertionPointAfter(srcTarget->ref.getOp());
669  } else if (srcTarget->ref.getImpl().isPort()) {
670  sendVal = srcModule.getArgument(srcTarget->ref.getImpl().getPortNo());
671  sendBuilder.setInsertionPointToStart(srcModule.getBodyBlock());
672  }
673  // If the target value is a field of an aggregate create the
674  // subfield/subaccess into it.
675  sendVal = getValueByFieldID(sendBuilder, sendVal, srcTarget->fieldIdx);
676  // Note: No DontTouch added to sendVal, it can be constantprop'ed or
677  // CSE'ed.
678  }
679 
680  auto *targetOp = wireTarget->ref.getOp();
681  auto sinkBuilder = ImplicitLocOpBuilder::atBlockEnd(wireModule.getLoc(),
682  targetOp->getBlock());
683  auto wireType = type_cast<FIRRTLBaseType>(targetOp->getResult(0).getType());
684  // Get type of sent value, if already a RefType, the base type.
685  auto valType = getBaseType(type_cast<FIRRTLType>(sendVal.getType()));
686  Value sink = getValueByFieldID(sinkBuilder, targetOp->getResult(0),
687  wireTarget->fieldIdx);
688 
689  // For resets, sometimes inject a cast between sink and target 'sink'.
690  // Introduced a dummy wire and cast that, dummy wire will be 'sink'.
691  if (valType.isResetType() &&
692  valType.getWidthlessType() != wireType.getWidthlessType()) {
693  // Helper: create a wire, cast it with callback, connect cast to sink.
694  auto addWireWithCast = [&](auto createCast) {
695  auto wire =
696  sinkBuilder.create<WireOp>(valType, tapName.getValue()).getResult();
697  emitConnect(sinkBuilder, sink, createCast(wire));
698  sink = wire;
699  };
700  if (isa<IntType>(wireType))
701  addWireWithCast(
702  [&](auto v) { return sinkBuilder.create<AsUIntPrimOp>(v); });
703  else if (isa<AsyncResetType>(wireType))
704  addWireWithCast(
705  [&](auto v) { return sinkBuilder.create<AsAsyncResetPrimOp>(v); });
706  }
707 
708  state.wiringProblems.push_back(
709  {sendVal, sink, "", WiringProblem::RefTypeUsage::Prefer});
710  }
711 
712  return success();
713 }
714 
715 LogicalResult circt::firrtl::applyGCTMemTaps(const AnnoPathValue &target,
716  DictionaryAttr anno,
717  ApplyState &state) {
718  auto loc = state.circuit.getLoc();
719 
720  auto sourceAttr =
721  tryGetAs<StringAttr>(anno, anno, "source", loc, memTapClass);
722  if (!sourceAttr)
723  return failure();
724 
725  auto sourceTargetStr = canonicalizeTarget(sourceAttr.getValue());
726  std::optional<AnnoPathValue> srcTarget = resolvePath(
727  sourceTargetStr, state.circuit, state.symTbl, state.targetCaches);
728  if (!srcTarget)
729  return mlir::emitError(loc, "cannot resolve source target path '")
730  << sourceTargetStr << "'";
731 
732  auto tapsAttr = tryGetAs<ArrayAttr>(anno, anno, "sink", loc, memTapClass);
733  if (!tapsAttr || tapsAttr.empty())
734  return mlir::emitError(loc, "sink must have at least one entry");
735 
736  auto tap = tapsAttr[0].dyn_cast_or_null<StringAttr>();
737  if (!tap) {
738  return mlir::emitError(
739  loc, "Annotation '" + Twine(memTapClass) +
740  "' with path '.taps[0" +
741  "]' contained an unexpected type (expected a string).")
742  .attachNote()
743  << "The full Annotation is reprodcued here: " << anno << "\n";
744  }
745 
746  auto wireTargetStr = canonicalizeTarget(tap.getValue());
747  if (!tokenizePath(wireTargetStr))
748  return failure();
749  std::optional<AnnoPathValue> wireTarget = resolvePath(
750  wireTargetStr, state.circuit, state.symTbl, state.targetCaches);
751  if (!wireTarget)
752  return mlir::emitError(loc, "Annotation '" + Twine(memTapClass) +
753  "' with path '.taps[0]' contains target '" +
754  wireTargetStr +
755  "' that cannot be resolved.")
756  .attachNote()
757  << "The full Annotation is reproduced here: " << anno << "\n";
758 
759  auto combMem = dyn_cast<chirrtl::CombMemOp>(srcTarget->ref.getOp());
760  if (!combMem)
761  return srcTarget->ref.getOp()->emitOpError(
762  "unsupported operation, only CombMem can be used as the source of "
763  "MemTap");
764  if (!combMem.getType().getElementType().isGround())
765  return combMem.emitOpError(
766  "cannot generate MemTap to a memory with aggregate data type");
767  if (tapsAttr.size() != combMem.getType().getNumElements())
768  return mlir::emitError(
769  loc, "sink cannot specify more taps than the depth of the memory");
770  if (srcTarget->instances.empty()) {
771  auto path = state.instancePathCache.getAbsolutePaths(
772  combMem->getParentOfType<FModuleOp>());
773  if (path.size() > 1)
774  return combMem.emitOpError(
775  "cannot be resolved as source for MemTap, multiple paths from top "
776  "exist and unique instance cannot be resolved");
777  srcTarget->instances.append(path.back().begin(), path.back().end());
778  }
779 
780  ImplicitLocOpBuilder builder(combMem->getLoc(), combMem);
781 
782  // Lower memory taps to real ports on the memory. This is done if the taps
783  // are supposed to be synthesized.
784  if (state.noRefTypePorts) {
785  // Create new ports _after_ all other ports to avoid permuting existing
786  // ports.
787  builder.setInsertionPointToEnd(
788  combMem->getParentOfType<FModuleOp>().getBodyBlock());
789 
790  // Determine the clock to use for the debug ports. Error if the same clock
791  // is not used for all ports or if no clock port is found.
792  Value clock;
793  for (auto *portOp : combMem.getResult().getUsers()) {
794  for (auto result : portOp->getResults()) {
795  for (auto *user : result.getUsers()) {
796  auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
797  if (!accessOp)
798  continue;
799  auto newClock = accessOp.getClock();
800  if (clock && clock != newClock)
801  return combMem.emitOpError(
802  "has different clocks on different ports (this is ambiguous "
803  "when compiling without reference types)");
804  clock = newClock;
805  }
806  }
807  }
808  if (!clock)
809  return combMem.emitOpError(
810  "does not have an access port to determine a clock connection (this "
811  "is necessary when compiling without reference types)");
812 
813  // Add one port per memory address.
814  SmallVector<Value> data;
815  for (uint64_t i = 0, e = combMem.getType().getNumElements(); i != e; ++i) {
816  auto port = builder.create<chirrtl::MemoryPortOp>(
817  combMem.getType().getElementType(),
818  CMemoryPortType::get(builder.getContext()), combMem.getResult(),
819  MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
820  builder.getArrayAttr({}));
821  builder.create<chirrtl::MemoryPortAccessOp>(
822  port.getPort(), builder.create<ConstantOp>(APSInt::get(i)), clock);
823  data.push_back(port.getData());
824  }
825 
826  // Package up all the reads into a vector.
827  auto sendVal = builder.create<VectorCreateOp>(
828  FVectorType::get(combMem.getType().getElementType(),
829  combMem.getType().getNumElements()),
830  data);
831  auto sink = wireTarget->ref.getOp()->getResult(0);
832 
833  // Add a wiring problem to hook up the vector to the destination wire.
834  state.wiringProblems.push_back(
835  {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Never});
836  return success();
837  }
838 
839  // Normal memory handling. Create a debug port.
840  builder.setInsertionPointAfter(combMem);
841  // Construct the type for the debug port.
842  auto debugType = RefType::get(FVectorType::get(
843  combMem.getType().getElementType(), combMem.getType().getNumElements()));
844  Value memDbgPort =
845  builder
846  .create<chirrtl::MemoryDebugPortOp>(
847  debugType, combMem,
848  state.getNamespace(srcTarget->ref.getModule()).newName("memTap"))
849  .getResult();
850 
851  auto sendVal = memDbgPort;
852  if (wireTarget->ref.getOp()->getResult(0).getType() !=
853  type_cast<RefType>(sendVal.getType()).getType())
854  return wireTarget->ref.getOp()->emitError(
855  "cannot generate the MemTap, wiretap Type does not match the memory "
856  "type");
857  auto sink = wireTarget->ref.getOp()->getResult(0);
858  state.wiringProblems.push_back(
859  {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Prefer});
860  return success();
861 }
assert(baseType &&"element must be base type")
static FailureOr< unsigned > findVectorElement(Operation *op, Type type, StringRef indexStr)
Try to resolve an array index from a target given the type of the resolved target.
static Value lowerInternalPathAnno(AnnoPathValue &srcTarget, const AnnoPathValue &moduleTarget, const AnnoPathValue &target, StringAttr internalPathAttr, FIRRTLBaseType targetType, ApplyState &state)
static FailureOr< unsigned > findFieldID(AnnoTarget &ref, ArrayRef< TargetToken > tokens)
static FailureOr< unsigned > findBundleElement(Operation *op, Type type, StringRef field)
Try to resolve an non-array aggregate name from a target given the type and operation of the resolved...
static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &ref)
Builder builder
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition: Namespace.h:63
llvm::iterator_range< UseIterator > uses()
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
Direction
This represents the direction of a single port.
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
Definition: FIRRTLUtils.h:217
std::optional< AnnoPathValue > resolveEntities(TokenAnnoTarget path, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Convert a parsed target string to a resolved target structure.
constexpr const char * memTapClass
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc)
Definition: FIRRTLOps.cpp:271
constexpr const char * dataTapsClass
std::string canonicalizeTarget(StringRef target)
Return an input target string in canonical form.
InstanceOp addPortsToModule(FModuleLike mod, InstanceOp instOnPath, FIRRTLType portType, Direction dir, StringRef newName, InstancePathCache &instancePathcache, CircuitTargetCache *targetCaches=nullptr)
Add ports to the module and all its instances and return the clone for instOnPath.
LogicalResult applyGCTMemTaps(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
constexpr const char * internalKeyClass
constexpr const char * referenceKeyClass
Value getValueByFieldID(ImplicitLocOpBuilder builder, Value value, unsigned fieldID)
This gets the value targeted by a field id.
LogicalResult applyGCTDataTaps(const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state)
std::optional< AnnoPathValue > resolvePath(StringRef rawPath, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache)
Resolve a string path to a named item inside a circuit.
std::optional< TokenAnnoTarget > tokenizePath(StringRef origTarget)
Parse a FIRRTL annotation path into its constituent parts.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:24
::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
SmallVector< InstanceOp > instances
void gatherTargets(FModuleLike mod)
Walk the module and add named things to 'targets'.
An annotation target is used to keep track of something that is targeted by an Annotation.
FIRRTLType getType() const
Get the type of the target.
Operation * getOp() const
State threaded through functions for resolving and applying annotations.
DenseSet< InstanceOp > wiringProblemInstRefs
hw::InnerSymbolNamespace & getNamespace(FModuleLike module)
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
Cache AnnoTargets for a circuit's modules, walked as needed.
void replaceOp(Operation *oldOp, Operation *newOp)
Replace oldOp with newOp in the target cache.
AnnoTarget lookup(FModuleLike module, StringRef name)
Lookup the target for 'name' in 'module'.
HierPathCache(Operation *op, SymbolTable &symbolTable)
DenseMap< ArrayAttr, hw::HierPathOp > cache
hw::HierPathOp getOpFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
This represents an annotation targeting a specific port of a module, memory, or instance.
This holds the name and type that describes the module's ports.
The parsed annotation path.
SmallVector< TargetToken > component
std::string str() const
Convert the annotation path to a string.
void toVector(SmallVectorImpl< char > &out) const
Append the annotation path to the given SmallString or SmallVector.
SmallVector< std::pair< StringRef, StringRef > > instances
A data structure that caches and provides absolute paths to module instances in the IR.
ArrayRef< InstancePath > getAbsolutePaths(ModuleOpInterface op)
void replaceInstance(InstanceOpInterface oldOp, InstanceOpInterface newOp)
Replace an InstanceOp. This is required to keep the cache updated.
InstanceGraph & instanceGraph
The instance graph of the IR.