CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
21using namespace circt;
22using namespace firrtl;
23using namespace chirrtl;
24
25using llvm::StringRef;
26
27// Some types have been expanded so the first layer of aggregate path is
28// a return value.
29static 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.
43static 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.
62static 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
78static FailureOr<unsigned> findFieldID(AnnoTarget &ref,
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
117void 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
140std::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
166std::optional<AnnoPathValue>
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 (isa<PortAnnoTarget>(ref) && 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.
291std::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
331std::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
348InstanceOp 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
381void 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
394HierPathCache::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
405hw::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
420static 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.
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));
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
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 = dyn_cast_or_null<StringAttr>(tapsAttr[0]);
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 Type uintType = builder.getType<UIntType>();
816 for (uint64_t i = 0, e = combMem.getType().getNumElements(); i != e; ++i) {
817 auto port = builder.create<chirrtl::MemoryPortOp>(
818 combMem.getType().getElementType(),
819 CMemoryPortType::get(builder.getContext()), combMem.getResult(),
820 MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
821 builder.getArrayAttr({}));
822 builder.create<chirrtl::MemoryPortAccessOp>(
823 port.getPort(),
824 builder.create<ConstantOp>(uintType, APSInt::getUnsigned(i)), clock);
825 data.push_back(port.getData());
826 }
827
828 // Package up all the reads into a vector.
829 auto sendVal = builder.create<VectorCreateOp>(
830 FVectorType::get(combMem.getType().getElementType(),
831 combMem.getType().getNumElements()),
832 data);
833 auto sink = wireTarget->ref.getOp()->getResult(0);
834
835 // Add a wiring problem to hook up the vector to the destination wire.
836 state.wiringProblems.push_back(
837 {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Never});
838 return success();
839 }
840
841 // Normal memory handling. Create a debug port.
842 builder.setInsertionPointAfter(combMem);
843 // Construct the type for the debug port.
844 auto debugType = RefType::get(FVectorType::get(
845 combMem.getType().getElementType(), combMem.getType().getNumElements()));
846 Value memDbgPort =
847 builder
848 .create<chirrtl::MemoryDebugPortOp>(
849 debugType, combMem,
850 state.getNamespace(srcTarget->ref.getModule()).newName("memTap"))
851 .getResult();
852
853 auto sendVal = memDbgPort;
854 if (wireTarget->ref.getOp()->getResult(0).getType() !=
855 type_cast<RefType>(sendVal.getType()).getType())
856 return wireTarget->ref.getOp()->emitError(
857 "cannot generate the MemTap, wiretap Type does not match the memory "
858 "type");
859 auto sink = wireTarget->ref.getOp()->getResult(0);
860 state.wiringProblems.push_back(
861 {sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Prefer});
862 return success();
863}
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)
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:85
llvm::iterator_range< UseIterator > uses()
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction
This represents the direction of a single port.
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
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)
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.
::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
SmallVector< InstanceOp > instances
void gatherTargets(FModuleLike mod)
Walk the module and add named things to 'targets'.
void insertPort(FModuleLike mod, size_t portNo)
Add a new module port to the target cache.
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.
State threaded through functions for resolving and applying annotations.
DenseSet< InstanceOp > wiringProblemInstRefs
SmallVector< WiringProblem > wiringProblems
InstancePathCache & instancePathCache
hw::InnerSymbolNamespace & getNamespace(FModuleLike module)
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.