mirror of https://github.com/llvm/circt.git
1280 lines
51 KiB
C++
1280 lines
51 KiB
C++
//===- LowerAnnotations.cpp - Lower Annotations -----------------*- 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 defines the LowerAnnotations pass. This pass processes FIRRTL
|
|
// annotations, rewriting them, scattering them, and dealing with non-local
|
|
// annotations.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
|
|
#include "circt/Dialect/FIRRTL/CHIRRTLDialect.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
|
|
#include "circt/Dialect/FIRRTL/Passes.h"
|
|
#include "circt/Dialect/HW/HWAttributes.h"
|
|
#include "circt/Dialect/SV/SVAttributes.h"
|
|
#include "circt/Support/Debug.h"
|
|
#include "mlir/IR/Diagnostics.h"
|
|
#include "mlir/Pass/Pass.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "lower-annos"
|
|
|
|
namespace circt {
|
|
namespace firrtl {
|
|
#define GEN_PASS_DEF_LOWERFIRRTLANNOTATIONS
|
|
#include "circt/Dialect/FIRRTL/Passes.h.inc"
|
|
} // namespace firrtl
|
|
} // namespace circt
|
|
|
|
using namespace circt;
|
|
using namespace firrtl;
|
|
using namespace chirrtl;
|
|
|
|
/// Get annotations or an empty set of annotations.
|
|
static ArrayAttr getAnnotationsFrom(Operation *op) {
|
|
if (auto annots = op->getAttrOfType<ArrayAttr>(getAnnotationAttrName()))
|
|
return annots;
|
|
return ArrayAttr::get(op->getContext(), {});
|
|
}
|
|
|
|
/// Construct the annotation array with a new thing appended.
|
|
static ArrayAttr appendArrayAttr(ArrayAttr array, Attribute a) {
|
|
if (!array)
|
|
return ArrayAttr::get(a.getContext(), ArrayRef<Attribute>{a});
|
|
SmallVector<Attribute> old(array.begin(), array.end());
|
|
old.push_back(a);
|
|
return ArrayAttr::get(a.getContext(), old);
|
|
}
|
|
|
|
/// Update an ArrayAttribute by replacing one entry.
|
|
static ArrayAttr replaceArrayAttrElement(ArrayAttr array, size_t elem,
|
|
Attribute newVal) {
|
|
SmallVector<Attribute> old(array.begin(), array.end());
|
|
old[elem] = newVal;
|
|
return ArrayAttr::get(array.getContext(), old);
|
|
}
|
|
|
|
/// Apply a new annotation to a resolved target. This handles ports,
|
|
/// aggregates, modules, wires, etc.
|
|
static void addAnnotation(AnnoTarget ref, unsigned fieldIdx,
|
|
ArrayRef<NamedAttribute> anno) {
|
|
auto *context = ref.getOp()->getContext();
|
|
DictionaryAttr annotation;
|
|
if (fieldIdx) {
|
|
SmallVector<NamedAttribute> annoField(anno.begin(), anno.end());
|
|
annoField.emplace_back(
|
|
StringAttr::get(context, "circt.fieldID"),
|
|
IntegerAttr::get(IntegerType::get(context, 32, IntegerType::Signless),
|
|
fieldIdx));
|
|
annotation = DictionaryAttr::get(context, annoField);
|
|
} else {
|
|
annotation = DictionaryAttr::get(context, anno);
|
|
}
|
|
|
|
if (isa<OpAnnoTarget>(ref)) {
|
|
auto newAnno = appendArrayAttr(getAnnotationsFrom(ref.getOp()), annotation);
|
|
ref.getOp()->setAttr(getAnnotationAttrName(), newAnno);
|
|
return;
|
|
}
|
|
|
|
auto portRef = cast<PortAnnoTarget>(ref);
|
|
auto portAnnoRaw = ref.getOp()->getAttr(getPortAnnotationAttrName());
|
|
ArrayAttr portAnno = dyn_cast_or_null<ArrayAttr>(portAnnoRaw);
|
|
if (!portAnno || portAnno.size() != getNumPorts(ref.getOp())) {
|
|
SmallVector<Attribute> emptyPortAttr(
|
|
getNumPorts(ref.getOp()),
|
|
ArrayAttr::get(ref.getOp()->getContext(), {}));
|
|
portAnno = ArrayAttr::get(ref.getOp()->getContext(), emptyPortAttr);
|
|
}
|
|
portAnno = replaceArrayAttrElement(
|
|
portAnno, portRef.getPortNo(),
|
|
appendArrayAttr(dyn_cast<ArrayAttr>(portAnno[portRef.getPortNo()]),
|
|
annotation));
|
|
ref.getOp()->setAttr("portAnnotations", portAnno);
|
|
}
|
|
|
|
/// Make an anchor for a non-local annotation. Use the expanded path to build
|
|
/// the module and name list in the anchor.
|
|
static FlatSymbolRefAttr buildNLA(const AnnoPathValue &target,
|
|
ApplyState &state) {
|
|
OpBuilder b(state.circuit.getBodyRegion());
|
|
SmallVector<Attribute> insts;
|
|
for (auto inst : target.instances) {
|
|
insts.push_back(OpAnnoTarget(inst).getNLAReference(
|
|
state.getNamespace(inst->getParentOfType<FModuleLike>())));
|
|
}
|
|
|
|
insts.push_back(
|
|
FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr()));
|
|
|
|
auto instAttr = ArrayAttr::get(state.circuit.getContext(), insts);
|
|
return state.hierPathCache.getRefFor(instAttr);
|
|
}
|
|
|
|
/// Scatter breadcrumb annotations corresponding to non-local annotations
|
|
/// along the instance path. Returns symbol name used to anchor annotations to
|
|
/// path.
|
|
// FIXME: uniq annotation chain links
|
|
static FlatSymbolRefAttr scatterNonLocalPath(const AnnoPathValue &target,
|
|
ApplyState &state) {
|
|
|
|
FlatSymbolRefAttr sym = buildNLA(target, state);
|
|
return sym;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Standard Utility Resolvers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Always resolve to the circuit, ignoring the annotation.
|
|
static std::optional<AnnoPathValue> noResolve(DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
return AnnoPathValue(state.circuit);
|
|
}
|
|
|
|
/// Implementation of standard resolution. First parses the target path, then
|
|
/// resolves it.
|
|
static std::optional<AnnoPathValue> stdResolveImpl(StringRef rawPath,
|
|
ApplyState &state) {
|
|
auto pathStr = canonicalizeTarget(rawPath);
|
|
StringRef path{pathStr};
|
|
|
|
auto tokens = tokenizePath(path);
|
|
if (!tokens) {
|
|
mlir::emitError(state.circuit.getLoc())
|
|
<< "Cannot tokenize annotation path " << rawPath;
|
|
return {};
|
|
}
|
|
|
|
return resolveEntities(*tokens, state.circuit, state.symTbl,
|
|
state.targetCaches);
|
|
}
|
|
|
|
/// (SFC) FIRRTL SingleTargetAnnotation resolver. Uses the 'target' field of
|
|
/// the annotation with standard parsing to resolve the path. This requires
|
|
/// 'target' to exist and be normalized (per docs/FIRRTLAnnotations.md).
|
|
std::optional<AnnoPathValue> circt::firrtl::stdResolve(DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto target = anno.getNamed("target");
|
|
if (!target) {
|
|
mlir::emitError(state.circuit.getLoc())
|
|
<< "No target field in annotation " << anno;
|
|
return {};
|
|
}
|
|
if (!isa<StringAttr>(target->getValue())) {
|
|
mlir::emitError(state.circuit.getLoc())
|
|
<< "Target field in annotation doesn't contain string " << anno;
|
|
return {};
|
|
}
|
|
return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(), state);
|
|
}
|
|
|
|
/// Resolves with target, if it exists. If not, resolves to the circuit.
|
|
std::optional<AnnoPathValue> circt::firrtl::tryResolve(DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto target = anno.getNamed("target");
|
|
if (target)
|
|
return stdResolveImpl(cast<StringAttr>(target->getValue()).getValue(),
|
|
state);
|
|
return AnnoPathValue(state.circuit);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Standard Utility Appliers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// An applier which puts the annotation on the target and drops the 'target'
|
|
/// field from the annotation. Optionally handles non-local annotations.
|
|
LogicalResult circt::firrtl::applyWithoutTargetImpl(const AnnoPathValue &target,
|
|
|
|
DictionaryAttr anno,
|
|
ApplyState &state,
|
|
bool allowNonLocal) {
|
|
if (!allowNonLocal && !target.isLocal()) {
|
|
Annotation annotation(anno);
|
|
auto diag = mlir::emitError(target.ref.getOp()->getLoc())
|
|
<< "is targeted by a non-local annotation \""
|
|
<< annotation.getClass() << "\" with target "
|
|
<< annotation.getMember("target")
|
|
<< ", but this annotation cannot be non-local";
|
|
diag.attachNote() << "see current annotation: " << anno << "\n";
|
|
return failure();
|
|
}
|
|
SmallVector<NamedAttribute> newAnnoAttrs;
|
|
for (auto &na : anno) {
|
|
if (na.getName().getValue() != "target") {
|
|
newAnnoAttrs.push_back(na);
|
|
} else if (!target.isLocal()) {
|
|
auto sym = scatterNonLocalPath(target, state);
|
|
newAnnoAttrs.push_back(
|
|
{StringAttr::get(anno.getContext(), "circt.nonlocal"), sym});
|
|
}
|
|
}
|
|
addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
|
|
return success();
|
|
}
|
|
|
|
/// Just drop the annotation. This is intended for Annotations which are known,
|
|
/// but can be safely ignored.
|
|
LogicalResult drop(const AnnoPathValue &target, DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
return success();
|
|
}
|
|
//===----------------------------------------------------------------------===//
|
|
// Customized Appliers
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static LogicalResult applyDUTAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno, ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto loc = op->getLoc();
|
|
|
|
if (!target.isLocal())
|
|
return mlir::emitError(loc) << "must be local";
|
|
|
|
if (!isa<OpAnnoTarget>(target.ref) || !isa<FModuleLike>(op))
|
|
return mlir::emitError(loc) << "can only target to a module";
|
|
|
|
auto moduleOp = cast<FModuleLike>(op);
|
|
|
|
// DUT has public visibility.
|
|
moduleOp.setPublic();
|
|
SmallVector<NamedAttribute> newAnnoAttrs;
|
|
for (auto &na : anno)
|
|
if (na.getName().getValue() != "target")
|
|
newAnnoAttrs.push_back(na);
|
|
addAnnotation(target.ref, target.fieldIdx, newAnnoAttrs);
|
|
return success();
|
|
}
|
|
|
|
// Like symbolizeConvention, but disallows the internal convention.
|
|
static std::optional<Convention> parseConvention(llvm::StringRef str) {
|
|
return ::llvm::StringSwitch<::std::optional<Convention>>(str)
|
|
.Case("scalarized", Convention::Scalarized)
|
|
.Default(std::nullopt);
|
|
}
|
|
|
|
static LogicalResult applyConventionAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto loc = op->getLoc();
|
|
auto error = [&]() {
|
|
auto diag = mlir::emitError(loc);
|
|
diag << "circuit.ConventionAnnotation ";
|
|
return diag;
|
|
};
|
|
|
|
auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
|
|
if (!opTarget)
|
|
return error() << "must target a module object";
|
|
|
|
if (!target.isLocal())
|
|
return error() << "must be local";
|
|
|
|
auto conventionStrAttr =
|
|
tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
|
|
if (!conventionStrAttr)
|
|
return failure();
|
|
|
|
auto conventionStr = conventionStrAttr.getValue();
|
|
auto conventionOpt = parseConvention(conventionStr);
|
|
if (!conventionOpt)
|
|
return error() << "unknown convention " << conventionStr;
|
|
|
|
auto convention = *conventionOpt;
|
|
|
|
if (auto moduleOp = dyn_cast<FModuleOp>(op)) {
|
|
moduleOp.setConvention(convention);
|
|
return success();
|
|
}
|
|
|
|
if (auto extModuleOp = dyn_cast<FExtModuleOp>(op)) {
|
|
extModuleOp.setConvention(convention);
|
|
return success();
|
|
}
|
|
|
|
return error() << "can only target to a module or extmodule";
|
|
}
|
|
|
|
static LogicalResult applyBodyTypeLoweringAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto loc = op->getLoc();
|
|
auto error = [&]() {
|
|
auto diag = mlir::emitError(loc);
|
|
diag << typeLoweringAnnoClass;
|
|
return diag;
|
|
};
|
|
|
|
auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
|
|
if (!opTarget)
|
|
return error() << "must target a module object";
|
|
|
|
if (!target.isLocal())
|
|
return error() << "must be local";
|
|
|
|
auto moduleOp = dyn_cast<FModuleOp>(op);
|
|
|
|
if (!moduleOp)
|
|
return error() << "can only target to a module";
|
|
|
|
auto conventionStrAttr =
|
|
tryGetAs<StringAttr>(anno, anno, "convention", loc, conventionAnnoClass);
|
|
|
|
if (!conventionStrAttr)
|
|
return failure();
|
|
|
|
auto conventionStr = conventionStrAttr.getValue();
|
|
auto conventionOpt = parseConvention(conventionStr);
|
|
if (!conventionOpt)
|
|
return error() << "unknown convention " << conventionStr;
|
|
|
|
auto convention = *conventionOpt;
|
|
|
|
if (convention == Convention::Internal)
|
|
// Convention is internal by default so there is nothing to change
|
|
return success();
|
|
|
|
auto conventionAttr = ConventionAttr::get(op->getContext(), convention);
|
|
|
|
// `includeHierarchy` only valid in BodyTypeLowering.
|
|
bool includeHierarchy = false;
|
|
if (auto includeHierarchyAttr = tryGetAs<BoolAttr>(
|
|
anno, anno, "includeHierarchy", loc, conventionAnnoClass))
|
|
includeHierarchy = includeHierarchyAttr.getValue();
|
|
|
|
if (includeHierarchy) {
|
|
// If includeHierarchy is true, update the convention for all modules in
|
|
// the hierarchy.
|
|
for (auto *node :
|
|
llvm::post_order(state.instancePathCache.instanceGraph[moduleOp])) {
|
|
if (!node)
|
|
continue;
|
|
if (auto fmodule = dyn_cast<FModuleOp>(*node->getModule()))
|
|
fmodule->setAttr("body_type_lowering", conventionAttr);
|
|
}
|
|
} else {
|
|
// Update the convention.
|
|
moduleOp->setAttr("body_type_lowering", conventionAttr);
|
|
}
|
|
|
|
return success();
|
|
}
|
|
|
|
static LogicalResult applyModulePrefixAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto loc = op->getLoc();
|
|
auto error = [&]() {
|
|
auto diag = mlir::emitError(loc);
|
|
diag << modulePrefixAnnoClass << " ";
|
|
return diag;
|
|
};
|
|
|
|
auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
|
|
if (!opTarget)
|
|
return error() << "must target an operation";
|
|
|
|
if (!isa<SeqMemOp, CombMemOp, MemOp>(opTarget.getOp()))
|
|
return error() << "must target a memory operation";
|
|
|
|
if (!target.isLocal())
|
|
return error() << "must be local";
|
|
|
|
auto prefixStrAttr =
|
|
tryGetAs<StringAttr>(anno, anno, "prefix", loc, modulePrefixAnnoClass);
|
|
if (!prefixStrAttr)
|
|
return failure();
|
|
|
|
if (auto mem = dyn_cast<SeqMemOp>(op))
|
|
mem.setPrefixAttr(prefixStrAttr);
|
|
else if (auto mem = dyn_cast<CombMemOp>(op))
|
|
mem.setPrefixAttr(prefixStrAttr);
|
|
else if (auto mem = dyn_cast<MemOp>(op))
|
|
mem.setPrefixAttr(prefixStrAttr);
|
|
|
|
return success();
|
|
}
|
|
|
|
static LogicalResult applyAttributeAnnotation(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
|
|
auto error = [&]() {
|
|
auto diag = mlir::emitError(op->getLoc());
|
|
diag << anno.getAs<StringAttr>("class").getValue() << " ";
|
|
return diag;
|
|
};
|
|
|
|
if (!isa<OpAnnoTarget>(target.ref))
|
|
return error()
|
|
<< "must target an operation. Currently ports are not supported";
|
|
|
|
if (!target.isLocal())
|
|
return error() << "must be local";
|
|
|
|
if (!isa<FModuleOp, WireOp, NodeOp, RegOp, RegResetOp>(op))
|
|
return error()
|
|
<< "unhandled operation. The target must be a module, wire, node or "
|
|
"register";
|
|
|
|
auto name = anno.getAs<StringAttr>("description");
|
|
auto svAttr = sv::SVAttributeAttr::get(name.getContext(), name);
|
|
sv::addSVAttributes(op, {svAttr});
|
|
return success();
|
|
}
|
|
|
|
/// Update a memory op with attributes about memory file loading.
|
|
template <bool isInline>
|
|
static LogicalResult applyLoadMemoryAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
if (!target.isLocal()) {
|
|
mlir::emitError(state.circuit.getLoc())
|
|
<< "has a " << anno.get("class")
|
|
<< " annotation which is non-local, but this annotation is not allowed "
|
|
"to be non-local";
|
|
return failure();
|
|
}
|
|
|
|
auto *op = target.ref.getOp();
|
|
|
|
if (!target.isOpOfType<MemOp, CombMemOp, SeqMemOp>()) {
|
|
mlir::emitError(op->getLoc())
|
|
<< "can only apply a load memory annotation to a memory";
|
|
return failure();
|
|
}
|
|
|
|
// The two annotations have different case usage in "filename".
|
|
StringAttr filename = tryGetAs<StringAttr>(
|
|
anno, anno, isInline ? "filename" : "fileName", op->getLoc(),
|
|
anno.getAs<StringAttr>("class").getValue());
|
|
if (!filename)
|
|
return failure();
|
|
|
|
auto hexOrBinary =
|
|
tryGetAs<StringAttr>(anno, anno, "hexOrBinary", op->getLoc(),
|
|
anno.getAs<StringAttr>("class").getValue());
|
|
if (!hexOrBinary)
|
|
return failure();
|
|
|
|
auto hexOrBinaryValue = hexOrBinary.getValue();
|
|
if (hexOrBinaryValue != "h" && hexOrBinaryValue != "b") {
|
|
auto diag = mlir::emitError(op->getLoc())
|
|
<< "has memory initialization annotation with invalid format, "
|
|
"'hexOrBinary' field must be either 'h' or 'b'";
|
|
diag.attachNote() << "the full annotation is: " << anno;
|
|
return failure();
|
|
}
|
|
|
|
op->setAttr("init", MemoryInitAttr::get(op->getContext(), filename,
|
|
hexOrBinaryValue == "b", isInline));
|
|
|
|
return success();
|
|
}
|
|
|
|
static LogicalResult applyOutputDirAnno(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto *context = op->getContext();
|
|
auto loc = op->getLoc();
|
|
|
|
auto error = [&]() {
|
|
return mlir::emitError(loc) << outputDirAnnoClass << " ";
|
|
};
|
|
|
|
auto opTarget = dyn_cast<OpAnnoTarget>(target.ref);
|
|
if (!opTarget)
|
|
return error() << "must target a module";
|
|
if (!target.isLocal())
|
|
return error() << "must be local";
|
|
|
|
auto moduleOp = dyn_cast<FModuleOp>(op);
|
|
if (!moduleOp)
|
|
return error() << "must target a module";
|
|
if (!moduleOp.isPublic())
|
|
return error() << "must target a public module";
|
|
if (moduleOp->hasAttr("output_file"))
|
|
return error() << "target already has an output file";
|
|
|
|
auto dirname =
|
|
tryGetAs<StringAttr>(anno, anno, "dirname", loc, outputDirAnnoClass);
|
|
if (!dirname)
|
|
return failure();
|
|
if (dirname.empty())
|
|
return error() << "dirname must not be empty";
|
|
|
|
auto outputFile =
|
|
hw::OutputFileAttr::getAsDirectory(context, dirname.getValue());
|
|
|
|
moduleOp->setAttr("output_file", outputFile);
|
|
return success();
|
|
}
|
|
|
|
/// Convert from FullAsyncResetAnnotation to FullResetAnnotation
|
|
static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target,
|
|
DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto *context = op->getContext();
|
|
|
|
mlir::emitWarning(op->getLoc())
|
|
<< "'" << fullAsyncResetAnnoClass << "' is deprecated, use '"
|
|
<< fullResetAnnoClass << "' instead";
|
|
|
|
NamedAttrList newAnno(anno.getValue());
|
|
newAnno.set("class", StringAttr::get(context, fullResetAnnoClass));
|
|
newAnno.append("resetType", StringAttr::get(context, "async"));
|
|
|
|
DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
|
|
|
|
return applyWithoutTarget<false>(target, newDictionary, state);
|
|
}
|
|
|
|
/// Convert from IgnoreFullAsyncResetAnnotation to
|
|
/// ExcludeFromFullResetAnnotation
|
|
static LogicalResult convertToExcludeFromFullResetAnnotation(
|
|
const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state) {
|
|
auto *op = target.ref.getOp();
|
|
auto *context = op->getContext();
|
|
|
|
mlir::emitWarning(op->getLoc())
|
|
<< "'" << ignoreFullAsyncResetAnnoClass << "' is deprecated, use '"
|
|
<< excludeFromFullResetAnnoClass << "' instead";
|
|
|
|
NamedAttrList newAnno(anno.getValue());
|
|
newAnno.set("class", StringAttr::get(context, excludeFromFullResetAnnoClass));
|
|
|
|
DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno);
|
|
|
|
return applyWithoutTarget<true, FModuleOp>(target, newDictionary, state);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Driving table
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace circt::firrtl {
|
|
/// Resolution and application of a "firrtl.annotations.NoTargetAnnotation".
|
|
/// This should be used for any Annotation which does not apply to anything in
|
|
/// the FIRRTL Circuit, i.e., an Annotation which has no target. Historically,
|
|
/// NoTargetAnnotations were used to control the Scala FIRRTL Compiler (SFC) or
|
|
/// its passes, e.g., to set the output directory or to turn on a pass.
|
|
/// Examples of these in the SFC are "firrtl.options.TargetDirAnnotation" to set
|
|
/// the output directory or "firrtl.stage.RunFIRRTLTransformAnnotation" to
|
|
/// cause the SFC to schedule a specified pass. Instead of leaving these
|
|
/// floating or attaching them to the top-level MLIR module (which is a purer
|
|
/// interpretation of "no target"), we choose to attach them to the Circuit even
|
|
/// they do not "apply" to the Circuit. This gives later passes a common place,
|
|
/// the Circuit, to search for these control Annotations.
|
|
static AnnoRecord NoTargetAnnotation = {noResolve,
|
|
applyWithoutTarget<false, CircuitOp>};
|
|
|
|
static llvm::StringMap<AnnoRecord> annotationRecords{{
|
|
|
|
// Testing Annotation
|
|
{"circt.test", {stdResolve, applyWithoutTarget<true>}},
|
|
{"circt.testLocalOnly", {stdResolve, applyWithoutTarget<>}},
|
|
{"circt.testNT", {noResolve, applyWithoutTarget<>}},
|
|
{"circt.missing", {tryResolve, applyWithoutTarget<true>}},
|
|
// Grand Central Views/Interfaces Annotations
|
|
{extractGrandCentralClass, NoTargetAnnotation},
|
|
{grandCentralHierarchyFileAnnoClass, NoTargetAnnotation},
|
|
{serializedViewAnnoClass, {noResolve, applyGCTView}},
|
|
{viewAnnoClass, {noResolve, applyGCTView}},
|
|
{companionAnnoClass, {stdResolve, applyWithoutTarget<>}},
|
|
{augmentedGroundTypeClass, {stdResolve, applyWithoutTarget<true>}},
|
|
// Grand Central Data Tap Annotations
|
|
{dataTapsClass, {noResolve, applyGCTDataTaps}},
|
|
{dataTapsBlackboxClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{referenceKeySourceClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{referenceKeyPortClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{internalKeySourceClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{internalKeyPortClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{deletedKeyClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{literalKeyClass, {stdResolve, applyWithoutTarget<true>}},
|
|
// Grand Central Mem Tap Annotations
|
|
{memTapClass, {noResolve, applyGCTMemTaps}},
|
|
{memTapSourceClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{memTapPortClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{memTapBlackboxClass, {stdResolve, applyWithoutTarget<true>}},
|
|
// Miscellaneous Annotations
|
|
{conventionAnnoClass, {stdResolve, applyConventionAnno}},
|
|
{typeLoweringAnnoClass, {stdResolve, applyBodyTypeLoweringAnno}},
|
|
{dontTouchAnnoClass,
|
|
{stdResolve, applyWithoutTarget<true, true, WireOp, NodeOp, RegOp,
|
|
RegResetOp, InstanceOp, MemOp, CombMemOp,
|
|
MemoryPortOp, SeqMemOp>}},
|
|
{modulePrefixAnnoClass, {stdResolve, applyModulePrefixAnno}},
|
|
{dutAnnoClass, {stdResolve, applyDUTAnno}},
|
|
{extractSeqMemsAnnoClass, NoTargetAnnotation},
|
|
{injectDUTHierarchyAnnoClass, NoTargetAnnotation},
|
|
{convertMemToRegOfVecAnnoClass, NoTargetAnnotation},
|
|
{excludeMemToRegAnnoClass,
|
|
{stdResolve, applyWithoutTarget<true, MemOp, CombMemOp>}},
|
|
{sitestBlackBoxAnnoClass, NoTargetAnnotation},
|
|
{enumComponentAnnoClass, {noResolve, drop}},
|
|
{enumDefAnnoClass, {noResolve, drop}},
|
|
{enumVecAnnoClass, {noResolve, drop}},
|
|
{forceNameAnnoClass,
|
|
{stdResolve, applyWithoutTarget<true, FModuleOp, FExtModuleOp>}},
|
|
{flattenAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
|
|
{inlineAnnoClass, {stdResolve, applyWithoutTarget<false, FModuleOp>}},
|
|
{noDedupAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
|
|
{dedupGroupAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
|
|
{blackBoxInlineAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
|
|
{blackBoxPathAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
|
|
{dontObfuscateModuleAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FModuleOp>}},
|
|
{verifBlackBoxAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FExtModuleOp>}},
|
|
{elaborationArtefactsDirectoryAnnoClass, NoTargetAnnotation},
|
|
{retimeModulesFileAnnoClass, NoTargetAnnotation},
|
|
{retimeModuleAnnoClass,
|
|
{stdResolve, applyWithoutTarget<false, FModuleOp, FExtModuleOp>}},
|
|
{metadataDirectoryAttrName, NoTargetAnnotation},
|
|
{moduleHierAnnoClass, NoTargetAnnotation},
|
|
{outputDirAnnoClass, {stdResolve, applyOutputDirAnno}},
|
|
{sitestTestHarnessBlackBoxAnnoClass, NoTargetAnnotation},
|
|
{testBenchDirAnnoClass, NoTargetAnnotation},
|
|
{testHarnessHierAnnoClass, NoTargetAnnotation},
|
|
{testHarnessPathAnnoClass, NoTargetAnnotation},
|
|
{extractAssertAnnoClass, NoTargetAnnotation},
|
|
{extractAssumeAnnoClass, NoTargetAnnotation},
|
|
{extractCoverageAnnoClass, NoTargetAnnotation},
|
|
{runFIRRTLTransformAnnoClass, {noResolve, drop}},
|
|
{mustDedupAnnoClass, NoTargetAnnotation},
|
|
{addSeqMemPortAnnoClass, NoTargetAnnotation},
|
|
{addSeqMemPortsFileAnnoClass, NoTargetAnnotation},
|
|
{extractClockGatesAnnoClass, NoTargetAnnotation},
|
|
{extractBlackBoxAnnoClass, {stdResolve, applyWithoutTarget<false>}},
|
|
{fullResetAnnoClass, {stdResolve, applyWithoutTarget<false>}},
|
|
{excludeFromFullResetAnnoClass,
|
|
{stdResolve, applyWithoutTarget<true, FModuleOp>}},
|
|
{fullAsyncResetAnnoClass, {stdResolve, convertToFullResetAnnotation}},
|
|
{ignoreFullAsyncResetAnnoClass,
|
|
{stdResolve, convertToExcludeFromFullResetAnnotation}},
|
|
{decodeTableAnnotation, {noResolve, drop}},
|
|
{blackBoxTargetDirAnnoClass, NoTargetAnnotation},
|
|
{traceNameAnnoClass, {stdResolve, applyTraceName}},
|
|
{traceAnnoClass, {stdResolve, applyWithoutTarget<true>}},
|
|
{loadMemoryFromFileAnnoClass, {stdResolve, applyLoadMemoryAnno<false>}},
|
|
{loadMemoryFromFileInlineAnnoClass,
|
|
{stdResolve, applyLoadMemoryAnno<true>}},
|
|
{wiringSinkAnnoClass, {stdResolve, applyWiring}},
|
|
{wiringSourceAnnoClass, {stdResolve, applyWiring}},
|
|
{attributeAnnoClass, {stdResolve, applyAttributeAnnotation}}}};
|
|
|
|
LogicalResult
|
|
registerAnnotationRecord(StringRef annoClass, AnnoRecord annoRecord,
|
|
const std::function<void(llvm::Twine)> &errorHandler) {
|
|
|
|
if (annotationRecords.insert({annoClass, annoRecord}).second)
|
|
return LogicalResult::success();
|
|
if (errorHandler)
|
|
errorHandler("annotation record '" + annoClass + "' is registered twice\n");
|
|
return LogicalResult::failure();
|
|
}
|
|
|
|
} // namespace circt::firrtl
|
|
|
|
/// Lookup a record for a given annotation class. Optionally, returns the
|
|
/// record for "circuit.missing" if the record doesn't exist.
|
|
static const AnnoRecord *getAnnotationHandler(StringRef annoStr,
|
|
bool ignoreAnnotationUnknown) {
|
|
auto ii = annotationRecords.find(annoStr);
|
|
if (ii != annotationRecords.end())
|
|
return &ii->second;
|
|
if (ignoreAnnotationUnknown)
|
|
return &annotationRecords.find("circt.missing")->second;
|
|
return nullptr;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Pass Infrastructure
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct LowerAnnotationsPass
|
|
: public circt::firrtl::impl::LowerFIRRTLAnnotationsBase<
|
|
LowerAnnotationsPass> {
|
|
void runOnOperation() override;
|
|
LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState &state);
|
|
LogicalResult legacyToWiringProblems(ApplyState &state);
|
|
LogicalResult solveWiringProblems(ApplyState &state);
|
|
|
|
using LowerFIRRTLAnnotationsBase::allowAddingPortsOnPublic;
|
|
using LowerFIRRTLAnnotationsBase::ignoreAnnotationClassless;
|
|
using LowerFIRRTLAnnotationsBase::ignoreAnnotationUnknown;
|
|
using LowerFIRRTLAnnotationsBase::noRefTypePorts;
|
|
SmallVector<DictionaryAttr> worklistAttrs;
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
|
|
ApplyState &state) {
|
|
LLVM_DEBUG(llvm::dbgs() << " - anno: " << anno << "\n";);
|
|
|
|
// Lookup the class
|
|
StringRef annoClassVal;
|
|
if (auto annoClass = anno.getNamed("class"))
|
|
annoClassVal = cast<StringAttr>(annoClass->getValue()).getValue();
|
|
else if (ignoreAnnotationClassless)
|
|
annoClassVal = "circt.missing";
|
|
else
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Annotation without a class: " << anno;
|
|
|
|
// See if we handle the class
|
|
auto *record = getAnnotationHandler(annoClassVal, false);
|
|
if (!record) {
|
|
++numUnhandled;
|
|
if (!ignoreAnnotationUnknown)
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Unhandled annotation: " << anno;
|
|
|
|
// Try again, requesting the fallback handler.
|
|
record = getAnnotationHandler(annoClassVal, ignoreAnnotationUnknown);
|
|
assert(record);
|
|
}
|
|
|
|
// Try to apply the annotation
|
|
auto target = record->resolver(anno, state);
|
|
if (!target)
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Unable to resolve target of annotation: " << anno;
|
|
if (record->applier(*target, anno, state).failed())
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Unable to apply annotation: " << anno;
|
|
return success();
|
|
}
|
|
|
|
/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems,
|
|
/// using the pin attribute as newNameHint
|
|
LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) {
|
|
for (const auto &[name, problem] : state.legacyWiringProblems) {
|
|
if (!problem.source)
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Unable to resolve source for pin: " << name;
|
|
|
|
if (problem.sinks.empty())
|
|
return mlir::emitError(state.circuit.getLoc())
|
|
<< "Unable to resolve sink(s) for pin: " << name;
|
|
|
|
for (const auto &sink : problem.sinks) {
|
|
state.wiringProblems.push_back(
|
|
{problem.source, sink, {}, WiringProblem::RefTypeUsage::Never});
|
|
}
|
|
}
|
|
return success();
|
|
}
|
|
|
|
/// Modify the circuit to solve and apply all Wiring Problems in the circuit. A
|
|
/// Wiring Problem is a mapping from a source to a sink that can be connected
|
|
/// via a base Type or RefType as requested. This uses a two-step approach.
|
|
/// First, all Wiring Problems are analyzed to compute pending modifications to
|
|
/// modules. Second, modules are visited from leaves to roots to apply module
|
|
/// modifications. Module modifications include addings ports and connecting
|
|
/// things up.
|
|
LogicalResult LowerAnnotationsPass::solveWiringProblems(ApplyState &state) {
|
|
// Utility function to extract the defining module from a value which may be
|
|
// either a BlockArgument or an Operation result.
|
|
auto getModule = [](Value value) {
|
|
if (BlockArgument blockArg = dyn_cast<BlockArgument>(value))
|
|
return cast<FModuleLike>(blockArg.getParentBlock()->getParentOp());
|
|
return value.getDefiningOp()->getParentOfType<FModuleLike>();
|
|
};
|
|
|
|
// Utility function to determine where to insert connection operations.
|
|
auto findInsertionBlock = [&getModule](Value src, Value dest) -> Block * {
|
|
// Check for easy case: both are in the same block.
|
|
if (src.getParentBlock() == dest.getParentBlock())
|
|
return src.getParentBlock();
|
|
|
|
// If connecting across blocks, figure out where to connect.
|
|
(void)getModule;
|
|
assert(getModule(src) == getModule(dest));
|
|
// Helper to determine if 'a' is available at 'b's block.
|
|
auto safelyDoms = [&](Value a, Value b) {
|
|
if (isa<BlockArgument>(a))
|
|
return true;
|
|
if (isa<BlockArgument>(b))
|
|
return false;
|
|
// Handle cases where 'b' is in child op after 'a'.
|
|
auto *ancestor =
|
|
a.getParentBlock()->findAncestorOpInBlock(*b.getDefiningOp());
|
|
return ancestor && a.getDefiningOp()->isBeforeInBlock(ancestor);
|
|
};
|
|
if (safelyDoms(src, dest))
|
|
return dest.getParentBlock();
|
|
if (safelyDoms(dest, src))
|
|
return src.getParentBlock();
|
|
return {};
|
|
};
|
|
|
|
auto getNoopCast = [](Value v) -> mlir::UnrealizedConversionCastOp {
|
|
auto op =
|
|
dyn_cast_or_null<mlir::UnrealizedConversionCastOp>(v.getDefiningOp());
|
|
if (op && op.getNumResults() == 1 && op.getNumOperands() == 1 &&
|
|
op.getResultTypes()[0] == op.getOperandTypes()[0])
|
|
return op;
|
|
return {};
|
|
};
|
|
|
|
// Utility function to connect a destination to a source. Always use a
|
|
// ConnectOp as the widths may be uninferred.
|
|
SmallVector<Operation *> opsToErase;
|
|
auto connect = [&](Value src, Value dest,
|
|
ImplicitLocOpBuilder &builder) -> LogicalResult {
|
|
// Strip away noop unrealized_conversion_cast's, used as placeholders.
|
|
// In the future, these should be created/managed as part of creating WP's.
|
|
if (auto op = getNoopCast(dest)) {
|
|
dest = op.getOperand(0);
|
|
opsToErase.push_back(op);
|
|
std::swap(src, dest);
|
|
} else if (auto op = getNoopCast(src)) {
|
|
src = op.getOperand(0);
|
|
opsToErase.push_back(op);
|
|
}
|
|
|
|
if (foldFlow(dest) == Flow::Source)
|
|
std::swap(src, dest);
|
|
|
|
// Figure out where to insert operations.
|
|
auto *insertBlock = findInsertionBlock(src, dest);
|
|
if (!insertBlock)
|
|
return emitError(src.getLoc())
|
|
.append("This value is involved with a Wiring Problem where the "
|
|
"destination is in the same module but neither dominates the "
|
|
"other, which is not supported.")
|
|
.attachNote(dest.getLoc())
|
|
.append("The destination is here.");
|
|
|
|
// Insert at end, past invalidation in same block.
|
|
builder.setInsertionPointToEnd(insertBlock);
|
|
|
|
// Create RefSend/RefResolve if necessary.
|
|
if (type_isa<RefType>(dest.getType()) != type_isa<RefType>(src.getType())) {
|
|
if (type_isa<RefType>(dest.getType()))
|
|
src = builder.create<RefSendOp>(src);
|
|
else
|
|
src = builder.create<RefResolveOp>(src);
|
|
}
|
|
|
|
// If the sink is a wire with no users, then convert this to a node.
|
|
// This is done to convert the undriven wires created for GCView's
|
|
// into the NodeOp's they're required to be in GrandCentral.cpp.
|
|
if (auto destOp = dyn_cast_or_null<WireOp>(dest.getDefiningOp());
|
|
destOp && dest.getUses().empty()) {
|
|
// Only perform this if the type is suitable (passive).
|
|
if (auto baseType = dyn_cast<FIRRTLBaseType>(src.getType());
|
|
baseType && baseType.isPassive()) {
|
|
// Note that the wire is replaced with the source type
|
|
// regardless, continue this behavior.
|
|
builder.create<NodeOp>(src, destOp.getName())
|
|
.setAnnotationsAttr(destOp.getAnnotations());
|
|
opsToErase.push_back(destOp);
|
|
return success();
|
|
}
|
|
}
|
|
|
|
// Otherwise, just connect to the source.
|
|
emitConnect(builder, dest, src);
|
|
|
|
return success();
|
|
};
|
|
|
|
auto &instanceGraph = state.instancePathCache.instanceGraph;
|
|
auto *context = state.circuit.getContext();
|
|
|
|
// Examine all discovered Wiring Problems to determine modifications that need
|
|
// to be made per-module.
|
|
LLVM_DEBUG({ llvm::dbgs() << "Analyzing wiring problems:\n"; });
|
|
DenseMap<FModuleLike, ModuleModifications> moduleModifications;
|
|
DenseSet<Value> visitedSinks;
|
|
for (auto e : llvm::enumerate(state.wiringProblems)) {
|
|
auto index = e.index();
|
|
auto problem = e.value();
|
|
// This is a unique index that is assigned to this specific wiring problem
|
|
// and is used as a key during wiring to know which Values (ports, sources,
|
|
// or sinks) should be connected.
|
|
auto source = problem.source;
|
|
auto sink = problem.sink;
|
|
|
|
// Check that no WiringProblems are trying to use the same sink. This
|
|
// should never happen.
|
|
if (!visitedSinks.insert(sink).second) {
|
|
auto diag = mlir::emitError(source.getLoc())
|
|
<< "This sink is involved with a Wiring Problem which is "
|
|
"targeted by a source used by another Wiring Problem. "
|
|
"(This is both illegal and should be impossible.)";
|
|
diag.attachNote(source.getLoc()) << "The source is here";
|
|
return failure();
|
|
}
|
|
FModuleLike sourceModule = getModule(source);
|
|
FModuleLike sinkModule = getModule(sink);
|
|
if (isa<FExtModuleOp>(sourceModule) || isa<FExtModuleOp>(sinkModule)) {
|
|
auto diag = mlir::emitError(source.getLoc())
|
|
<< "This source is involved with a Wiring Problem which "
|
|
"includes an External Module port and External Module "
|
|
"ports anre not supported.";
|
|
diag.attachNote(sink.getLoc()) << "The sink is here.";
|
|
return failure();
|
|
}
|
|
|
|
LLVM_DEBUG({
|
|
llvm::dbgs() << " - index: " << index << "\n"
|
|
<< " source:\n"
|
|
<< " module: " << sourceModule.getModuleName() << "\n"
|
|
<< " value: " << source << "\n"
|
|
<< " sink:\n"
|
|
<< " module: " << sinkModule.getModuleName() << "\n"
|
|
<< " value: " << sink << "\n"
|
|
<< " newNameHint: " << problem.newNameHint << "\n";
|
|
});
|
|
|
|
// If the source and sink are in the same block, just wire them up.
|
|
if (sink.getParentBlock() == source.getParentBlock()) {
|
|
auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
|
|
sink.getParentBlock());
|
|
if (failed(connect(source, sink, builder)))
|
|
return failure();
|
|
continue;
|
|
}
|
|
// If both are in the same module but not same block, U-turn.
|
|
// We may not be able to handle this, but that is checked below while
|
|
// connecting.
|
|
if (sourceModule == sinkModule) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< " LCA: " << sourceModule.getModuleName() << "\n");
|
|
moduleModifications[sourceModule].connectionMap[index] = source;
|
|
moduleModifications[sourceModule].uturns.push_back({index, sink});
|
|
continue;
|
|
}
|
|
|
|
// Otherwise, get instance paths for source/sink, and compute LCA.
|
|
auto sourcePaths = state.instancePathCache.getAbsolutePaths(sourceModule);
|
|
auto sinkPaths = state.instancePathCache.getAbsolutePaths(sinkModule);
|
|
|
|
if (sourcePaths.size() != 1 || sinkPaths.size() != 1) {
|
|
auto diag =
|
|
mlir::emitError(source.getLoc())
|
|
<< "This source is involved with a Wiring Problem where the source "
|
|
"or the sink are multiply instantiated and this is not supported.";
|
|
diag.attachNote(sink.getLoc()) << "The sink is here.";
|
|
return failure();
|
|
}
|
|
|
|
FModuleOp lca =
|
|
cast<FModuleOp>(instanceGraph.getTopLevelNode()->getModule());
|
|
auto sources = sourcePaths[0];
|
|
auto sinks = sinkPaths[0];
|
|
while (!sources.empty() && !sinks.empty()) {
|
|
if (sources.top() != sinks.top())
|
|
break;
|
|
auto newLCA = cast<InstanceOp>(*sources.top());
|
|
lca = cast<FModuleOp>(newLCA.getReferencedModule(instanceGraph));
|
|
sources = sources.dropFront();
|
|
sinks = sinks.dropFront();
|
|
}
|
|
|
|
LLVM_DEBUG({
|
|
llvm::dbgs() << " LCA: " << lca.getModuleName() << "\n"
|
|
<< " sourcePath: " << sourcePaths[0] << "\n"
|
|
<< " sinkPaths: " << sinkPaths[0] << "\n";
|
|
});
|
|
|
|
// Pre-populate the connectionMap of the module with the source and sink.
|
|
moduleModifications[sourceModule].connectionMap[index] = source;
|
|
moduleModifications[sinkModule].connectionMap[index] = sink;
|
|
|
|
// Record port types that should be added to each module along the LCA path.
|
|
Type sourceType, sinkType;
|
|
auto useRefTypes =
|
|
!noRefTypePorts &&
|
|
problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer;
|
|
if (useRefTypes) {
|
|
// Use RefType ports if possible
|
|
RefType refType = TypeSwitch<Type, RefType>(source.getType())
|
|
.Case<FIRRTLBaseType>([](FIRRTLBaseType base) {
|
|
return RefType::get(base.getPassiveType());
|
|
})
|
|
.Case<RefType>([](RefType ref) { return ref; });
|
|
sourceType = refType;
|
|
sinkType = refType.getType();
|
|
} else {
|
|
// Use specified port types.
|
|
sourceType = source.getType();
|
|
sinkType = sink.getType();
|
|
|
|
// Types must be connectable, which means FIRRTLType's.
|
|
auto sourceFType = type_dyn_cast<FIRRTLType>(sourceType);
|
|
auto sinkFType = type_dyn_cast<FIRRTLType>(sinkType);
|
|
if (!sourceFType)
|
|
return emitError(source.getLoc())
|
|
<< "Wiring Problem source type \"" << sourceType
|
|
<< "\" must be a FIRRTL type";
|
|
if (!sinkFType)
|
|
return emitError(sink.getLoc())
|
|
<< "Wiring Problem sink type \"" << sinkType
|
|
<< "\" must be a FIRRTL type";
|
|
|
|
// Otherwise they must be identical or FIRRTL type-equivalent
|
|
// (connectable).
|
|
if (sourceFType != sinkFType &&
|
|
!areTypesEquivalent(sinkFType, sourceFType)) {
|
|
// Support tapping mixed alignment -> passive , emulate probe behavior.
|
|
if (auto sourceBaseType = dyn_cast<FIRRTLBaseType>(sourceFType);
|
|
problem.refTypeUsage == WiringProblem::RefTypeUsage::Prefer &&
|
|
sourceBaseType &&
|
|
areTypesEquivalent(sinkFType, sourceBaseType.getPassiveType())) {
|
|
// Change "sourceType" to the passive version that's type-equivalent,
|
|
// this will be used for wiring on the "up" side.
|
|
// This relies on `emitConnect` supporting connecting to the passive
|
|
// version from the original source.
|
|
sourceType = sourceBaseType.getPassiveType();
|
|
} else {
|
|
auto diag = mlir::emitError(source.getLoc())
|
|
<< "Wiring Problem source type " << sourceType
|
|
<< " does not match sink type " << sinkType;
|
|
diag.attachNote(sink.getLoc()) << "The sink is here.";
|
|
return failure();
|
|
}
|
|
}
|
|
}
|
|
// If wiring using references, check that the sink value we connect to is
|
|
// passive.
|
|
if (auto sinkFType = type_dyn_cast<FIRRTLType>(sink.getType());
|
|
sinkFType && type_isa<RefType>(sourceType) &&
|
|
!getBaseType(sinkFType).isPassive())
|
|
return emitError(sink.getLoc())
|
|
<< "Wiring Problem sink type \"" << sink.getType()
|
|
<< "\" must be passive (no flips) when using references";
|
|
|
|
// Record module modifications related to adding ports to modules.
|
|
auto addPorts = [&](igraph::InstancePath insts, Value val, Type tpe,
|
|
Direction dir) -> LogicalResult {
|
|
StringRef name, instName;
|
|
for (auto instNode : llvm::reverse(insts)) {
|
|
auto inst = cast<InstanceOp>(*instNode);
|
|
auto mod = inst.getReferencedModule<FModuleOp>(instanceGraph);
|
|
if (mod.isPublic()) {
|
|
if (!allowAddingPortsOnPublic) {
|
|
auto diag = emitError(
|
|
mod.getLoc(), "cannot wire port through this public module");
|
|
diag.attachNote(source.getLoc()) << "source here";
|
|
diag.attachNote(sink.getLoc()) << "sink here";
|
|
return diag;
|
|
}
|
|
++numPublicPortsWired;
|
|
}
|
|
if (name.empty()) {
|
|
if (problem.newNameHint.empty())
|
|
name = state.getNamespace(mod).newName(
|
|
getFieldName(
|
|
getFieldRefFromValue(val, /*lookThroughCasts=*/true),
|
|
/*nameSafe=*/true)
|
|
.first +
|
|
"__bore");
|
|
else
|
|
name = state.getNamespace(mod).newName(problem.newNameHint);
|
|
} else {
|
|
assert(!instName.empty());
|
|
name = state.getNamespace(mod).newName(instName + "_" + name);
|
|
}
|
|
moduleModifications[mod].portsToAdd.push_back(
|
|
{index, {StringAttr::get(context, name), tpe, dir}});
|
|
instName = inst.getInstanceName();
|
|
}
|
|
return success();
|
|
};
|
|
|
|
// Record the addition of ports.
|
|
if (failed(addPorts(sources, source, sourceType, Direction::Out)) ||
|
|
failed(addPorts(sinks, sink, sinkType, Direction::In)))
|
|
return failure();
|
|
}
|
|
|
|
// Iterate over modules from leaves to roots, applying ModuleModifications to
|
|
// each module.
|
|
LLVM_DEBUG({ llvm::dbgs() << "Updating modules:\n"; });
|
|
for (auto *op : llvm::post_order(instanceGraph.getTopLevelNode())) {
|
|
auto fmodule = dyn_cast<FModuleOp>(*op->getModule());
|
|
// Skip external modules and modules that have no modifications.
|
|
if (!fmodule || !moduleModifications.count(fmodule))
|
|
continue;
|
|
|
|
auto modifications = moduleModifications[fmodule];
|
|
LLVM_DEBUG({
|
|
llvm::dbgs() << " - module: " << fmodule.getModuleName() << "\n";
|
|
llvm::dbgs() << " ports:\n";
|
|
for (auto [index, port] : modifications.portsToAdd) {
|
|
llvm::dbgs() << " - name: " << port.getName() << "\n"
|
|
<< " id: " << index << "\n"
|
|
<< " type: " << port.type << "\n"
|
|
<< " direction: "
|
|
<< (port.direction == Direction::In ? "in" : "out")
|
|
<< "\n";
|
|
}
|
|
});
|
|
|
|
// Add ports to the module after all other existing ports.
|
|
SmallVector<std::pair<unsigned, PortInfo>> newPorts;
|
|
SmallVector<unsigned> problemIndices;
|
|
for (auto [problemIdx, portInfo] : modifications.portsToAdd) {
|
|
// Create the port.
|
|
newPorts.push_back({fmodule.getNumPorts(), portInfo});
|
|
problemIndices.push_back(problemIdx);
|
|
}
|
|
auto originalNumPorts = fmodule.getNumPorts();
|
|
auto portIdx = fmodule.getNumPorts();
|
|
fmodule.insertPorts(newPorts);
|
|
|
|
auto builder = ImplicitLocOpBuilder::atBlockBegin(UnknownLoc::get(context),
|
|
fmodule.getBodyBlock());
|
|
|
|
// Connect each port to the value stored in the connectionMap for this
|
|
// wiring problem index.
|
|
for (auto [problemIdx, portPair] : llvm::zip(problemIndices, newPorts)) {
|
|
Value src = moduleModifications[fmodule].connectionMap[problemIdx];
|
|
assert(src && "there did not exist a driver for the port");
|
|
Value dest = fmodule.getArgument(portIdx++);
|
|
if (failed(connect(src, dest, builder)))
|
|
return failure();
|
|
}
|
|
|
|
// If a U-turn exists, this is an LCA and we need a U-turn connection. These
|
|
// are the last connections made for this module.
|
|
for (auto [problemIdx, dest] : moduleModifications[fmodule].uturns) {
|
|
Value src = moduleModifications[fmodule].connectionMap[problemIdx];
|
|
assert(src && "there did not exist a connection for the u-turn");
|
|
if (failed(connect(src, dest, builder)))
|
|
return failure();
|
|
}
|
|
|
|
// Update the connectionMap of all modules for which we created a port.
|
|
for (auto *inst : instanceGraph.lookup(fmodule)->uses()) {
|
|
InstanceOp useInst = cast<InstanceOp>(inst->getInstance());
|
|
auto enclosingModule = useInst->getParentOfType<FModuleOp>();
|
|
auto clonedInst = useInst.cloneAndInsertPorts(newPorts);
|
|
state.instancePathCache.replaceInstance(useInst, clonedInst);
|
|
// When RAUW-ing, ignore the new ports that we added when replacing (they
|
|
// cannot have uses).
|
|
useInst->replaceAllUsesWith(
|
|
clonedInst.getResults().drop_back(newPorts.size()));
|
|
useInst->erase();
|
|
// Record information in the moduleModifications strucutre for the module
|
|
// _where this is instantiated_. This is done so that when that module is
|
|
// visited later, there will be information available for it to find ports
|
|
// it needs to wire up. If there is already an existing connection, then
|
|
// this is a U-turn.
|
|
for (auto [newPortIdx, problemIdx] : llvm::enumerate(problemIndices)) {
|
|
auto &modifications = moduleModifications[enclosingModule];
|
|
auto newPort = clonedInst.getResult(newPortIdx + originalNumPorts);
|
|
if (modifications.connectionMap.count(problemIdx)) {
|
|
modifications.uturns.push_back({problemIdx, newPort});
|
|
continue;
|
|
}
|
|
modifications.connectionMap[problemIdx] = newPort;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete unused WireOps created by producers of WiringProblems.
|
|
for (auto *op : opsToErase)
|
|
op->erase();
|
|
|
|
return success();
|
|
}
|
|
|
|
// This is the main entrypoint for the lowering pass.
|
|
void LowerAnnotationsPass::runOnOperation() {
|
|
CircuitOp circuit = getOperation();
|
|
SymbolTable modules(circuit);
|
|
|
|
LLVM_DEBUG(debugPassHeader(this) << "\n");
|
|
|
|
// Grab the annotations from a non-standard attribute called "rawAnnotations".
|
|
// This is a temporary location for all annotations that are earmarked for
|
|
// processing by this pass as we migrate annotations from being handled by
|
|
// FIRAnnotations/FIRParser into this pass. While we do this, this pass is
|
|
// not supposed to touch _other_ annotations to enable this pass to be run
|
|
// after FIRAnnotations/FIRParser.
|
|
auto annotations = circuit->getAttrOfType<ArrayAttr>(rawAnnotations);
|
|
if (!annotations)
|
|
return;
|
|
circuit->removeAttr(rawAnnotations);
|
|
|
|
// Populate the worklist in reverse order. This has the effect of causing
|
|
// annotations to be processed in the order in which they appear in the
|
|
// original JSON.
|
|
for (auto anno : llvm::reverse(annotations.getValue()))
|
|
worklistAttrs.push_back(cast<DictionaryAttr>(anno));
|
|
|
|
size_t numFailures = 0;
|
|
size_t numAdded = 0;
|
|
auto addToWorklist = [&](DictionaryAttr anno) {
|
|
++numAdded;
|
|
worklistAttrs.push_back(anno);
|
|
};
|
|
InstancePathCache instancePathCache(getAnalysis<InstanceGraph>());
|
|
ApplyState state{circuit, modules, addToWorklist, instancePathCache,
|
|
noRefTypePorts};
|
|
LLVM_DEBUG(llvm::dbgs() << "Processing annotations:\n");
|
|
while (!worklistAttrs.empty()) {
|
|
auto attr = worklistAttrs.pop_back_val();
|
|
if (applyAnnotation(attr, state).failed())
|
|
++numFailures;
|
|
}
|
|
|
|
if (failed(legacyToWiringProblems(state)))
|
|
++numFailures;
|
|
|
|
if (failed(solveWiringProblems(state)))
|
|
++numFailures;
|
|
|
|
// Update statistics
|
|
numRawAnnotations += annotations.size();
|
|
numAddedAnnos += numAdded;
|
|
numAnnos += numAdded + annotations.size();
|
|
numReusedHierPathOps += state.numReusedHierPaths;
|
|
|
|
if (numFailures)
|
|
signalPassFailure();
|
|
}
|
|
|
|
/// This is the pass constructor.
|
|
std::unique_ptr<mlir::Pass> circt::firrtl::createLowerFIRRTLAnnotationsPass(
|
|
bool ignoreAnnotationUnknown, bool ignoreAnnotationClassless,
|
|
bool noRefTypePorts, bool allowAddingPortsOnPublic) {
|
|
auto pass = std::make_unique<LowerAnnotationsPass>();
|
|
pass->ignoreAnnotationUnknown = ignoreAnnotationUnknown;
|
|
pass->ignoreAnnotationClassless = ignoreAnnotationClassless;
|
|
pass->noRefTypePorts = noRefTypePorts;
|
|
pass->allowAddingPortsOnPublic = allowAddingPortsOnPublic;
|
|
return pass;
|
|
}
|