16 #include "mlir/IR/ImplicitLocOpBuilder.h"
17 #include "llvm/Support/Debug.h"
19 #define DEBUG_TYPE "lower-annos"
21 using namespace circt;
22 using namespace firrtl;
23 using namespace chirrtl;
25 using llvm::StringRef;
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) {
36 ref.
getOp()->emitError(
"Cannot find port with name ") << field;
45 auto bundle = type_dyn_cast<BundleType>(type);
47 op->emitError(
"field access '")
48 << field <<
"' into non-bundle type '" << type <<
"'";
51 auto idx = bundle.getElementIndex(field);
53 op->emitError(
"cannot resolve field '")
54 << field <<
"' in subtype '" << bundle <<
"'";
65 if (indexStr.getAsInteger(10, index)) {
66 op->emitError(
"Cannot convert '") << indexStr <<
"' to an integer";
69 auto vec = type_dyn_cast<FVectorType>(type);
71 op->emitError(
"index access '")
72 << index <<
"' into non-vector type '" << type <<
"'";
79 ArrayRef<TargetToken> tokens) {
83 auto *op = ref.
getOp();
89 tokens = tokens.drop_front();
94 return op->emitError(tokens.front().isIndex ?
"index" :
"field")
95 <<
" access in annotation not supported for this operation";
97 for (
auto token : tokens) {
102 auto vector = type_cast<FVectorType>(type);
103 type = vector.getElementType();
104 fieldIdx += vector.getFieldID(*result);
109 auto bundle = type_cast<BundleType>(type);
110 type = bundle.getElementType(*result);
111 fieldIdx += bundle.getFieldID(*result);
119 out.append(circuit.begin(), circuit.end());
121 for (
auto modInstPair : instances) {
122 out.append(modInstPair.first.begin(), modInstPair.first.end());
124 out.append(modInstPair.second.begin(), modInstPair.second.end());
127 out.append(module.begin(), module.end());
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());
147 if (target[0] ==
'~')
156 std::string newTarget = (
"~" + target).str();
157 auto n = newTarget.find(
'.');
158 if (n != std::string::npos)
160 n = newTarget.find(
'.');
161 if (n != std::string::npos)
166 std::optional<AnnoPathValue>
171 mlir::emitError(circuit.getLoc())
172 <<
"circuit name doesn't match annotation '" << path.
circuit <<
'\'';
176 if (path.
module.empty()) {
183 SmallVector<InstanceOp> instances;
185 auto mod = symTbl.lookup<FModuleOp>(p.first);
187 mlir::emitError(circuit.getLoc())
188 <<
"module doesn't exist '" << p.first <<
'\'';
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() <<
"'";
197 instances.push_back(cast<InstanceOp>(resolved.getOp()));
200 auto mod = symTbl.lookup<FModuleLike>(path.
module);
202 mlir::emitError(circuit.getLoc())
203 <<
"module doesn't exist '" << path.
module <<
'\'';
209 if (isa<ClassOp>(mod)) {
210 mlir::emitError(mod.getLoc()) <<
"annotations cannot target classes";
215 if (path.
name.empty()) {
221 mlir::emitError(circuit.getLoc()) <<
"cannot find name '" << path.
name
222 <<
"' in " << mod.getModuleName();
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();
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()) {
250 }
else if (component.front().isIndex) {
251 mlir::emitError(circuit.getLoc())
252 <<
"illegal target '" << path.
str() <<
"' indexes into an instance";
255 auto field = component.front().name;
257 for (
size_t p = 0, pe =
getNumPorts(target); p < pe; ++p)
258 if (target.getPortName(p) == field) {
263 mlir::emitError(circuit.getLoc())
264 <<
"cannot find port '" << field <<
"' in module "
265 << target.getModuleName();
269 if (isa<RefType>(ref.
getType())) {
270 mlir::emitError(circuit.getLoc())
271 <<
"annotation cannot target reference-type port '" << field
272 <<
"' in module " << target.getModuleName();
275 component = component.drop_front();
284 auto fieldIdx = *result;
293 if (origTarget.empty())
295 StringRef target = origTarget;
297 std::tie(retval.
circuit, target) = target.split(
'|');
300 while (target.count(
':')) {
302 std::tie(nla, target) = target.split(
':');
304 std::tie(mod, inst) = nla.split(
'/');
305 retval.
instances.emplace_back(mod, inst);
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});
336 StringRef path{pathStr};
340 mlir::emitError(circuit.getLoc())
341 <<
"Cannot tokenize annotation path " << rawPath;
354 InstanceOp clonedInstOnPath;
359 PortInfo portInfo = {portName, portType, dir, {}, mod.getLoc()};
360 mod.insertPorts({{portNo, portInfo}});
363 InstanceOp useInst = cast<InstanceOp>(use->getInstance());
364 auto clonedInst = useInst.cloneAndInsertPorts({{portNo, portInfo}});
365 if (useInst == instOnPath)
366 clonedInstOnPath = clonedInst;
370 targetCaches->
replaceOp(useInst, clonedInst);
371 useInst->replaceAllUsesWith(clonedInst.getResults().drop_back());
374 return clonedInstOnPath;
383 for (
const auto &p : llvm::enumerate(mod.getPorts()))
384 insertPort(mod, p.index());
387 mod.walk([&](Operation *op) { insertOp(op); });
395 : builder(OpBuilder::atBlockBegin(&op->getRegion(0).front())),
396 symbolTable(symbolTable) {
399 for (
auto ®ion : op->getRegions())
400 for (
auto &block : region.getBlocks())
401 for (
auto path : block.getOps<hw::HierPathOp>())
402 cache[path.getNamepathAttr()] = path;
406 auto &op =
cache[attr];
411 op.setVisibility(SymbolTable::Visibility::Private);
423 StringAttr internalPathAttr,
427 FModuleLike mod = cast<FModuleLike>(moduleTarget.
ref.
getOp());
428 InstanceOp modInstance;
430 modInstance = moduleTarget.
instances.back();
433 if (!node->hasOneUse()) {
435 "cannot be used for DataTaps, it is instantiated multiple times");
438 modInstance = cast<InstanceOp>((*node->uses().begin())->getInstance());
440 ImplicitLocOpBuilder builder(modInstance.getLoc(), modInstance);
441 builder.setInsertionPointAfter(modInstance);
443 SmallString<32> refName;
444 for (
auto c : internalPathAttr.getValue()) {
448 refName.push_back(
'_');
453 refName.push_back(c);
470 "cannot be used for both legacy Wiring and DataTaps simultaneously");
477 sendVal = modInstance.getResults().back();
480 .create<mlir::UnrealizedConversionCastOp>(sendVal.getType(), sendVal)
485 if (
auto extMod = dyn_cast<FExtModuleOp>((Operation *)mod)) {
488 SmallVector<Attribute> paths;
489 if (
auto internalPaths = extMod.getInternalPaths())
490 llvm::append_range(paths, internalPaths->getValue());
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());
510 srcTarget.
instances.append(path.begin(), path.end());
536 auto *context = state.
circuit.getContext();
537 auto loc = state.
circuit.getLoc();
540 auto keyAttr = tryGetAs<ArrayAttr>(anno, anno,
"keys", loc,
dataTapsClass);
543 for (
size_t i = 0, e = keyAttr.size(); i != e; ++i) {
545 auto path = (
"keys[" + Twine(i) +
"]").str();
546 auto bDict = cast<DictionaryAttr>(b);
548 tryGetAs<StringAttr>(bDict, anno,
"class", loc,
dataTapsClass, path);
554 return mlir::emitError(loc,
"Annotation '" + Twine(
dataTapsClass) +
556 (Twine(path) +
".class") +
557 "' contained an unknown/unimplemented "
558 "DataTapKey class '" +
559 classAttr.getValue() +
"'.")
561 <<
"The full Annotation is reproduced here: " << anno <<
"\n";
564 tryGetAs<StringAttr>(bDict, anno,
"sink", loc,
dataTapsClass, path);
565 std::string wirePathStr;
568 if (!wirePathStr.empty())
571 std::optional<AnnoPathValue> wireTarget;
572 if (!wirePathStr.empty())
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) +
582 (Twine(path) +
".class") +
583 "' cannot specify a port for sink.");
586 context, wirePathStr.substr(wirePathStr.find_last_of(
'>') + 1));
587 std::optional<AnnoPathValue> srcTarget;
595 auto internalPathAttr = tryGetAs<StringAttr>(bDict, anno,
"internalPath",
598 tryGetAs<StringAttr>(bDict, anno,
"module", loc,
dataTapsClass, path);
599 if (!internalPathAttr || !moduleAttr)
604 std::optional<AnnoPathValue> moduleTarget =
resolvePath(
610 firrtl::type_cast<FIRRTLBaseType>(wireTarget->ref.getType());
611 if (wireTarget->fieldIdx)
612 targetType = firrtl::type_cast<FIRRTLBaseType>(
614 wireTarget->fieldIdx));
616 internalPathAttr, targetType, state);
619 srcTarget = internalPathSrc;
623 tryGetAs<StringAttr>(bDict, anno,
"source", loc,
dataTapsClass, path);
629 LLVM_DEBUG(llvm::dbgs() <<
"\n Drill xmr path from :" << sourcePathStr
630 <<
" to " << wirePathStr);
635 return mlir::emitError(loc,
"Annotation '" + Twine(
dataTapsClass) +
636 "' source path could not be resolved.");
639 cast<FModuleOp>(wireTarget->ref.getModule().getOperation());
641 if (
auto extMod = dyn_cast<FExtModuleOp>(srcTarget->ref.getOp())) {
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);
650 auto node = builder.create<NodeOp>(lastInst.getResult(portNo));
659 dyn_cast<FModuleOp>(srcTarget->ref.getModule().getOperation());
661 ImplicitLocOpBuilder sendBuilder(srcModule.getLoc(), srcModule);
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());
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());
685 auto valType =
getBaseType(type_cast<FIRRTLType>(sendVal.getType()));
687 wireTarget->fieldIdx);
691 if (valType.isResetType() &&
692 valType.getWidthlessType() != wireType.getWidthlessType()) {
694 auto addWireWithCast = [&](
auto createCast) {
696 sinkBuilder.create<WireOp>(valType, tapName.getValue()).getResult();
700 if (isa<IntType>(wireType))
702 [&](
auto v) {
return sinkBuilder.create<AsUIntPrimOp>(v); });
703 else if (isa<AsyncResetType>(wireType))
705 [&](
auto v) {
return sinkBuilder.create<AsAsyncResetPrimOp>(v); });
718 auto loc = state.
circuit.getLoc();
721 tryGetAs<StringAttr>(anno, anno,
"source", loc,
memTapClass);
726 std::optional<AnnoPathValue> srcTarget =
resolvePath(
729 return mlir::emitError(loc,
"cannot resolve source target path '")
730 << sourceTargetStr <<
"'";
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");
736 auto tap = dyn_cast_or_null<StringAttr>(tapsAttr[0]);
738 return mlir::emitError(
740 "' with path '.taps[0" +
741 "]' contained an unexpected type (expected a string).")
743 <<
"The full Annotation is reprodcued here: " << anno <<
"\n";
749 std::optional<AnnoPathValue> wireTarget =
resolvePath(
752 return mlir::emitError(loc,
"Annotation '" + Twine(
memTapClass) +
753 "' with path '.taps[0]' contains target '" +
755 "' that cannot be resolved.")
757 <<
"The full Annotation is reproduced here: " << anno <<
"\n";
759 auto combMem = dyn_cast<chirrtl::CombMemOp>(srcTarget->ref.getOp());
761 return srcTarget->ref.getOp()->emitOpError(
762 "unsupported operation, only CombMem can be used as the source of "
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()) {
772 combMem->getParentOfType<FModuleOp>());
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());
780 ImplicitLocOpBuilder builder(combMem->getLoc(), combMem);
787 builder.setInsertionPointToEnd(
788 combMem->getParentOfType<FModuleOp>().getBodyBlock());
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);
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)");
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)");
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(),
820 MemDirAttr::Read, builder.getStringAttr(
"memTap_" + Twine(i)),
821 builder.getArrayAttr({}));
822 builder.create<chirrtl::MemoryPortAccessOp>(
824 builder.create<ConstantOp>(uintType, APSInt::getUnsigned(i)), clock);
825 data.push_back(port.getData());
829 auto sendVal = builder.create<VectorCreateOp>(
831 combMem.getType().getNumElements()),
833 auto sink = wireTarget->ref.getOp()->getResult(0);
842 builder.setInsertionPointAfter(combMem);
845 combMem.getType().getElementType(), combMem.getType().getNumElements()));
848 .create<chirrtl::MemoryDebugPortOp>(
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 "
859 auto sink = wireTarget->ref.getOp()->getResult(0);
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.
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.
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'.
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
CircuitTargetCache targetCaches
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)
SymbolTable & symbolTable
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.