Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] Add VersionAttr, put on Circuit, use to parse FIRRTL 6 Annotations #8390

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions include/circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,11 @@ struct CircuitTargetCache {
/// 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.
std::string canonicalizeTarget(StringRef target);
std::string canonicalizeTarget(StringRef target, VersionAttr version);

/// Parse a FIRRTL annotation path into its constituent parts.
std::optional<TokenAnnoTarget> tokenizePath(StringRef origTarget);
std::optional<TokenAnnoTarget> tokenizePath(StringRef origTarget,
VersionAttr version);

/// Convert a parsed target string to a resolved target structure. This
/// resolves all names and aggregates from a parsed target.
Expand Down
76 changes: 76 additions & 0 deletions include/circt/Dialect/FIRRTL/FIRRTLAttributes.td
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,82 @@ def InternalPathAttr : AttrDef<FIRRTLDialect, "InternalPath"> {
def InternalPathArrayAttr
: TypedArrayAttrBase<InternalPathAttr, "InternalPath array attribute">;

//===----------------------------------------------------------------------===//
// Circuit version attribute
//===----------------------------------------------------------------------===//

def VersionAttr : AttrDef<FIRRTLDialect, "Version"> {
let summary = "A FIRRTL version";
let description = [{
This attribute records a specific version of the FIRRTL specification.

This consists of a major, minor, and patch version.
}];

let mnemonic = "version";
let parameters = (ins "uint16_t":$major, "uint16_t":$minor, "uint16_t":$patch);
let assemblyFormat = [{
`<` struct($major, $minor, $patch) `>`
}];

let extraClassDeclaration = [{
operator uint64_t() const {
return uint64_t(getMajor()) << 32 | uint64_t(getMinor()) << 16 | uint64_t(getPatch());
}

bool operator<(VersionAttr other) const {
return uint64_t(*this) < uint64_t(other);
}

bool operator>(VersionAttr other) const {
return uint64_t(*this) > uint64_t(other);
}

bool operator<=(VersionAttr other) const {
return uint64_t(*this) <= uint64_t(other);
}

bool operator>=(VersionAttr other) const {
return uint64_t(*this) >= uint64_t(other);
}

/// The minimum FIRRTL version supported by CIRCT.
static VersionAttr getMinimumVersion(::mlir::MLIRContext *context) {
return get(context, 2, 0, 0);
}

/// The next version of FIRRTL that is not yet released.
///
/// Features use this version if they have been landed on the main branch of
/// `chipsalliance/firrtl-spec`, but have not been part of a release
/// yet. Once a new version of the spec is released, all uses of
/// `nextFIRVersion` in the parser are replaced with the concrete version
/// `{x, y, z}`, and this declaration here is bumped to the next probable
/// version number.
static VersionAttr getNextVersion(::mlir::MLIRContext *context) {
return get(context, 5, 1, 0);
}

/// A marker for parser features that are currently missing from the spec.
///
/// Features use this version if they have _not_ been added to the
/// documentation in the `chipsalliance/firrtl-spec` repository. This allows
/// us to distinguish features that are released in the next version of the
/// spec and features that are still missing from the spec.
static VersionAttr getMissingVersion(::mlir::MLIRContext *context) {
return getNextVersion(context);
}

/// The version of FIRRTL that the exporter produces. This is always the
/// next version, since it contains any new developments.
static VersionAttr getExportVersion(::mlir::MLIRContext *context) {
return getNextVersion(context);
}

}];
}


//===----------------------------------------------------------------------===//
// Miscellaneous attributes
//===----------------------------------------------------------------------===//
Expand Down
7 changes: 6 additions & 1 deletion include/circt/Dialect/FIRRTL/FIRRTLStructure.td
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def CircuitOp : FIRRTLOp<"circuit",
let arguments = (ins
StrAttr:$name,
DefaultValuedAttr<AnnotationArrayAttr, "{}">:$annotations,
OptionalAttr<VersionAttr>:$version,
OptionalAttr<SymbolRefArrayAttr>:$enable_layers,
OptionalAttr<SymbolRefArrayAttr>:$disable_layers,
OptionalAttr<LayerSpecializationAttr>:$default_layer_specialization,
Expand All @@ -45,7 +46,7 @@ def CircuitOp : FIRRTLOp<"circuit",
let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "StringAttr":$name,
CArg<"ArrayAttr","ArrayAttr()">:$annotations)>
CArg<"ArrayAttr","ArrayAttr()">:$annotations)>
];

let extraClassDeclaration = [{
Expand All @@ -57,6 +58,10 @@ def CircuitOp : FIRRTLOp<"circuit",

/// Return body of this circuit.
Block *getBodyBlock();

VersionAttr getVersionOrDefault() {
return getVersion().value_or(VersionAttr::getNextVersion(getContext()));
}
}];

let assemblyFormat = "$name `` custom<CircuitOpAttrs>(attr-dict) $body";
Expand Down
66 changes: 46 additions & 20 deletions lib/Dialect/FIRRTL/FIRRTLAnnotationHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,13 @@ static FailureOr<unsigned> findFieldID(AnnoTarget &ref,
}

void TokenAnnoTarget::toVector(SmallVectorImpl<char> &out) const {
out.push_back('~');
out.append(circuit.begin(), circuit.end());
out.push_back('|');
// If there is no circuit, then this is a FIRRTL 6+ target. Don't include a
// circuit.
if (!circuit.empty()) {
out.push_back('~');
out.append(circuit.begin(), circuit.end());
out.push_back('|');
}
for (auto modInstPair : instances) {
out.append(modInstPair.first.begin(), modInstPair.first.end());
out.push_back('/');
Expand All @@ -137,11 +141,16 @@ void TokenAnnoTarget::toVector(SmallVectorImpl<char> &out) const {
}
}

std::string firrtl::canonicalizeTarget(StringRef target) {
std::string firrtl::canonicalizeTarget(StringRef target, VersionAttr version) {

if (target.empty())
return target.str();

// In FIRRTL 6+, there is only one annotation format and no canonicalization
// is needed.
if (version.getMajor() >= 6)
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] == '~')
Expand All @@ -166,7 +175,8 @@ std::string firrtl::canonicalizeTarget(StringRef target) {
std::optional<AnnoPathValue>
firrtl::resolveEntities(TokenAnnoTarget path, CircuitOp circuit,
SymbolTable &symTbl, CircuitTargetCache &cache) {
// Validate circuit name.
// Validate circuit name. If there is _no_ circuit name, then don't validate
// it. This allows FIRRTL version 6+ to just work as it has no circuit name.
if (!path.circuit.empty() && circuit.getName() != path.circuit) {
mlir::emitError(circuit.getLoc())
<< "circuit name doesn't match annotation '" << path.circuit << '\'';
Expand Down Expand Up @@ -288,15 +298,26 @@ firrtl::resolveEntities(TokenAnnoTarget path, CircuitOp circuit,

/// split a target string into it constituent parts. This is the primary parser
/// for targets.
std::optional<TokenAnnoTarget> firrtl::tokenizePath(StringRef origTarget) {
std::optional<TokenAnnoTarget> firrtl::tokenizePath(StringRef origTarget,
VersionAttr version) {
// An empty string is not a legal target.
if (origTarget.empty())
return {};
StringRef target = origTarget;
TokenAnnoTarget retval;
std::tie(retval.circuit, target) = target.split('|');
if (!retval.circuit.empty() && retval.circuit[0] == '~')
retval.circuit = retval.circuit.drop_front();
// If the version is before FIRRTL 6, then there is a circuit to parse.
if (version.getMajor() < 6) {
std::tie(retval.circuit, target) = target.split('|');
if (!retval.circuit.empty() && retval.circuit[0] == '~')
retval.circuit = retval.circuit.drop_front();
} else {
// Reject 6- annotations.
if (origTarget[0] == '~')
return {};
// Reject legay annotations.
if (origTarget.contains('.') && !origTarget.contains('>'))
return {};
}
while (target.count(':')) {
StringRef nla;
std::tie(nla, target) = target.split(':');
Expand Down Expand Up @@ -332,10 +353,10 @@ std::optional<AnnoPathValue> firrtl::resolvePath(StringRef rawPath,
CircuitOp circuit,
SymbolTable &symTbl,
CircuitTargetCache &cache) {
auto pathStr = canonicalizeTarget(rawPath);
auto pathStr = canonicalizeTarget(rawPath, circuit.getVersionOrDefault());
StringRef path{pathStr};

auto tokens = tokenizePath(path);
auto tokens = tokenizePath(path, circuit.getVersionOrDefault());
if (!tokens) {
mlir::emitError(circuit.getLoc())
<< "Cannot tokenize annotation path " << rawPath;
Expand Down Expand Up @@ -564,9 +585,10 @@ LogicalResult circt::firrtl::applyGCTDataTaps(const AnnoPathValue &target,
tryGetAs<StringAttr>(bDict, anno, "sink", loc, dataTapsClass, path);
std::string wirePathStr;
if (sinkNameAttr)
wirePathStr = canonicalizeTarget(sinkNameAttr.getValue());
wirePathStr = canonicalizeTarget(sinkNameAttr.getValue(),
state.circuit.getVersionOrDefault());
if (!wirePathStr.empty())
if (!tokenizePath(wirePathStr))
if (!tokenizePath(wirePathStr, state.circuit.getVersionOrDefault()))
wirePathStr.clear();
std::optional<AnnoPathValue> wireTarget;
if (!wirePathStr.empty())
Expand Down Expand Up @@ -598,8 +620,9 @@ LogicalResult circt::firrtl::applyGCTDataTaps(const AnnoPathValue &target,
tryGetAs<StringAttr>(bDict, anno, "module", loc, dataTapsClass, path);
if (!internalPathAttr || !moduleAttr)
return failure();
auto moduleTargetStr = canonicalizeTarget(moduleAttr.getValue());
if (!tokenizePath(moduleTargetStr))
auto moduleTargetStr = canonicalizeTarget(
moduleAttr.getValue(), state.circuit.getVersionOrDefault());
if (!tokenizePath(moduleTargetStr, state.circuit.getVersionOrDefault()))
return failure();
std::optional<AnnoPathValue> moduleTarget = resolvePath(
moduleTargetStr, state.circuit, state.symTbl, state.targetCaches);
Expand All @@ -623,8 +646,9 @@ LogicalResult circt::firrtl::applyGCTDataTaps(const AnnoPathValue &target,
tryGetAs<StringAttr>(bDict, anno, "source", loc, dataTapsClass, path);
if (!sourceAttr)
return failure();
auto sourcePathStr = canonicalizeTarget(sourceAttr.getValue());
if (!tokenizePath(sourcePathStr))
auto sourcePathStr = canonicalizeTarget(
sourceAttr.getValue(), state.circuit.getVersionOrDefault());
if (!tokenizePath(sourcePathStr, state.circuit.getVersionOrDefault()))
return failure();
LLVM_DEBUG(llvm::dbgs() << "\n Drill xmr path from :" << sourcePathStr
<< " to " << wirePathStr);
Expand Down Expand Up @@ -722,7 +746,8 @@ LogicalResult circt::firrtl::applyGCTMemTaps(const AnnoPathValue &target,
if (!sourceAttr)
return failure();

auto sourceTargetStr = canonicalizeTarget(sourceAttr.getValue());
auto sourceTargetStr = canonicalizeTarget(
sourceAttr.getValue(), state.circuit.getVersionOrDefault());
std::optional<AnnoPathValue> srcTarget = resolvePath(
sourceTargetStr, state.circuit, state.symTbl, state.targetCaches);
if (!srcTarget)
Expand All @@ -743,8 +768,9 @@ LogicalResult circt::firrtl::applyGCTMemTaps(const AnnoPathValue &target,
<< "The full Annotation is reprodcued here: " << anno << "\n";
}

auto wireTargetStr = canonicalizeTarget(tap.getValue());
if (!tokenizePath(wireTargetStr))
auto wireTargetStr =
canonicalizeTarget(tap.getValue(), state.circuit.getVersionOrDefault());
if (!tokenizePath(wireTargetStr, state.circuit.getVersionOrDefault()))
return failure();
std::optional<AnnoPathValue> wireTarget = resolvePath(
wireTargetStr, state.circuit, state.symTbl, state.targetCaches);
Expand Down
Loading