mirror of https://github.com/llvm/circt.git
862 lines
34 KiB
C++
862 lines
34 KiB
C++
//===- FIRRTLAnnotationHelper.cpp - FIRRTL Annotation Lookup ----*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements helpers mapping annotations to operations.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h"
|
|
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
|
|
#include "mlir/IR/ImplicitLocOpBuilder.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "lower-annos"
|
|
|
|
using namespace circt;
|
|
using namespace firrtl;
|
|
using namespace chirrtl;
|
|
|
|
using llvm::StringRef;
|
|
|
|
// Some types have been expanded so the first layer of aggregate path is
|
|
// a return value.
|
|
static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &ref) {
|
|
if (auto mem = dyn_cast<MemOp>(ref.getOp()))
|
|
for (size_t p = 0, pe = mem.getPortNames().size(); p < pe; ++p)
|
|
if (mem.getPortNameStr(p) == field) {
|
|
ref = PortAnnoTarget(mem, p);
|
|
return success();
|
|
}
|
|
ref.getOp()->emitError("Cannot find port with name ") << field;
|
|
return failure();
|
|
}
|
|
|
|
/// Try to resolve an non-array aggregate name from a target given the type and
|
|
/// operation of the resolved target. This needs to deal with places where we
|
|
/// represent bundle returns as split into constituent parts.
|
|
static FailureOr<unsigned> findBundleElement(Operation *op, Type type,
|
|
StringRef field) {
|
|
auto bundle = type_dyn_cast<BundleType>(type);
|
|
if (!bundle) {
|
|
op->emitError("field access '")
|
|
<< field << "' into non-bundle type '" << type << "'";
|
|
return failure();
|
|
}
|
|
auto idx = bundle.getElementIndex(field);
|
|
if (!idx) {
|
|
op->emitError("cannot resolve field '")
|
|
<< field << "' in subtype '" << bundle << "'";
|
|
return failure();
|
|
}
|
|
return *idx;
|
|
}
|
|
|
|
/// Try to resolve an array index from a target given the type of the resolved
|
|
/// target.
|
|
static FailureOr<unsigned> findVectorElement(Operation *op, Type type,
|
|
StringRef indexStr) {
|
|
size_t index;
|
|
if (indexStr.getAsInteger(10, index)) {
|
|
op->emitError("Cannot convert '") << indexStr << "' to an integer";
|
|
return failure();
|
|
}
|
|
auto vec = type_dyn_cast<FVectorType>(type);
|
|
if (!vec) {
|
|
op->emitError("index access '")
|
|
<< index << "' into non-vector type '" << type << "'";
|
|
return failure();
|
|
}
|
|
return index;
|
|
}
|
|
|
|
static FailureOr<unsigned> findFieldID(AnnoTarget &ref,
|
|
ArrayRef<TargetToken> tokens) {
|
|
if (tokens.empty())
|
|
return 0;
|
|
|
|
auto *op = ref.getOp();
|
|
auto fieldIdx = 0;
|
|
// The first field for some ops refers to expanded return values.
|
|
if (isa<MemOp>(op)) {
|
|
if (failed(updateExpandedPort(tokens.front().name, ref)))
|
|
return {};
|
|
tokens = tokens.drop_front();
|
|
}
|
|
|
|
auto type = ref.getType();
|
|
if (!type)
|
|
return op->emitError(tokens.front().isIndex ? "index" : "field")
|
|
<< " access in annotation not supported for this operation";
|
|
|
|
for (auto token : tokens) {
|
|
if (token.isIndex) {
|
|
auto result = findVectorElement(op, type, token.name);
|
|
if (failed(result))
|
|
return failure();
|
|
auto vector = type_cast<FVectorType>(type);
|
|
type = vector.getElementType();
|
|
fieldIdx += vector.getFieldID(*result);
|
|
} else {
|
|
auto result = findBundleElement(op, type, token.name);
|
|
if (failed(result))
|
|
return failure();
|
|
auto bundle = type_cast<BundleType>(type);
|
|
type = bundle.getElementType(*result);
|
|
fieldIdx += bundle.getFieldID(*result);
|
|
}
|
|
}
|
|
return fieldIdx;
|
|
}
|
|
|
|
void TokenAnnoTarget::toVector(SmallVectorImpl<char> &out) const {
|
|
out.push_back('~');
|
|
out.append(circuit.begin(), circuit.end());
|
|
out.push_back('|');
|
|
for (auto modInstPair : instances) {
|
|
out.append(modInstPair.first.begin(), modInstPair.first.end());
|
|
out.push_back('/');
|
|
out.append(modInstPair.second.begin(), modInstPair.second.end());
|
|
out.push_back(':');
|
|
}
|
|
out.append(module.begin(), module.end());
|
|
if (name.empty())
|
|
return;
|
|
out.push_back('>');
|
|
out.append(name.begin(), name.end());
|
|
for (auto comp : component) {
|
|
out.push_back(comp.isIndex ? '[' : '.');
|
|
out.append(comp.name.begin(), comp.name.end());
|
|
if (comp.isIndex)
|
|
out.push_back(']');
|
|
}
|
|
}
|
|
|
|
std::string firrtl::canonicalizeTarget(StringRef target) {
|
|
|
|
if (target.empty())
|
|
return target.str();
|
|
|
|
// If this is a normal Target (not a Named), erase that field in the JSON
|
|
// object and return that Target.
|
|
if (target[0] == '~')
|
|
return target.str();
|
|
|
|
// This is a legacy target using the firrtl.annotations.Named type. This
|
|
// can be trivially canonicalized to a non-legacy target, so we do it with
|
|
// the following three mappings:
|
|
// 1. CircuitName => CircuitTarget, e.g., A -> ~A
|
|
// 2. ModuleName => ModuleTarget, e.g., A.B -> ~A|B
|
|
// 3. ComponentName => ReferenceTarget, e.g., A.B.C -> ~A|B>C
|
|
std::string newTarget = ("~" + target).str();
|
|
auto n = newTarget.find('.');
|
|
if (n != std::string::npos)
|
|
newTarget[n] = '|';
|
|
n = newTarget.find('.');
|
|
if (n != std::string::npos)
|
|
newTarget[n] = '>';
|
|
return newTarget;
|
|
}
|
|
|
|
std::optional<AnnoPathValue>
|
|
firrtl::resolveEntities(TokenAnnoTarget path, CircuitOp circuit,
|
|
SymbolTable &symTbl, CircuitTargetCache &cache) {
|
|
// Validate circuit name.
|
|
if (!path.circuit.empty() && circuit.getName() != path.circuit) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "circuit name doesn't match annotation '" << path.circuit << '\'';
|
|
return {};
|
|
}
|
|
// Circuit only target.
|
|
if (path.module.empty()) {
|
|
assert(path.name.empty() && path.instances.empty() &&
|
|
path.component.empty());
|
|
return AnnoPathValue(circuit);
|
|
}
|
|
|
|
// Resolve all instances for non-local paths.
|
|
SmallVector<InstanceOp> instances;
|
|
for (auto p : path.instances) {
|
|
auto mod = symTbl.lookup<FModuleOp>(p.first);
|
|
if (!mod) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "module doesn't exist '" << p.first << '\'';
|
|
return {};
|
|
}
|
|
auto resolved = cache.lookup(mod, p.second);
|
|
if (!resolved || !isa<InstanceOp>(resolved.getOp())) {
|
|
mlir::emitError(circuit.getLoc()) << "cannot find instance '" << p.second
|
|
<< "' in '" << mod.getName() << "'";
|
|
return {};
|
|
}
|
|
instances.push_back(cast<InstanceOp>(resolved.getOp()));
|
|
}
|
|
// The final module is where the named target is (or is the named target).
|
|
auto mod = symTbl.lookup<FModuleLike>(path.module);
|
|
if (!mod) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "module doesn't exist '" << path.module << '\'';
|
|
return {};
|
|
}
|
|
|
|
// ClassOps may not participate in annotation targeting. Neither the class
|
|
// itself, nor any "named thing" defined under it, may be targeted by an anno.
|
|
if (isa<ClassOp>(mod)) {
|
|
mlir::emitError(mod.getLoc()) << "annotations cannot target classes";
|
|
return {};
|
|
}
|
|
|
|
AnnoTarget ref;
|
|
if (path.name.empty()) {
|
|
assert(path.component.empty());
|
|
ref = OpAnnoTarget(mod);
|
|
} else {
|
|
ref = cache.lookup(mod, path.name);
|
|
if (!ref) {
|
|
mlir::emitError(circuit.getLoc()) << "cannot find name '" << path.name
|
|
<< "' in " << mod.getModuleName();
|
|
return {};
|
|
}
|
|
// AnnoTarget::getType() is not safe (CHIRRTL ops crash, null if instance),
|
|
// avoid. For now, only references in ports can be targets, check that.
|
|
// TODO: containsReference().
|
|
if (ref.isa<PortAnnoTarget>() && isa<RefType>(ref.getType())) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "cannot target reference-type '" << path.name << "' in "
|
|
<< mod.getModuleName();
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// If the reference is pointing to an instance op, we have to move the target
|
|
// to the module. This is done both because it is logical to have one
|
|
// representation (this effectively canonicalizes a reference target on an
|
|
// instance into an instance target) and because the SFC has a pass that does
|
|
// this conversion. E.g., this is converting (where "bar" is an instance):
|
|
// ~Foo|Foo>bar
|
|
// Into:
|
|
// ~Foo|Foo/bar:Bar
|
|
ArrayRef<TargetToken> component(path.component);
|
|
if (auto instance = dyn_cast<InstanceOp>(ref.getOp())) {
|
|
instances.push_back(instance);
|
|
auto target = cast<FModuleLike>(instance.getReferencedOperation(symTbl));
|
|
if (component.empty()) {
|
|
ref = OpAnnoTarget(target);
|
|
} else if (component.front().isIndex) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "illegal target '" << path.str() << "' indexes into an instance";
|
|
return {};
|
|
} else {
|
|
auto field = component.front().name;
|
|
ref = AnnoTarget();
|
|
for (size_t p = 0, pe = getNumPorts(target); p < pe; ++p)
|
|
if (target.getPortName(p) == field) {
|
|
ref = PortAnnoTarget(target, p);
|
|
break;
|
|
}
|
|
if (!ref) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "cannot find port '" << field << "' in module "
|
|
<< target.getModuleName();
|
|
return {};
|
|
}
|
|
// TODO: containsReference().
|
|
if (isa<RefType>(ref.getType())) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "annotation cannot target reference-type port '" << field
|
|
<< "' in module " << target.getModuleName();
|
|
return {};
|
|
}
|
|
component = component.drop_front();
|
|
}
|
|
}
|
|
|
|
// If we have aggregate specifiers, resolve those now. This call can update
|
|
// the ref to target a port of a memory.
|
|
auto result = findFieldID(ref, component);
|
|
if (failed(result))
|
|
return {};
|
|
auto fieldIdx = *result;
|
|
|
|
return AnnoPathValue(instances, ref, fieldIdx);
|
|
}
|
|
|
|
/// split a target string into it constituent parts. This is the primary parser
|
|
/// for targets.
|
|
std::optional<TokenAnnoTarget> firrtl::tokenizePath(StringRef origTarget) {
|
|
// An empty string is not a legal target.
|
|
if (origTarget.empty())
|
|
return {};
|
|
StringRef target = origTarget;
|
|
TokenAnnoTarget retval;
|
|
std::tie(retval.circuit, target) = target.split('|');
|
|
if (!retval.circuit.empty() && retval.circuit[0] == '~')
|
|
retval.circuit = retval.circuit.drop_front();
|
|
while (target.count(':')) {
|
|
StringRef nla;
|
|
std::tie(nla, target) = target.split(':');
|
|
StringRef inst, mod;
|
|
std::tie(mod, inst) = nla.split('/');
|
|
retval.instances.emplace_back(mod, inst);
|
|
}
|
|
// remove aggregate
|
|
auto targetBase =
|
|
target.take_until([](char c) { return c == '.' || c == '['; });
|
|
auto aggBase = target.drop_front(targetBase.size());
|
|
std::tie(retval.module, retval.name) = targetBase.split('>');
|
|
while (!aggBase.empty()) {
|
|
if (aggBase[0] == '.') {
|
|
aggBase = aggBase.drop_front();
|
|
StringRef field = aggBase.take_front(aggBase.find_first_of("[."));
|
|
aggBase = aggBase.drop_front(field.size());
|
|
retval.component.push_back({field, false});
|
|
} else if (aggBase[0] == '[') {
|
|
aggBase = aggBase.drop_front();
|
|
StringRef index = aggBase.take_front(aggBase.find_first_of(']'));
|
|
aggBase = aggBase.drop_front(index.size() + 1);
|
|
retval.component.push_back({index, true});
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
std::optional<AnnoPathValue> firrtl::resolvePath(StringRef rawPath,
|
|
CircuitOp circuit,
|
|
SymbolTable &symTbl,
|
|
CircuitTargetCache &cache) {
|
|
auto pathStr = canonicalizeTarget(rawPath);
|
|
StringRef path{pathStr};
|
|
|
|
auto tokens = tokenizePath(path);
|
|
if (!tokens) {
|
|
mlir::emitError(circuit.getLoc())
|
|
<< "Cannot tokenize annotation path " << rawPath;
|
|
return {};
|
|
}
|
|
|
|
return resolveEntities(*tokens, circuit, symTbl, cache);
|
|
}
|
|
|
|
InstanceOp firrtl::addPortsToModule(FModuleLike mod, InstanceOp instOnPath,
|
|
FIRRTLType portType, Direction dir,
|
|
StringRef newName,
|
|
InstancePathCache &instancePathcache,
|
|
CircuitTargetCache *targetCaches) {
|
|
// To store the cloned version of `instOnPath`.
|
|
InstanceOp clonedInstOnPath;
|
|
// Get a new port name from the Namespace.
|
|
auto portName = StringAttr::get(mod.getContext(), newName);
|
|
// The port number for the new port.
|
|
unsigned portNo = getNumPorts(mod);
|
|
PortInfo portInfo = {portName, portType, dir, {}, mod.getLoc()};
|
|
mod.insertPorts({{portNo, portInfo}});
|
|
// Now update all the instances of `mod`.
|
|
for (auto *use : instancePathcache.instanceGraph.lookup(mod)->uses()) {
|
|
InstanceOp useInst = cast<InstanceOp>(use->getInstance());
|
|
auto clonedInst = useInst.cloneAndInsertPorts({{portNo, portInfo}});
|
|
if (useInst == instOnPath)
|
|
clonedInstOnPath = clonedInst;
|
|
// Update all occurences of old instance.
|
|
instancePathcache.replaceInstance(useInst, clonedInst);
|
|
if (targetCaches)
|
|
targetCaches->replaceOp(useInst, clonedInst);
|
|
useInst->replaceAllUsesWith(clonedInst.getResults().drop_back());
|
|
useInst->erase();
|
|
}
|
|
return clonedInstOnPath;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// AnnoTargetCache
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void AnnoTargetCache::gatherTargets(FModuleLike mod) {
|
|
// Add ports
|
|
for (const auto &p : llvm::enumerate(mod.getPorts()))
|
|
insertPort(mod, p.index());
|
|
|
|
// And named things
|
|
mod.walk([&](Operation *op) { insertOp(op); });
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// HierPathOpCache
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
HierPathCache::HierPathCache(Operation *op, SymbolTable &symbolTable)
|
|
: builder(OpBuilder::atBlockBegin(&op->getRegion(0).front())),
|
|
symbolTable(symbolTable) {
|
|
|
|
// Populate the cache with any symbols preexisting.
|
|
for (auto ®ion : op->getRegions())
|
|
for (auto &block : region.getBlocks())
|
|
for (auto path : block.getOps<hw::HierPathOp>())
|
|
cache[path.getNamepathAttr()] = path;
|
|
}
|
|
|
|
hw::HierPathOp HierPathCache::getOpFor(ArrayAttr attr) {
|
|
auto &op = cache[attr];
|
|
if (!op) {
|
|
op = builder.create<hw::HierPathOp>(UnknownLoc::get(builder.getContext()),
|
|
"nla", attr);
|
|
symbolTable.insert(op);
|
|
op.setVisibility(SymbolTable::Visibility::Private);
|
|
}
|
|
return op;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Code related to handling Grand Central Data/Mem Taps annotations
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static Value lowerInternalPathAnno(AnnoPathValue &srcTarget,
|
|
const AnnoPathValue &moduleTarget,
|
|
const AnnoPathValue &target,
|
|
StringAttr internalPathAttr,
|
|
FIRRTLBaseType targetType,
|
|
ApplyState &state) {
|
|
Value sendVal;
|
|
FModuleLike mod = cast<FModuleLike>(moduleTarget.ref.getOp());
|
|
InstanceOp modInstance;
|
|
if (!moduleTarget.instances.empty()) {
|
|
modInstance = moduleTarget.instances.back();
|
|
} else {
|
|
auto *node = state.instancePathCache.instanceGraph.lookup(mod);
|
|
if (!node->hasOneUse()) {
|
|
mod->emitOpError(
|
|
"cannot be used for DataTaps, it is instantiated multiple times");
|
|
return nullptr;
|
|
}
|
|
modInstance = cast<InstanceOp>((*node->uses().begin())->getInstance());
|
|
}
|
|
ImplicitLocOpBuilder builder(modInstance.getLoc(), modInstance);
|
|
builder.setInsertionPointAfter(modInstance);
|
|
auto portRefType = RefType::get(targetType);
|
|
SmallString<32> refName;
|
|
for (auto c : internalPathAttr.getValue()) {
|
|
switch (c) {
|
|
case '.':
|
|
case '[':
|
|
refName.push_back('_');
|
|
break;
|
|
case ']':
|
|
break;
|
|
default:
|
|
refName.push_back(c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Add RefType ports corresponding to this "internalPath" to the external
|
|
// module. This also updates all the instances of the external module.
|
|
// This removes and replaces the instance, and returns the updated
|
|
// instance.
|
|
if (!state.wiringProblemInstRefs.contains(modInstance)) {
|
|
modInstance =
|
|
addPortsToModule(mod, modInstance, portRefType, Direction::Out, refName,
|
|
state.instancePathCache, &state.targetCaches);
|
|
} else {
|
|
// As a current limitation, mixing legacy Wiring and Data Taps is forbidden
|
|
// to prevent invalidating Values used later
|
|
mod->emitOpError(
|
|
"cannot be used for both legacy Wiring and DataTaps simultaneously");
|
|
return nullptr;
|
|
}
|
|
|
|
// Since the instance op generates the RefType output, no need of another
|
|
// RefSendOp. Store into an op to ensure we have stable reference,
|
|
// so future tapping won't invalidate this Value.
|
|
sendVal = modInstance.getResults().back();
|
|
sendVal =
|
|
builder
|
|
.create<mlir::UnrealizedConversionCastOp>(sendVal.getType(), sendVal)
|
|
->getResult(0);
|
|
|
|
// Now set the instance as the source for the final datatap xmr.
|
|
srcTarget = AnnoPathValue(modInstance);
|
|
if (auto extMod = dyn_cast<FExtModuleOp>((Operation *)mod)) {
|
|
// Set the internal path for the new port, creating the paths array
|
|
// if not already present.
|
|
SmallVector<Attribute> paths;
|
|
if (auto internalPaths = extMod.getInternalPaths())
|
|
llvm::append_range(paths, internalPaths->getValue());
|
|
else
|
|
paths.resize(extMod.getNumPorts(), builder.getAttr<InternalPathAttr>());
|
|
paths.back() = builder.getAttr<InternalPathAttr>(internalPathAttr);
|
|
extMod.setInternalPathsAttr(builder.getArrayAttr(paths));
|
|
} else if (auto intMod = dyn_cast<FModuleOp>((Operation *)mod)) {
|
|
auto builder = ImplicitLocOpBuilder::atBlockEnd(
|
|
intMod.getLoc(), &intMod.getBody().getBlocks().back());
|
|
auto pathStr = builder.create<VerbatimExprOp>(
|
|
portRefType.getType(), internalPathAttr.getValue(), ValueRange{});
|
|
auto sendPath = builder.create<RefSendOp>(pathStr);
|
|
emitConnect(builder, intMod.getArguments().back(), sendPath.getResult());
|
|
}
|
|
|
|
if (!moduleTarget.instances.empty())
|
|
srcTarget.instances = moduleTarget.instances;
|
|
else {
|
|
auto path = state.instancePathCache
|
|
.getAbsolutePaths(modInstance->getParentOfType<FModuleOp>())
|
|
.back();
|
|
srcTarget.instances.append(path.begin(), path.end());
|
|
}
|
|
return sendVal;
|
|
}
|
|
|
|
// Describes tap points into the design. This has the following structure:
|
|
// keys: Seq[DataTapKey]
|
|
// DataTapKey has multiple implementations:
|
|
// - ReferenceDataTapKey: (tapping a point which exists in the FIRRTL)
|
|
// sink: ReferenceTarget
|
|
// source: ReferenceTarget
|
|
// - DataTapModuleSignalKey: (tapping a point, by name, in a blackbox)
|
|
// module: IsModule
|
|
// internalPath: String
|
|
// sink: ReferenceTarget
|
|
// - DeletedDataTapKey: (not implemented here)
|
|
// sink: ReferenceTarget
|
|
// - LiteralDataTapKey: (not implemented here)
|
|
// literal: Literal
|
|
// sink: ReferenceTarget
|
|
// A Literal is a FIRRTL IR literal serialized to a string. For now, just
|
|
// store the string.
|
|
// TODO: Parse the literal string into a UInt or SInt literal.
|
|
LogicalResult circt::firrtl::applyGCTDataTaps(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *context = state.circuit.getContext();
|
|
auto loc = state.circuit.getLoc();
|
|
|
|
// Process all the taps.
|
|
auto keyAttr = tryGetAs<ArrayAttr>(anno, anno, "keys", loc, dataTapsClass);
|
|
if (!keyAttr)
|
|
return failure();
|
|
for (size_t i = 0, e = keyAttr.size(); i != e; ++i) {
|
|
auto b = keyAttr[i];
|
|
auto path = ("keys[" + Twine(i) + "]").str();
|
|
auto bDict = cast<DictionaryAttr>(b);
|
|
auto classAttr =
|
|
tryGetAs<StringAttr>(bDict, anno, "class", loc, dataTapsClass, path);
|
|
if (!classAttr)
|
|
return failure();
|
|
// Can only handle ReferenceDataTapKey and DataTapModuleSignalKey
|
|
if (classAttr.getValue() != referenceKeyClass &&
|
|
classAttr.getValue() != internalKeyClass)
|
|
return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
|
|
"' with path '" +
|
|
(Twine(path) + ".class") +
|
|
"' contained an unknown/unimplemented "
|
|
"DataTapKey class '" +
|
|
classAttr.getValue() + "'.")
|
|
.attachNote()
|
|
<< "The full Annotation is reproduced here: " << anno << "\n";
|
|
|
|
auto sinkNameAttr =
|
|
tryGetAs<StringAttr>(bDict, anno, "sink", loc, dataTapsClass, path);
|
|
std::string wirePathStr;
|
|
if (sinkNameAttr)
|
|
wirePathStr = canonicalizeTarget(sinkNameAttr.getValue());
|
|
if (!wirePathStr.empty())
|
|
if (!tokenizePath(wirePathStr))
|
|
wirePathStr.clear();
|
|
std::optional<AnnoPathValue> wireTarget;
|
|
if (!wirePathStr.empty())
|
|
wireTarget = resolvePath(wirePathStr, state.circuit, state.symTbl,
|
|
state.targetCaches);
|
|
if (!wireTarget)
|
|
return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
|
|
"' with wire path '" + wirePathStr +
|
|
"' could not be resolved.");
|
|
if (!wireTarget->ref.getImpl().isOp())
|
|
return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
|
|
"' with path '" +
|
|
(Twine(path) + ".class") +
|
|
"' cannot specify a port for sink.");
|
|
// Extract the name of the wire, used for datatap.
|
|
auto tapName = StringAttr::get(
|
|
context, wirePathStr.substr(wirePathStr.find_last_of('>') + 1));
|
|
std::optional<AnnoPathValue> srcTarget;
|
|
Value sendVal;
|
|
if (classAttr.getValue() == internalKeyClass) {
|
|
// For DataTapModuleSignalKey, the source is encoded as a string, that
|
|
// should exist inside the specified module. This source string is used as
|
|
// a suffix to the instance name for the module inside a VerbatimExprOp.
|
|
// This verbatim represents an intermediate xmr, which is then used by a
|
|
// ref.send to be read remotely.
|
|
auto internalPathAttr = tryGetAs<StringAttr>(bDict, anno, "internalPath",
|
|
loc, dataTapsClass, path);
|
|
auto moduleAttr =
|
|
tryGetAs<StringAttr>(bDict, anno, "module", loc, dataTapsClass, path);
|
|
if (!internalPathAttr || !moduleAttr)
|
|
return failure();
|
|
auto moduleTargetStr = canonicalizeTarget(moduleAttr.getValue());
|
|
if (!tokenizePath(moduleTargetStr))
|
|
return failure();
|
|
std::optional<AnnoPathValue> moduleTarget = resolvePath(
|
|
moduleTargetStr, state.circuit, state.symTbl, state.targetCaches);
|
|
if (!moduleTarget)
|
|
return failure();
|
|
AnnoPathValue internalPathSrc;
|
|
auto targetType =
|
|
firrtl::type_cast<FIRRTLBaseType>(wireTarget->ref.getType());
|
|
if (wireTarget->fieldIdx)
|
|
targetType = firrtl::type_cast<FIRRTLBaseType>(
|
|
hw::FieldIdImpl::getFinalTypeByFieldID(targetType,
|
|
wireTarget->fieldIdx));
|
|
sendVal = lowerInternalPathAnno(internalPathSrc, *moduleTarget, target,
|
|
internalPathAttr, targetType, state);
|
|
if (!sendVal)
|
|
return failure();
|
|
srcTarget = internalPathSrc;
|
|
} else {
|
|
// Now handle ReferenceDataTapKey. Get the source from annotation.
|
|
auto sourceAttr =
|
|
tryGetAs<StringAttr>(bDict, anno, "source", loc, dataTapsClass, path);
|
|
if (!sourceAttr)
|
|
return failure();
|
|
auto sourcePathStr = canonicalizeTarget(sourceAttr.getValue());
|
|
if (!tokenizePath(sourcePathStr))
|
|
return failure();
|
|
LLVM_DEBUG(llvm::dbgs() << "\n Drill xmr path from :" << sourcePathStr
|
|
<< " to " << wirePathStr);
|
|
srcTarget = resolvePath(sourcePathStr, state.circuit, state.symTbl,
|
|
state.targetCaches);
|
|
}
|
|
if (!srcTarget)
|
|
return mlir::emitError(loc, "Annotation '" + Twine(dataTapsClass) +
|
|
"' source path could not be resolved.");
|
|
|
|
auto wireModule =
|
|
cast<FModuleOp>(wireTarget->ref.getModule().getOperation());
|
|
|
|
if (auto extMod = dyn_cast<FExtModuleOp>(srcTarget->ref.getOp())) {
|
|
// If the source is a port on extern module, then move the source to the
|
|
// instance port for the ext module.
|
|
auto portNo = srcTarget->ref.getImpl().getPortNo();
|
|
auto lastInst = srcTarget->instances.pop_back_val();
|
|
auto builder = ImplicitLocOpBuilder::atBlockEnd(lastInst.getLoc(),
|
|
lastInst->getBlock());
|
|
builder.setInsertionPointAfter(lastInst);
|
|
// Instance port cannot be used as an annotation target, so use a NodeOp.
|
|
auto node = builder.create<NodeOp>(lastInst.getResult(portNo));
|
|
AnnotationSet::addDontTouch(node);
|
|
srcTarget->ref = AnnoTarget(circt::firrtl::detail::AnnoTargetImpl(node));
|
|
}
|
|
|
|
// The RefSend value can be either generated by the instance of an external
|
|
// module or a RefSendOp.
|
|
if (!sendVal) {
|
|
auto srcModule =
|
|
dyn_cast<FModuleOp>(srcTarget->ref.getModule().getOperation());
|
|
|
|
ImplicitLocOpBuilder sendBuilder(srcModule.getLoc(), srcModule);
|
|
// Set the insertion point for the RefSend, it should be dominated by the
|
|
// srcTarget value. If srcTarget is a port, then insert the RefSend
|
|
// at the beggining of the module, else define the RefSend at the end of
|
|
// the block that contains the srcTarget Op.
|
|
if (srcTarget->ref.getImpl().isOp()) {
|
|
sendVal = srcTarget->ref.getImpl().getOp()->getResult(0);
|
|
sendBuilder.setInsertionPointAfter(srcTarget->ref.getOp());
|
|
} else if (srcTarget->ref.getImpl().isPort()) {
|
|
sendVal = srcModule.getArgument(srcTarget->ref.getImpl().getPortNo());
|
|
sendBuilder.setInsertionPointToStart(srcModule.getBodyBlock());
|
|
}
|
|
// If the target value is a field of an aggregate create the
|
|
// subfield/subaccess into it.
|
|
sendVal = getValueByFieldID(sendBuilder, sendVal, srcTarget->fieldIdx);
|
|
// Note: No DontTouch added to sendVal, it can be constantprop'ed or
|
|
// CSE'ed.
|
|
}
|
|
|
|
auto *targetOp = wireTarget->ref.getOp();
|
|
auto sinkBuilder = ImplicitLocOpBuilder::atBlockEnd(wireModule.getLoc(),
|
|
targetOp->getBlock());
|
|
auto wireType = type_cast<FIRRTLBaseType>(targetOp->getResult(0).getType());
|
|
// Get type of sent value, if already a RefType, the base type.
|
|
auto valType = getBaseType(type_cast<FIRRTLType>(sendVal.getType()));
|
|
Value sink = getValueByFieldID(sinkBuilder, targetOp->getResult(0),
|
|
wireTarget->fieldIdx);
|
|
|
|
// For resets, sometimes inject a cast between sink and target 'sink'.
|
|
// Introduced a dummy wire and cast that, dummy wire will be 'sink'.
|
|
if (valType.isResetType() &&
|
|
valType.getWidthlessType() != wireType.getWidthlessType()) {
|
|
// Helper: create a wire, cast it with callback, connect cast to sink.
|
|
auto addWireWithCast = [&](auto createCast) {
|
|
auto wire =
|
|
sinkBuilder.create<WireOp>(valType, tapName.getValue()).getResult();
|
|
emitConnect(sinkBuilder, sink, createCast(wire));
|
|
sink = wire;
|
|
};
|
|
if (isa<IntType>(wireType))
|
|
addWireWithCast(
|
|
[&](auto v) { return sinkBuilder.create<AsUIntPrimOp>(v); });
|
|
else if (isa<AsyncResetType>(wireType))
|
|
addWireWithCast(
|
|
[&](auto v) { return sinkBuilder.create<AsAsyncResetPrimOp>(v); });
|
|
}
|
|
|
|
state.wiringProblems.push_back(
|
|
{sendVal, sink, "", WiringProblem::RefTypeUsage::Prefer});
|
|
}
|
|
|
|
return success();
|
|
}
|
|
|
|
LogicalResult circt::firrtl::applyGCTMemTaps(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto loc = state.circuit.getLoc();
|
|
|
|
auto sourceAttr =
|
|
tryGetAs<StringAttr>(anno, anno, "source", loc, memTapClass);
|
|
if (!sourceAttr)
|
|
return failure();
|
|
|
|
auto sourceTargetStr = canonicalizeTarget(sourceAttr.getValue());
|
|
std::optional<AnnoPathValue> srcTarget = resolvePath(
|
|
sourceTargetStr, state.circuit, state.symTbl, state.targetCaches);
|
|
if (!srcTarget)
|
|
return mlir::emitError(loc, "cannot resolve source target path '")
|
|
<< sourceTargetStr << "'";
|
|
|
|
auto tapsAttr = tryGetAs<ArrayAttr>(anno, anno, "sink", loc, memTapClass);
|
|
if (!tapsAttr || tapsAttr.empty())
|
|
return mlir::emitError(loc, "sink must have at least one entry");
|
|
|
|
auto tap = tapsAttr[0].dyn_cast_or_null<StringAttr>();
|
|
if (!tap) {
|
|
return mlir::emitError(
|
|
loc, "Annotation '" + Twine(memTapClass) +
|
|
"' with path '.taps[0" +
|
|
"]' contained an unexpected type (expected a string).")
|
|
.attachNote()
|
|
<< "The full Annotation is reprodcued here: " << anno << "\n";
|
|
}
|
|
|
|
auto wireTargetStr = canonicalizeTarget(tap.getValue());
|
|
if (!tokenizePath(wireTargetStr))
|
|
return failure();
|
|
std::optional<AnnoPathValue> wireTarget = resolvePath(
|
|
wireTargetStr, state.circuit, state.symTbl, state.targetCaches);
|
|
if (!wireTarget)
|
|
return mlir::emitError(loc, "Annotation '" + Twine(memTapClass) +
|
|
"' with path '.taps[0]' contains target '" +
|
|
wireTargetStr +
|
|
"' that cannot be resolved.")
|
|
.attachNote()
|
|
<< "The full Annotation is reproduced here: " << anno << "\n";
|
|
|
|
auto combMem = dyn_cast<chirrtl::CombMemOp>(srcTarget->ref.getOp());
|
|
if (!combMem)
|
|
return srcTarget->ref.getOp()->emitOpError(
|
|
"unsupported operation, only CombMem can be used as the source of "
|
|
"MemTap");
|
|
if (!combMem.getType().getElementType().isGround())
|
|
return combMem.emitOpError(
|
|
"cannot generate MemTap to a memory with aggregate data type");
|
|
if (tapsAttr.size() != combMem.getType().getNumElements())
|
|
return mlir::emitError(
|
|
loc, "sink cannot specify more taps than the depth of the memory");
|
|
if (srcTarget->instances.empty()) {
|
|
auto path = state.instancePathCache.getAbsolutePaths(
|
|
combMem->getParentOfType<FModuleOp>());
|
|
if (path.size() > 1)
|
|
return combMem.emitOpError(
|
|
"cannot be resolved as source for MemTap, multiple paths from top "
|
|
"exist and unique instance cannot be resolved");
|
|
srcTarget->instances.append(path.back().begin(), path.back().end());
|
|
}
|
|
|
|
ImplicitLocOpBuilder builder(combMem->getLoc(), combMem);
|
|
|
|
// Lower memory taps to real ports on the memory. This is done if the taps
|
|
// are supposed to be synthesized.
|
|
if (state.noRefTypePorts) {
|
|
// Create new ports _after_ all other ports to avoid permuting existing
|
|
// ports.
|
|
builder.setInsertionPointToEnd(
|
|
combMem->getParentOfType<FModuleOp>().getBodyBlock());
|
|
|
|
// Determine the clock to use for the debug ports. Error if the same clock
|
|
// is not used for all ports or if no clock port is found.
|
|
Value clock;
|
|
for (auto *portOp : combMem.getResult().getUsers()) {
|
|
for (auto result : portOp->getResults()) {
|
|
for (auto *user : result.getUsers()) {
|
|
auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
|
|
if (!accessOp)
|
|
continue;
|
|
auto newClock = accessOp.getClock();
|
|
if (clock && clock != newClock)
|
|
return combMem.emitOpError(
|
|
"has different clocks on different ports (this is ambiguous "
|
|
"when compiling without reference types)");
|
|
clock = newClock;
|
|
}
|
|
}
|
|
}
|
|
if (!clock)
|
|
return combMem.emitOpError(
|
|
"does not have an access port to determine a clock connection (this "
|
|
"is necessary when compiling without reference types)");
|
|
|
|
// Add one port per memory address.
|
|
SmallVector<Value> data;
|
|
for (uint64_t i = 0, e = combMem.getType().getNumElements(); i != e; ++i) {
|
|
auto port = builder.create<chirrtl::MemoryPortOp>(
|
|
combMem.getType().getElementType(),
|
|
CMemoryPortType::get(builder.getContext()), combMem.getResult(),
|
|
MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
|
|
builder.getArrayAttr({}));
|
|
builder.create<chirrtl::MemoryPortAccessOp>(
|
|
port.getPort(), builder.create<ConstantOp>(APSInt::get(i)), clock);
|
|
data.push_back(port.getData());
|
|
}
|
|
|
|
// Package up all the reads into a vector.
|
|
auto sendVal = builder.create<VectorCreateOp>(
|
|
FVectorType::get(combMem.getType().getElementType(),
|
|
combMem.getType().getNumElements()),
|
|
data);
|
|
auto sink = wireTarget->ref.getOp()->getResult(0);
|
|
|
|
// Add a wiring problem to hook up the vector to the destination wire.
|
|
state.wiringProblems.push_back(
|
|
{sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Never});
|
|
return success();
|
|
}
|
|
|
|
// Normal memory handling. Create a debug port.
|
|
builder.setInsertionPointAfter(combMem);
|
|
// Construct the type for the debug port.
|
|
auto debugType = RefType::get(FVectorType::get(
|
|
combMem.getType().getElementType(), combMem.getType().getNumElements()));
|
|
Value memDbgPort =
|
|
builder
|
|
.create<chirrtl::MemoryDebugPortOp>(
|
|
debugType, combMem,
|
|
state.getNamespace(srcTarget->ref.getModule()).newName("memTap"))
|
|
.getResult();
|
|
|
|
auto sendVal = memDbgPort;
|
|
if (wireTarget->ref.getOp()->getResult(0).getType() !=
|
|
type_cast<RefType>(sendVal.getType()).getType())
|
|
return wireTarget->ref.getOp()->emitError(
|
|
"cannot generate the MemTap, wiretap Type does not match the memory "
|
|
"type");
|
|
auto sink = wireTarget->ref.getOp()->getResult(0);
|
|
state.wiringProblems.push_back(
|
|
{sendVal, sink, "memTap", WiringProblem::RefTypeUsage::Prefer});
|
|
return success();
|
|
}
|