Annotation Scattering and trivial processing Pass (#1808)

Add a pass to handle all annotation scattering. This pass is table driven, with customizable scattering per-annotation-class. When this is fleshed out, it will replace the annotation handling code in the parser.

Right now, this supports a couple testing annotation to make test cases against.

Until this is live in the pipelines, add an option to the parser to bypass annotation handling and scattering to enable testing.
This commit is contained in:
Andrew Lenharth 2021-09-22 11:55:55 -07:00 committed by GitHub
parent ead85fec87
commit c4ac7b8fb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 980 additions and 123 deletions

View File

@ -30,6 +30,9 @@ struct FIRParserOptions {
/// If this is set to true, the @info locators are ignored, and the locations
/// are set to the location in the .fir file.
bool ignoreInfoLocators = false;
/// If this is set to true, the annotations are just attached to the circuit
/// and not scattered or processed.
bool rawAnnotations = false;
};
mlir::OwningModuleRef importFIRFile(llvm::SourceMgr &sourceMgr,

View File

@ -28,6 +28,9 @@ class FModuleLike;
/// Return the name of the attribute used for annotations on FIRRTL ops.
inline StringRef getAnnotationAttrName() { return "annotations"; }
/// Return the name of the attribute used for port annotations on FIRRTL ops.
inline StringRef getPortAnnotationAttrName() { return "portAnnotations"; }
/// Return the name of the dialect-prefixed attribute used for annotations.
inline StringRef getDialectAnnotationAttrName() { return "firrtl.annotations"; }

View File

@ -41,7 +41,7 @@ def FModuleLike : OpInterface<"FModuleLike"> {
return $_op.portNames()[portIndex].template cast<StringAttr>();
}]>,
InterfaceMethod<"Get infromation about all ports",
InterfaceMethod<"Get information about all ports",
"SmallVector<PortInfo>", "getPorts">,
InterfaceMethod<"Get the module port directions",

4
include/circt/Dialect/FIRRTL/Passes.h Normal file → Executable file
View File

@ -24,6 +24,10 @@ class Pass;
namespace circt {
namespace firrtl {
std::unique_ptr<mlir::Pass>
createLowerFIRRTLAnnotationsPass(bool ignoreUnhandledAnnotations = false,
bool ignoreClasslessAnnotations = false);
std::unique_ptr<mlir::Pass> createLowerFIRRTLTypesPass();
std::unique_ptr<mlir::Pass> createLowerBundleVectorTypesPass();

16
include/circt/Dialect/FIRRTL/Passes.td Normal file → Executable file
View File

@ -15,6 +15,22 @@
include "mlir/Pass/PassBase.td"
def LowerFIRRTLAnnotations : Pass<"firrtl-lower-annotations", "firrtl::CircuitOp"> {
let summary = "Lower FIRRTL annotations to usable entities";
let description = [{
Lower FIRRTL annotations to usable forms. FIRRTL annotations are a big bag
of semi-structured, irregular json. This pass normalizes all supported
annotations and annotation paths.
}];
let constructor = "circt::firrtl::createLowerFIRRTLAnnotationsPass()";
let options = [
Option<"ignoreAnnotationClassless", "disable-annotation-classless", "bool", "false",
"Ignore classless annotations.">,
Option<"ignoreAnnotationUnknown", "disable-annotation-unknown", "bool", "true",
"Ignore unknown annotations.">
];
}
def LowerFIRRTLTypes : Pass<"firrtl-lower-types", "firrtl::CircuitOp"> {
let summary = "Lower FIRRTL types to ground types";
let description = [{

View File

@ -152,6 +152,14 @@ DeclKind firrtl::getDeclarationKind(Value val) {
.Default([](auto) { return DeclKind::Other; });
}
size_t firrtl::getNumPorts(Operation *op) {
if (auto extmod = dyn_cast<FExtModuleOp>(op))
return extmod.getType().getInputs().size();
if (auto mod = dyn_cast<FModuleOp>(op))
return mod.getBodyBlock()->getArguments().size();
return op->getNumResults();
}
//===----------------------------------------------------------------------===//
// CircuitOp
//===----------------------------------------------------------------------===//

View File

@ -251,6 +251,61 @@ static A tryGetAs(DictionaryAttr &dict, DictionaryAttr &root, StringRef key,
return valueA;
}
/// Convert arbitrary JSON to an MLIR Attribute.
static Attribute convertJSONToAttribute(MLIRContext *context,
json::Value &value, json::Path p) {
// String or quoted JSON
if (auto a = value.getAsString()) {
// Test to see if this might be quoted JSON (a string that is actually
// JSON). Sometimes FIRRTL developers will do this to serialize objects
// that the Scala FIRRTL Compiler doesn't know about.
auto unquotedValue = json::parse(a.getValue());
auto err = unquotedValue.takeError();
// If this parsed without an error, then it's more JSON and recurse on
// that.
if (!err)
return convertJSONToAttribute(context, unquotedValue.get(), p);
// If there was an error, then swallow it and handle this as a string.
handleAllErrors(std::move(err), [&](const json::ParseError &a) {});
return StringAttr::get(context, a.getValue());
}
// Integer
if (auto a = value.getAsInteger())
return IntegerAttr::get(IntegerType::get(context, 64), a.getValue());
// Float
if (auto a = value.getAsNumber())
return FloatAttr::get(mlir::FloatType::getF64(context), a.getValue());
// Boolean
if (auto a = value.getAsBoolean())
return BoolAttr::get(context, a.getValue());
// Null
if (auto a = value.getAsNull())
return mlir::UnitAttr::get(context);
// Object
if (auto a = value.getAsObject()) {
NamedAttrList metadata;
for (auto b : *a)
metadata.append(
b.first, convertJSONToAttribute(context, b.second, p.field(b.first)));
return DictionaryAttr::get(context, metadata);
}
// Array
if (auto a = value.getAsArray()) {
SmallVector<Attribute> metadata;
for (size_t i = 0, e = (*a).size(); i != e; ++i)
metadata.push_back(convertJSONToAttribute(context, (*a)[i], p.index(i)));
return ArrayAttr::get(context, metadata);
}
llvm_unreachable("Impossible unhandled JSON type");
};
/// Deserialize a JSON value into FIRRTL Annotations. Annotations are
/// represented as a Target-keyed arrays of attributes. The input JSON value is
/// checked, at runtime, to be an array of objects. Returns true if successful,
@ -294,61 +349,6 @@ bool circt::firrtl::fromJSON(json::Value &value, StringRef circuitTarget,
return llvm::Optional<std::string>(target);
};
/// Convert arbitrary JSON to an MLIR Attribute.
std::function<Attribute(json::Value &, json::Path)> convertJSONToAttribute =
[&](json::Value &value, json::Path p) -> Attribute {
// String or quoted JSON
if (auto a = value.getAsString()) {
// Test to see if this might be quoted JSON (a string that is actually
// JSON). Sometimes FIRRTL developers will do this to serialize objects
// that the Scala FIRRTL Compiler doesn't know about.
auto unquotedValue = json::parse(a.getValue());
auto err = unquotedValue.takeError();
// If this parsed without an error, then it's more JSON and recurse on
// that.
if (!err)
return convertJSONToAttribute(unquotedValue.get(), p);
// If there was an error, then swallow it and handle this as a string.
handleAllErrors(std::move(err), [&](const json::ParseError &a) {});
return StringAttr::get(context, a.getValue());
}
// Integer
if (auto a = value.getAsInteger())
return IntegerAttr::get(IntegerType::get(context, 64), a.getValue());
// Float
if (auto a = value.getAsNumber())
return FloatAttr::get(mlir::FloatType::getF64(context), a.getValue());
// Boolean
if (auto a = value.getAsBoolean())
return BoolAttr::get(context, a.getValue());
// Null
if (auto a = value.getAsNull())
return mlir::UnitAttr::get(context);
// Object
if (auto a = value.getAsObject()) {
NamedAttrList metadata;
for (auto b : *a)
metadata.append(b.first,
convertJSONToAttribute(b.second, p.field(b.first)));
return DictionaryAttr::get(context, metadata);
}
// Array
if (auto a = value.getAsArray()) {
SmallVector<Attribute> metadata;
for (size_t i = 0, e = (*a).size(); i != e; ++i)
metadata.push_back(convertJSONToAttribute((*a)[i], p.index(i)));
return ArrayAttr::get(context, metadata);
}
llvm_unreachable("Impossible unhandled JSON type");
};
// The JSON value must be an array of objects. Anything else is reported as
// invalid.
auto array = value.getAsArray();
@ -394,7 +394,7 @@ bool circt::firrtl::fromJSON(json::Value &value, StringRef circuitTarget,
splitAndAppendTarget(metadata, std::get<0>(NLATargets.back()), context)
.first;
for (auto field : *object) {
if (auto value = convertJSONToAttribute(field.second, p)) {
if (auto value = convertJSONToAttribute(context, field.second, p)) {
metadata.append(field.first, value);
continue;
}
@ -1071,3 +1071,47 @@ bool circt::firrtl::scatterCustomAnnotations(
return true;
}
/// Deserialize a JSON value into FIRRTL Annotations. Annotations are
/// represented as a Target-keyed arrays of attributes. The input JSON value is
/// checked, at runtime, to be an array of objects. Returns true if successful,
/// false if unsuccessful.
bool circt::firrtl::fromJSONRaw(json::Value &value, StringRef circuitTarget,
SmallVectorImpl<Attribute> &attrs,
json::Path path, MLIRContext *context) {
// The JSON value must be an array of objects. Anything else is reported as
// invalid.
auto array = value.getAsArray();
if (!array) {
path.report(
"Expected annotations to be an array, but found something else.");
return false;
}
// Build an array of annotations.
for (size_t i = 0, e = (*array).size(); i != e; ++i) {
auto object = (*array)[i].getAsObject();
auto p = path.index(i);
if (!object) {
p.report("Expected annotations to be an array of objects, but found an "
"array of something else.");
return false;
}
// Build up the Attribute to represent the Annotation
NamedAttrList metadata;
for (auto field : *object) {
if (auto value = convertJSONToAttribute(context, field.second, p)) {
metadata.append(field.first, value);
continue;
}
return false;
}
attrs.push_back(DictionaryAttr::get(context, metadata));
}
return true;
}

View File

@ -40,6 +40,9 @@ bool scatterCustomAnnotations(llvm::StringMap<ArrayAttr> &annotationMap,
CircuitOp circuit, unsigned &annotationID,
Location loc, size_t &nlaNumber);
bool fromJSONRaw(llvm::json::Value &value, StringRef circuitTarget,
SmallVectorImpl<Attribute> &attrs, llvm::json::Path path,
MLIRContext *context);
} // namespace firrtl
} // namespace circt

View File

@ -2222,8 +2222,10 @@ ParseResult FIRStmtParser::parseMemPort(MemDirAttr direction) {
"memory port should have behavioral memory type");
auto resultType = memVType.getElementType();
auto annotations = getAnnotations(getModuleTarget() + ">" + id, startLoc,
moduleContext.targetsInModule, resultType);
ArrayAttr annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations = getAnnotations(getModuleTarget() + ">" + id, startLoc,
moduleContext.targetsInModule, resultType);
locationProcessor.setLoc(startLoc);
@ -2601,19 +2603,24 @@ ParseResult FIRStmtParser::parseInstance() {
resultNamesAndTypes.push_back({port.name, port.type});
}
// Combine annotations that are ReferenceTargets and InstanceTargets. By
// example, this will lookup all annotations with either of the following
// formats:
// ~Foo|Foo>bar
// ~Foo|Foo/bar:Bar
auto annotations = getSplitAnnotations(
{getModuleTarget() + ">" + id,
getModuleTarget() + "/" + id + ":" + moduleName},
startTok.getLoc(), resultNamesAndTypes, moduleContext.targetsInModule);
InstanceOp result;
if (getConstants().options.rawAnnotations) {
result = builder.create<InstanceOp>(resultTypes, moduleName, id);
} else {
// Combine annotations that are ReferenceTargets and InstanceTargets. By
// example, this will lookup all annotations with either of the following
// formats:
// ~Foo|Foo>bar
// ~Foo|Foo/bar:Bar
auto annotations = getSplitAnnotations(
{getModuleTarget() + ">" + id,
getModuleTarget() + "/" + id + ":" + moduleName},
startTok.getLoc(), resultNamesAndTypes, moduleContext.targetsInModule);
auto result = builder.create<InstanceOp>(resultTypes, moduleName, id,
annotations.first.getValue(),
annotations.second.getValue());
result = builder.create<InstanceOp>(resultTypes, moduleName, id,
annotations.first.getValue(),
annotations.second.getValue());
}
// Since we are implicitly unbundling the instance results, we need to keep
// track of the mapping from bundle fields to results in the unbundledValues
@ -2656,9 +2663,11 @@ ParseResult FIRStmtParser::parseCombMem() {
auto memType = CMemoryType::get(vectorType.getElementType(),
vectorType.getNumElements());
auto annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto result = builder.create<CombMemOp>(memType, id, annotations);
return moduleContext.addSymbolEntry(id, result, startTok.getLoc());
@ -2693,9 +2702,11 @@ ParseResult FIRStmtParser::parseSeqMem() {
auto memType = CMemoryType::get(getContext(), vectorType.getElementType(),
vectorType.getNumElements());
auto annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto result = builder.create<SeqMemOp>(memType, ruw, id, annotations);
return moduleContext.addSymbolEntry(id, result, startTok.getLoc());
@ -2820,14 +2831,25 @@ ParseResult FIRStmtParser::parseMem(unsigned memIndent) {
locationProcessor.setLoc(startTok.getLoc());
auto annotations =
getSplitAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
ports, moduleContext.targetsInModule);
MemOp result;
if (getConstants().options.rawAnnotations) {
SmallVector<Attribute, 4> portAnnotations(ports.size(),
ArrayAttr::get(getContext(), {}));
auto result =
builder.create<MemOp>(resultTypes, readLatency, writeLatency, depth, ruw,
builder.getArrayAttr(resultNames), id,
annotations.first, annotations.second);
result = builder.create<MemOp>(
resultTypes, readLatency, writeLatency, depth, ruw,
builder.getArrayAttr(resultNames), id, getConstants().emptyArrayAttr,
builder.getArrayAttr(portAnnotations));
} else {
auto annotations =
getSplitAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
ports, moduleContext.targetsInModule);
result =
builder.create<MemOp>(resultTypes, readLatency, writeLatency, depth,
ruw, builder.getArrayAttr(resultNames), id,
annotations.first, annotations.second);
}
UnbundledValueEntry unbundledValueEntry;
unbundledValueEntry.reserve(result.getNumResults());
@ -2876,9 +2898,11 @@ ParseResult FIRStmtParser::parseNode() {
return failure();
}
auto annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, initializerType);
auto annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, initializerType);
Value result = builder.create<NodeOp>(initializer.getType(), initializer, id,
annotations);
@ -2903,9 +2927,11 @@ ParseResult FIRStmtParser::parseWire() {
locationProcessor.setLoc(startTok.getLoc());
auto annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
auto result = builder.create<WireOp>(type, id, annotations);
return moduleContext.addSymbolEntry(id, result, startTok.getLoc());
@ -2993,9 +3019,11 @@ ParseResult FIRStmtParser::parseRegister(unsigned regIndent) {
locationProcessor.setLoc(startTok.getLoc());
auto annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
ArrayAttr annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations =
getAnnotations(getModuleTarget() + ">" + id, startTok.getLoc(),
moduleContext.targetsInModule, type);
Value result;
if (resetSignal)
@ -3029,6 +3057,9 @@ private:
ParseResult importAnnotations(CircuitOp circuit, SMLoc loc,
StringRef circuitTarget,
StringRef annotationsStr, size_t &nlaNumber);
ParseResult importAnnotationsRaw(SMLoc loc, StringRef circuitTarget,
StringRef annotationsStr,
SmallVector<Attribute> &attrs);
ParseResult parseModule(CircuitOp circuit, StringRef circuitTarget,
unsigned indent);
@ -3058,6 +3089,35 @@ private:
};
} // end anonymous namespace
ParseResult
FIRCircuitParser::importAnnotationsRaw(SMLoc loc, StringRef circuitTarget,
StringRef annotationsStr,
SmallVector<Attribute> &attrs) {
auto annotations = json::parse(annotationsStr);
if (auto err = annotations.takeError()) {
handleAllErrors(std::move(err), [&](const json::ParseError &a) {
auto diag = emitError(loc, "Failed to parse JSON Annotations");
diag.attachNote() << a.message();
});
return failure();
}
json::Path::Root root;
llvm::StringMap<ArrayAttr> thisAnnotationMap;
if (!fromJSONRaw(annotations.get(), circuitTarget, attrs, root,
getContext())) {
auto diag = emitError(loc, "Invalid/unsupported annotation format");
std::string jsonErrorMessage =
"See inline comments for problem area in JSON:\n";
llvm::raw_string_ostream s(jsonErrorMessage);
root.printErrorContext(annotations.get(), s);
diag.attachNote() << jsonErrorMessage;
return failure();
}
return success();
}
ParseResult FIRCircuitParser::importAnnotations(CircuitOp circuit, SMLoc loc,
StringRef circuitTarget,
@ -3156,10 +3216,11 @@ FIRCircuitParser::parsePortList(SmallVectorImpl<PortInfo> &resultPorts,
// compile time creating too many unique locations.
info.setDefaultLoc(defaultLoc);
AnnotationSet annotations(
getAnnotations(moduleTarget + ">" + name.getValue(), info.getFIRLoc(),
getConstants().targetSet, type));
AnnotationSet annotations(getContext());
if (!getConstants().options.rawAnnotations)
annotations = AnnotationSet(
getAnnotations(moduleTarget + ">" + name.getValue(), info.getFIRLoc(),
getConstants().targetSet, type));
resultPorts.push_back(
{name, type, direction::get(isOutput), info.getLoc(), annotations});
resultPortLocs.push_back(info.getFIRLoc());
@ -3193,8 +3254,10 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit,
return failure();
auto moduleTarget = (circuitTarget + "|" + name.getValue()).str();
ArrayAttr annotations = getAnnotations({moduleTarget}, info.getFIRLoc(),
getConstants().targetSet);
ArrayAttr annotations = getConstants().emptyArrayAttr;
if (!getConstants().options.rawAnnotations)
annotations = getAnnotations({moduleTarget}, info.getFIRLoc(),
getConstants().targetSet);
if (parseToken(FIRToken::colon, "expected ':' in module definition") ||
info.parseOptionalInfo() ||
@ -3399,27 +3462,52 @@ ParseResult FIRCircuitParser::parseCircuit(
std::string circuitTarget = "~" + name.getValue().str();
size_t nlaNumber = 0;
// Deal with any inline annotations, if they exist. These are processed first
// to place any annotations from an annotation file *after* the inline
// annotations. While arbitrary, this makes the annotation file have "append"
// semantics.
if (!inlineAnnotations.empty())
if (importAnnotations(circuit, inlineAnnotationsLoc, circuitTarget,
inlineAnnotations, nlaNumber))
return failure();
ArrayAttr annotations;
if (getConstants().options.rawAnnotations) {
SmallVector<Attribute> rawAnno;
// Deal with any inline annotations, if they exist. These are processed
// first to place any annotations from an annotation file *after* the inline
// annotations. While arbitrary, this makes the annotation file have
// "append" semantics.
if (!inlineAnnotations.empty())
if (importAnnotationsRaw(inlineAnnotationsLoc, circuitTarget,
inlineAnnotations, rawAnno))
return failure();
// Deal with the annotation file if one was specified
for (auto annotationsBuf : annotationsBufs)
if (importAnnotations(circuit, info.getFIRLoc(), circuitTarget,
annotationsBuf->getBuffer(), nlaNumber))
return failure();
// Deal with the annotation file if one was specified
for (auto annotationsBuf : annotationsBufs)
if (importAnnotationsRaw(info.getFIRLoc(), circuitTarget,
annotationsBuf->getBuffer(), rawAnno))
return failure();
// Get annotations associated with this circuit. These are either:
// 1. Annotations with no target (which we use "~" to identify)
// 2. Annotations targeting the circuit, e.g., "~Foo"
ArrayAttr annotations = getAnnotations({"~", circuitTarget}, info.getFIRLoc(),
getConstants().targetSet);
// Get annotations associated with this circuit. These are either:
// 1. Annotations with no target (which we use "~" to identify)
// 2. Annotations targeting the circuit, e.g., "~Foo"
annotations = b.getArrayAttr(rawAnno);
} else {
// Deal with any inline annotations, if they exist. These are processed
// first to place any annotations from an annotation file *after* the inline
// annotations. While arbitrary, this makes the annotation file have
// "append" semantics.
if (!inlineAnnotations.empty())
if (importAnnotations(circuit, inlineAnnotationsLoc, circuitTarget,
inlineAnnotations, nlaNumber))
return failure();
// Deal with the annotation file if one was specified
for (auto annotationsBuf : annotationsBufs)
if (importAnnotations(circuit, info.getFIRLoc(), circuitTarget,
annotationsBuf->getBuffer(), nlaNumber))
return failure();
// Get annotations associated with this circuit. These are either:
// 1. Annotations with no target (which we use "~" to identify)
// 2. Annotations targeting the circuit, e.g., "~Foo"
annotations = getAnnotations({"~", circuitTarget}, info.getFIRLoc(),
getConstants().targetSet);
}
circuit->setAttr("annotations", annotations);
deferredModules.reserve(16);

1
lib/Dialect/FIRRTL/Transforms/CMakeLists.txt Normal file → Executable file
View File

@ -8,6 +8,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
IMConstProp.cpp
InferResets.cpp
InferWidths.cpp
LowerAnnotations.cpp
LowerCHIRRTL.cpp
LowerTypes.cpp
ModuleInliner.cpp

View File

@ -0,0 +1,659 @@
//===- 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 "PassDetails.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "mlir/IR/Diagnostics.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/StringExtras.h"
using namespace circt;
using namespace firrtl;
namespace {
/// Stores an index into an aggregate.
struct TargetToken {
StringRef name;
bool isIndex;
};
/// The parsed annotation path.
struct TokenAnnoTarget {
StringRef circuit;
SmallVector<std::pair<StringRef, StringRef>> instances;
StringRef module;
// The final name of the target
StringRef name;
// Any aggregates indexed.
SmallVector<TargetToken> component;
};
/// The (local) target of an annotation, resolved.
struct AnnoTarget {
Operation *op;
size_t portNum;
unsigned fieldIdx = 0;
AnnoTarget(Operation *op) : op(op), portNum(~0UL) {}
AnnoTarget(Operation *mod, size_t portNum) : op(mod), portNum(portNum) {}
AnnoTarget() : op(nullptr), portNum(~0UL) {}
operator bool() const { return op != nullptr; }
bool isPort() const { return op && portNum != ~0UL; }
bool isInstance() const { return op && isa<InstanceOp>(op); }
FModuleOp getModule() const {
if (auto mod = dyn_cast<FModuleOp>(op))
return mod;
return op->getParentOfType<FModuleOp>();
}
FIRRTLType getType() const {
if (!op)
return FIRRTLType();
if (portNum != ~0UL) {
if (auto mod = dyn_cast<FModuleLike>(op))
return mod.portType(portNum).getSubTypeByFieldID(fieldIdx);
if (isa<MemOp, InstanceOp>(op))
return op->getResult(portNum)
.getType()
.cast<FIRRTLType>()
.getSubTypeByFieldID(fieldIdx);
llvm_unreachable("Unknown port instruction");
}
if (op->getNumResults() == 0)
return FIRRTLType();
return op->getResult(0).getType().cast<FIRRTLType>().getSubTypeByFieldID(
fieldIdx);
}
};
// The potentially non-local resolved annotation.
struct AnnoPathValue {
SmallVector<InstanceOp> instances;
AnnoTarget ref;
AnnoPathValue() = default;
AnnoPathValue(CircuitOp op) : ref(op) {}
AnnoPathValue(Operation *op) : ref(op) {}
AnnoPathValue(const SmallVectorImpl<InstanceOp> &insts, AnnoTarget b)
: instances(insts.begin(), insts.end()), ref(b) {}
bool isLocal() const { return instances.empty(); }
template <typename... T>
bool isOpOfType() const {
if (!ref || ref.isPort())
return false;
return isa<T...>(ref.op);
}
};
/// State threaded through functions for resolving and applying annotations.
struct ApplyState {
CircuitOp circuit;
SymbolTable &symTbl;
llvm::function_ref<void(DictionaryAttr)> addToWorklistFn;
};
} // namespace
/// Abstraction over namable things. Get a name in a generic way.
static StringRef getName(Operation *op) {
return TypeSwitch<Operation *, StringRef>(op)
.Case<InstanceOp, MemOp, NodeOp, RegOp, RegResetOp, WireOp, CombMemOp,
SeqMemOp, MemoryPortOp>([&](auto nop) { return nop.name(); })
.Default([](auto &) {
llvm_unreachable("unnamable op");
return "";
});
}
/// Abstraction over namable things. Do they have names?
static bool hasName(StringRef name, Operation *op) {
return TypeSwitch<Operation *, bool>(op)
.Case<InstanceOp, MemOp, NodeOp, RegOp, RegResetOp, WireOp, CombMemOp,
SeqMemOp, MemoryPortOp>(
[&](auto nop) { return nop.name() == name; })
.Default([](auto &) { return false; });
}
/// Find a matching name in an operation (usually FModuleOp). This walk could
/// be cached in the future. This finds a port or operation for a given name.
static AnnoTarget findNamedThing(StringRef name, Operation *op) {
AnnoTarget retval;
auto nameChecker = [name, &retval](Operation *op) -> WalkResult {
if (auto mod = dyn_cast<FModuleLike>(op)) {
// Check the ports.
auto ports = mod.getPorts();
for (size_t i = 0, e = ports.size(); i != e; ++i)
if (ports[i].name.getValue() == name) {
retval = AnnoTarget{op, i};
return WalkResult::interrupt();
}
return WalkResult::advance();
}
if (hasName(name, op)) {
retval = AnnoTarget{op};
return WalkResult::interrupt();
}
return WalkResult::advance();
};
op->walk(nameChecker);
return retval;
}
/// 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, ArrayRef<NamedAttribute> anno) {
DictionaryAttr annotation;
if (ref.fieldIdx) {
SmallVector<NamedAttribute> annoField(anno.begin(), anno.end());
annoField.emplace_back(
Identifier::get("circt.fieldID", ref.op->getContext()),
IntegerAttr::get(
IntegerType::get(ref.op->getContext(), 32, IntegerType::Signless),
ref.fieldIdx));
annotation = DictionaryAttr::get(ref.op->getContext(), annoField);
} else {
annotation = DictionaryAttr::get(ref.op->getContext(), anno);
}
if (!ref.isPort()) {
auto newAnno = appendArrayAttr(getAnnotationsFrom(ref.op), annotation);
ref.op->setAttr(getAnnotationAttrName(), newAnno);
return;
}
auto portAnnoRaw = ref.op->getAttr(getPortAnnotationAttrName());
ArrayAttr portAnno = portAnnoRaw.dyn_cast_or_null<ArrayAttr>();
if (!portAnno || portAnno.size() != getNumPorts(ref.op)) {
SmallVector<Attribute> emptyPortAttr(
getNumPorts(ref.op), ArrayAttr::get(ref.op->getContext(), {}));
portAnno = ArrayAttr::get(ref.op->getContext(), emptyPortAttr);
}
portAnno = replaceArrayAttrElement(
portAnno, ref.portNum,
appendArrayAttr(portAnno[ref.portNum].dyn_cast<ArrayAttr>(), annotation));
ref.op->setAttr("portAnnotations", portAnno);
}
// Some types have been expanded so the first layer of aggregate path is
// a return value.
static LogicalResult updateExpandedPort(StringRef field, AnnoTarget &entity) {
if (auto mem = dyn_cast<MemOp>(entity.op))
for (size_t p = 0, pe = mem.portNames().size(); p < pe; ++p)
if (mem.getPortNameStr(p) == field) {
entity.portNum = p;
return success();
}
if (auto inst = dyn_cast<InstanceOp>(entity.op))
for (size_t p = 0, pe = inst.getNumResults(); p < pe; ++p)
if (inst.getPortNameStr(p) == field) {
entity.portNum = p;
return success();
}
entity.op->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 LogicalResult updateStruct(StringRef field, AnnoTarget &entity) {
// The first field for some ops refers to expanded return values.
if (isa<MemOp, InstanceOp>(entity.op) && entity.portNum == ~0UL)
return updateExpandedPort(field, entity);
auto bundle = entity.getType().dyn_cast<BundleType>();
if (!bundle)
return entity.op->emitError("field access '")
<< field << "' into non-bundle type '" << bundle << "'";
if (auto idx = bundle.getElementIndex(field)) {
entity.fieldIdx += bundle.getFieldID(*idx);
return success();
}
return entity.op->emitError("cannot resolve field '")
<< field << "' in subtype '" << bundle << "'";
}
/// Try to resolve an array index from a target given the type of the resolved
/// target.
static LogicalResult updateArray(StringRef indexStr, AnnoTarget &entity) {
size_t index;
if (indexStr.getAsInteger(10, index)) {
entity.op->emitError("Cannot convert '") << indexStr << "' to an integer";
return failure();
}
auto vec = entity.getType().dyn_cast<FVectorType>();
if (!vec)
return entity.op->emitError("index access '")
<< index << "' into non-vector type '" << vec << "'";
entity.fieldIdx += vec.getFieldID(index);
return success();
}
/// Convert a parsed target string to a resolved target structure. This
/// resolves all names and aggregates from a parsed target.
Optional<AnnoPathValue> resolveEntities(TokenAnnoTarget path,
ApplyState state) {
// Validate circuit name.
if (!path.circuit.empty() && state.circuit.name() != path.circuit) {
state.circuit->emitError("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(state.circuit);
}
// Resolve all instances for non-local paths.
SmallVector<InstanceOp> instances;
for (auto p : path.instances) {
auto mod = state.symTbl.lookup<FModuleOp>(p.first);
if (!mod) {
state.circuit->emitError("module doesn't exist '") << p.first << '\'';
return {};
}
auto resolved = findNamedThing(p.second, mod);
if (!resolved.isInstance()) {
state.circuit.emitError("cannot find instance '")
<< p.second << "' in '" << mod.getName() << "'";
return {};
}
instances.push_back(cast<InstanceOp>(resolved.op));
}
// The final module is where the named target is (or is the named target).
auto mod = state.symTbl.lookup<FModuleOp>(path.module);
if (!mod) {
state.circuit->emitError("module doesn't exist '") << path.module << '\'';
return {};
}
AnnoTarget ref;
if (path.name.empty()) {
assert(path.component.empty());
ref = AnnoTarget(mod);
} else {
ref = findNamedThing(path.name, mod);
if (!ref) {
state.circuit->emitError("cannot find name '")
<< path.name << "' in " << mod.getName();
return {};
}
}
// If we have aggregate specifiers, resolve those now.
for (auto agg : path.component) {
if (agg.isIndex) {
if (failed(updateArray(agg.name, ref)))
return {};
} else {
if (failed(updateStruct(agg.name, ref)))
return {};
}
}
return AnnoPathValue(instances, ref);
}
/// Return an input \p target string in canonical form. This converts a Legacy
/// Annotation (e.g., A.B.C) into a modern annotation (e.g., ~A|B>C). Trailing
/// subfield/subindex references are preserved.
static std::string 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;
}
/// split a target string into it constituent parts. This is the primary parser
/// for targets.
static Optional<TokenAnnoTarget> tokenizePath(StringRef origTarget) {
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;
}
/// 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(AnnoPathValue target, ApplyState state) {
OpBuilder b(state.circuit.getBodyRegion());
SmallVector<Attribute> mods;
SmallVector<Attribute> insts;
for (auto inst : target.instances) {
mods.push_back(FlatSymbolRefAttr::get(inst->getParentOfType<FModuleOp>()));
insts.push_back(StringAttr::get(state.circuit.getContext(), inst.name()));
}
mods.push_back(FlatSymbolRefAttr::get(target.ref.getModule()));
insts.push_back(
StringAttr::get(state.circuit.getContext(), getName(target.ref.op)));
auto modAttr = ArrayAttr::get(state.circuit.getContext(), mods);
auto instAttr = ArrayAttr::get(state.circuit.getContext(), insts);
auto nla = b.create<NonLocalAnchor>(state.circuit.getLoc(), "nla", modAttr,
instAttr);
state.symTbl.insert(nla);
return FlatSymbolRefAttr::get(nla);
}
/// 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(AnnoPathValue target,
ApplyState state) {
FlatSymbolRefAttr sym = buildNLA(target, state);
NamedAttrList pathmetadata;
pathmetadata.append("circt.nonlocal", sym);
pathmetadata.append(
"class", StringAttr::get(state.circuit.getContext(), "circt.nonlocal"));
for (auto item : target.instances)
addAnnotation(AnnoTarget(item), pathmetadata);
return sym;
}
////////////////////////////////////////////////////////////////////////////////
// Standard Utility Resolvers
////////////////////////////////////////////////////////////////////////////////
/// Always resolve to the circuit, ignoring the annotation.
static Optional<AnnoPathValue> noResolve(DictionaryAttr anno,
ApplyState state) {
return AnnoPathValue(state.circuit);
}
/// Implementation of standard resolution. First parses the target path, then
/// resolves it.
static Optional<AnnoPathValue> stdResolveImpl(StringRef rawPath,
ApplyState state) {
auto pathStr = canonicalizeTarget(rawPath);
StringRef path{pathStr};
auto tokens = tokenizePath(path);
if (!tokens) {
state.circuit->emitError("Cannot tokenize annotation path ") << rawPath;
return {};
}
return resolveEntities(*tokens, state);
}
/// (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).
static Optional<AnnoPathValue> stdResolve(DictionaryAttr anno,
ApplyState state) {
auto target = anno.getNamed("target");
if (!target) {
state.circuit.emitError("No target field in annotation ") << anno;
return {};
}
if (!target->second.isa<StringAttr>()) {
state.circuit.emitError(
"Target field in annotation doesn't contain string ")
<< anno;
return {};
}
return stdResolveImpl(target->second.cast<StringAttr>().getValue(), state);
}
/// Resolves with target, if it exists. If not, resolves to the circuit.
static Optional<AnnoPathValue> tryResolve(DictionaryAttr anno,
ApplyState state) {
auto target = anno.getNamed("target");
if (target)
return stdResolveImpl(target->second.cast<StringAttr>().getValue(), state);
return AnnoPathValue(state.circuit);
}
////////////////////////////////////////////////////////////////////////////////
// Standard Utility Appliers
////////////////////////////////////////////////////////////////////////////////
/// A generic applier that drops the annotation.
static LogicalResult
ignoreAnno(AnnoPathValue target, DictionaryAttr anno,
llvm::function_ref<void(ArrayAttr)> addToWorklist) {
return success();
}
/// An applier which puts the annotation on the target and drops the 'target'
/// field from the annotaiton. Optionally handles non-local annotations.
static LogicalResult applyWithoutTargetImpl(AnnoPathValue target,
DictionaryAttr anno,
ApplyState state,
bool allowNonLocal) {
if (!allowNonLocal && !target.isLocal())
return failure();
SmallVector<NamedAttribute> newAnnoAttrs;
for (auto &na : anno) {
if (na.first != "target") {
newAnnoAttrs.push_back(na);
} else if (!target.isLocal()) {
auto sym = scatterNonLocalPath(target, state);
newAnnoAttrs.push_back(
{Identifier::get("circt.nonlocal", anno.getContext()), sym});
}
}
addAnnotation(target.ref, newAnnoAttrs);
return success();
}
/// An applier which puts the annotation on the target and drops the 'target'
/// field from the annotaiton. Optionally handles non-local annotations.
/// Ensures the target resolves to an expected type of operation.
template <bool allowNonLocal, typename T, typename... Tr>
static LogicalResult applyWithoutTarget(AnnoPathValue target,
DictionaryAttr anno, ApplyState state) {
if (!target.isOpOfType<T, Tr...>())
return failure();
return applyWithoutTargetImpl(target, anno, state, allowNonLocal);
}
/// An applier which puts the annotation on the target and drops the 'target'
/// field from the annotaiton. Optionally handles non-local annotations.
template <bool allowNonLocal = false>
static LogicalResult applyWithoutTarget(AnnoPathValue target,
DictionaryAttr anno, ApplyState state) {
return applyWithoutTargetImpl(target, anno, state, allowNonLocal);
}
////////////////////////////////////////////////////////////////////////////////
// Driving table
////////////////////////////////////////////////////////////////////////////////
namespace {
struct AnnoRecord {
llvm::function_ref<Optional<AnnoPathValue>(DictionaryAttr, ApplyState)>
resolver;
llvm::function_ref<LogicalResult(AnnoPathValue, DictionaryAttr, ApplyState)>
applier;
};
}; // namespace
static const llvm::StringMap<AnnoRecord> annotationRecords{{
// Testing Annotation
{"circt.test", {stdResolve, applyWithoutTarget<true>}},
{"circt.testNT", {noResolve, applyWithoutTarget<>}},
{"circt.missing", {tryResolve, applyWithoutTarget<>}}
}};
/// 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 ignoreUnhandledAnno) {
auto ii = annotationRecords.find(annoStr);
if (ii != annotationRecords.end())
return &ii->second;
if (ignoreUnhandledAnno)
return &annotationRecords.find("circt.missing")->second;
return nullptr;
}
//===----------------------------------------------------------------------===//
// Pass Infrastructure
//===----------------------------------------------------------------------===//
namespace {
struct LowerAnnotationsPass
: public LowerFIRRTLAnnotationsBase<LowerAnnotationsPass> {
void runOnOperation() override;
LogicalResult applyAnnotation(DictionaryAttr anno, ApplyState state);
bool ignoreUnhandledAnno = false;
bool ignoreClasslessAnno = false;
SmallVector<DictionaryAttr> worklistAttrs;
};
} // end anonymous namespace
LogicalResult LowerAnnotationsPass::applyAnnotation(DictionaryAttr anno,
ApplyState state) {
// Lookup the class
StringRef annoClassVal;
if (auto annoClass = anno.getNamed("class"))
annoClassVal = annoClass->second.cast<StringAttr>().getValue();
else if (ignoreClasslessAnno)
annoClassVal = "circt.missing";
else
return state.circuit.emitError("Annotation without a class: ") << anno;
// See if we handle the class
auto record = getAnnotationHandler(annoClassVal, ignoreUnhandledAnno);
if (!record)
return state.circuit.emitWarning("Unhandled annotation: ") << anno;
// Try to apply the annotation
auto target = record->resolver(anno, state);
if (!target)
return state.circuit.emitError("Unable to resolve target of annotation: ")
<< anno;
if (record->applier(*target, anno, state).failed())
return state.circuit.emitError("Unable to apply annotation: ") << anno;
return success();
}
// This is the main entrypoint for the lowering pass.
void LowerAnnotationsPass::runOnOperation() {
CircuitOp circuit = getOperation();
SymbolTable modules(circuit);
// Grab the annotations.
for (auto anno : circuit.annotations())
worklistAttrs.push_back(anno.cast<DictionaryAttr>());
// Clear the annotations.
circuit.annotationsAttr(ArrayAttr::get(circuit.getContext(), {}));
size_t numFailures = 0;
ApplyState state{circuit, modules,
[&](DictionaryAttr ann) { worklistAttrs.push_back(ann); }
};
while (!worklistAttrs.empty()) {
auto attr = worklistAttrs.pop_back_val();
if (applyAnnotation(attr, state).failed())
++numFailures;
}
if (numFailures)
signalPassFailure();
}
/// This is the pass constructor.
std::unique_ptr<mlir::Pass> circt::firrtl::createLowerFIRRTLAnnotationsPass(
bool ignoreUnhandledAnnotations, bool ignoreClasslessAnnotations) {
auto pass = std::make_unique<LowerAnnotationsPass>();
pass->ignoreUnhandledAnno = ignoreUnhandledAnnotations;
pass->ignoreClasslessAnno = ignoreClasslessAnnotations;
return pass;
}

View File

@ -1,5 +1,4 @@
// RUN: circt-opt --pass-pipeline='firrtl.circuit(firrtl-lower-annotations)' -split-input-file %s |FileCheck %s
// XFAIL: *
// circt.test copies the annotation to the target
// circt.testNT puts the targetless annotation on the circuit
@ -8,14 +7,23 @@
// A non-local annotation should work.
// CHECK-LABEL: firrtl.circuit "FooNL"
// CHECK: firrtl.nla @nla_0 [@FooNL, @BazNL, @BarNL] ["baz", "bar", "w"]
// CHECK: firrtl.nla @nla [@FooNL, @BazNL, @BarNL] ["baz", "bar", "w2"]
// CHECK: firrtl.module @BarNL
// CHECK: firrtl.wire {annotations = [{circt.nonlocal = @"~FooNL|FooNL/baz:BazNL/bar:BarNL>w_NA_0", class = "circt.test", nl = "nl"}]}
// CHECK: firrtl.instance @BarNL {annotations = [{circt.nonlocal = @"~FooNL|FooNL/baz:BazNL/bar:BarNL>w_NA_0", class = "circt.nonlocal"}], name = "bar"}
// CHECK: firrtl.instance @BazNL {annotations = [{circt.nonlocal = @"~FooNL|FooNL/baz:BazNL/bar:BarNL>w_NA_0", class = "circt.nonlocal"}], name = "baz"
// CHECK: firrtl.nla @"~FooNL|FooNL/baz:BazNL/bar:BarNL>w_NA_0" [@FooNL, @BazNL, @BarNL] ["baz", "bar", "w"]
firrtl.circuit "FooNL" attributes {annotations = [{class = "circt.test", nl = "nl", target = "~FooNL|FooNL/baz:BazNL/bar:BarNL>w"}]} {
// CHECK: %w = firrtl.wire {annotations = [{circt.nonlocal = @nla_0, class = "circt.test", nl = "nl"}]}
// CHECK: %w2 = firrtl.wire {annotations = [{circt.fieldID = 5 : i32, circt.nonlocal = @nla, class = "circt.test", nl = "nl2"}]} : !firrtl.bundle<a: uint, b: vector<uint, 4>>
// CHECK: firrtl.instance @BarNL {annotations = [{circt.nonlocal = @nla, class = "circt.nonlocal"}, {circt.nonlocal = @nla_0, class = "circt.nonlocal"}], name = "bar"}
// CHECK: firrtl.instance @BazNL {annotations = [{circt.nonlocal = @nla, class = "circt.nonlocal"}, {circt.nonlocal = @nla_0, class = "circt.nonlocal"}], name = "baz"
// CHECK: firrtl.module @FooL
// CHECK: %w3 = firrtl.wire {annotations = [{class = "circt.test", nl = "nl3"}]}
firrtl.circuit "FooNL" attributes {annotations = [
{class = "circt.test", nl = "nl", target = "~FooNL|FooNL/baz:BazNL/bar:BarNL>w"},
{class = "circt.test", nl = "nl2", target = "~FooNL|FooNL/baz:BazNL/bar:BarNL>w2.b[2]"},
{class = "circt.test", nl = "nl3", target = "~FooNL|FooL>w3"}
]} {
firrtl.module @BarNL() {
%w = firrtl.wire : !firrtl.uint
%w2 = firrtl.wire : !firrtl.bundle<a: uint, b: vector<uint, 4>>
firrtl.skip
}
firrtl.module @BazNL() {
@ -24,6 +32,9 @@ firrtl.circuit "FooNL" attributes {annotations = [{class = "circt.test", nl = "
firrtl.module @FooNL() {
firrtl.instance @BazNL {name = "baz"}
}
firrtl.module @FooL() {
%w3 = firrtl.wire: !firrtl.uint
}
}

View File

@ -97,6 +97,15 @@ static cl::opt<bool> enableAnnotationWarning(
cl::desc("Warn about annotations that were not removed by lower-to-hw"),
cl::init(false));
static cl::opt<bool> disableAnnotationsClassless(
"disable-annotation-classless",
cl::desc("Ignore annotations without a class when parsing"),
cl::init(false));
static cl::opt<bool> disableAnnotationsUnknown(
"disable-annotation-unknown",
cl::desc("Ignore unknown annotations when parsing"), cl::init(false));
static cl::opt<bool> imconstprop(
"imconstprop",
cl::desc(
@ -158,6 +167,9 @@ static cl::opt<bool>
checkCombCycles("firrtl-check-comb-cycles",
cl::desc("check combinational cycles on firrtl"),
cl::init(false));
static cl::opt<bool> newAnno("new-anno",
cl::desc("enable new annotation handling"),
cl::init(false));
enum OutputFormatKind {
OutputMLIR,
@ -228,6 +240,7 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
auto parserTimer = ts.nest("FIR Parser");
firrtl::FIRParserOptions options;
options.ignoreInfoLocators = ignoreFIRLocations;
options.rawAnnotations = newAnno;
module = importFIRFile(sourceMgr, &context, options);
} else {
auto parserTimer = ts.nest("MLIR Parser");
@ -262,6 +275,10 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
pm.enableTiming(ts);
applyPassManagerCLOptions(pm);
if (newAnno)
pm.nest<firrtl::CircuitOp>().addPass(
firrtl::createLowerFIRRTLAnnotationsPass(disableAnnotationsUnknown,
disableAnnotationsClassless));
if (!disableOptimization) {
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
createCSEPass());