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
[LowerAnnotations] Resolve legacy wiring annotations as WiringProblems #4496
Conversation
7605434
to
52b85df
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High level, looking good!
Some comments throughout.
Also, could the PR include some tests?
llvm::StringMap<Value> legacyWiringSources; | ||
llvm::StringMap<SmallVector<Value>> legacyWiringSinks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be slightly cleaner to define a new struct LegacyWiringProblem
as opposed to doing the split of two string maps. Consider:
struct LegacyWiringProblem {
source Value;
sinks SmallVector<Value>;
};
DenseMap<LegacyWiringProblem> legacyWiringProblems;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, there's usually not a huge benefit in using llvm::StringMap
unless you have actual strings. Because you're dealing with StringAttr
s (which are wrappers around interned strings), it's usually better to just use a DenseMap<Attribute>
(and ignore the fact that this will allow for keys of any attribute type and not just StringAttr
). This should be more efficient than llvm::StringMap
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A logical issue I'm having with this is that the order of annotations impacts how we can structure data. If the sink for a pin is defined before a source for it, it's not possible to define a LegacyWiringProblem like this right away, we'd have to store something immediately and update it later. Would
struct LegacyWiringProblem {
Optional<Value> source;
SmallVector<Value> sinks;
};
make any sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional<Value> source
is overkill as Value
is nullable. I.e., it's totally fine to just leave Value
uninitialized and it has boolean operations defined on it. Stuff like the following is totally fine:
LegacyWiringProblem foo = ...;
if (!foo.source)
return mlir::emitError() << "no source set!";
Tangential tidbit: llvm::Optional
is deprecated (in upstream LLVM) in favor of unifying around std::optional
.
// Get pin field | ||
StringRef pin; | ||
if (auto pinName = anno.getNamed("pin")) { | ||
pin = pinName->getValue().cast<StringAttr>().getValue(); | ||
} else { | ||
return mlir::emitError(state.circuit.getLoc()) | ||
<< "Annotation does not have an associated pin name: " << anno; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A slightly cleaner way to do this using getAs<T>
.
auto pin = anno.getAs<StringAttr>("pin");
if (!pin)
return mlir::emitError(...);
I've had a crack at everything above except testing. LowerAnnotations as a whole seems to not have any CIRCT level tests, so what would be the best way to test this for now? Adding tests to firtool? |
The PR can model the tests off of how the Grand Central Taps tests are written: circt/test/Dialect/FIRRTL/annotations.mlir Line 1121 in 0692c19
An example of testing the error paths is here:
These are, for the most part, directly testing the wiring problem infra. It isn't necessary to duplicate that, only the novel features that are getting added here. E.g., single-source/multi-sink, real ports bored from source to LCA, and error code paths. |
2e0778e
to
4286084
Compare
Had a chance to circle back to this, I've added some simple tests and resolved conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, looks good! Bounced through it and added some comments--most pressing are ones regarding ensuring this works properly re:dominance in various scenarios, secondarily ensuring it composes with taps properly (although if can't find a good solution for that here, wiling to be convinced we file an issue for that and tackle separately).
Thanks for updating and the tests!
@@ -0,0 +1,121 @@ | |||
//===- LegacyWiring- legacy Wiring annotation resolver ----------*- C++ -*-===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
//===- LegacyWiring- legacy Wiring annotation resolver ----------*- C++ -*-===// | |
//===- LegacyWiring- legacy Wiring annotation resolver ----------*- C++ -*-===// | |
//===- LegacyWiring- legacy Wiring annotation resolver --------------------===// |
It's tedious but the idea is we only include the "C++" bit for headers to help editors unclear if it's a C or C++ file: https://llvm.org/docs/CodingStandards.html#file-headers . AFAIK only recognized by emacs...
@@ -206,6 +206,19 @@ struct WiringProblem { | |||
/// A base name to use when generating new signals associated with this wiring | |||
/// problem. | |||
std::string newNameHint; | |||
|
|||
/// Create real type ports instead of ref type when solving this problem. | |||
bool useRealTypePorts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Micro-nit: both values for this boolean will use non-ref ports in some situations (ref type only used for half of the up/down wiring) maybe rename this (as it's essentially the WiringProblem API presently)? "disallowRefTypePorts" but that's not wonderful, "onlyBaseTypePorts", similarly so.
Alternatively, maybe make this an enum with each describing the wiring behavior, something like "PreferRefType" "NoRefType" (or RefTypeUsage::Prefer
, RefTypeUsage::Never
)? This would make the requested behavior more understandable when instantiating WiringProblem's too, but 🤷♂️ .
Just some thoughts, not at all a blocker as-is 👍.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 to using an enum class.
auto portNum = portTarget.getImpl().getPortNo(); | ||
if (auto module = dyn_cast<FModuleOp>(portTarget.getOp())) { | ||
builder.setInsertionPointToEnd(module.getBodyBlock()); | ||
targetsValues.push_back(getValueByFieldID( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, IMO, this is something we need to solve more robustly re:WiringProblem infra improvements, but for now it's the responsibility of the problem creation code to ensure a number of things that are a little tricky. Hopefully we can consolidate/improve things in the future. Anyway.
Here, inserting into the end of the block makes sense if this is a sink, but for the source you probably want the value to dominate everything the target port dominates (so it can be connected to the same sorts of things, for example down through an instance).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it enough to insert before/ at the start for sources?
if (paths.size() > 1) { | ||
mlir::emitError(state.circuit.getLoc()) | ||
<< "cannot resolve a unique instance path from the " | ||
"external module target" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"external module target" | |
"external module target " |
(I think?)
targetsValues.end()); | ||
} else { | ||
// Create a new problem | ||
state.legacyWiringProblems[pin] = {.sinks = targetsValues}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly thought this was a GCC extension (I think it is for C?), but looks like maybe it's a C++20 feature? In either case, I'm not sure this is a construct that's valid for all our intended toolchains/platforms (c++17, not necessarily w/gcc extensions).
/// are visited from leaves to roots to apply module modifications. Module | ||
/// modifications include addings ports and connecting things up. | ||
/// Wiring Problem is a mapping from a source to a sink that can be connected | ||
/// via a real Type or RefType as requested. This uses a two-step approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW non-reftype is "base" not "real", but maybe they should've been called real instead 😄. Calling these "real" ports makes sense to me.
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."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Thanks for checking this.
%1 = firrtl.subfield %bar_io[out] : !firrtl.bundle<out: uint<1>> | ||
firrtl.strictconnect %0, %1 : !firrtl.uint<1> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: newline (see red mark on github vs other files)
/// using the pin attribute as newNameHint | ||
LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) { | ||
for (const auto &problem : state.legacyWiringProblems) { | ||
auto pin = cast<StringAttr>(problem.first); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the DenseMap
have StringAttr
as its key instead of Attribute
?
Any chance for a review of these latest patches? I don't think anything else was considered a blocker, and I'd rather avoid this PR stagnating. |
Sorry for the delay! We can take a look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thanks for tackling this.
A few nits and a few lingering comments from @dtzSiFive, but this looks good.
if (!problem.second.source) { | ||
return mlir::emitError(state.circuit.getLoc()) | ||
<< "Unable to resolve source for pin: " << problem.first; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mega-nit:
if (!problem.second.source) { | |
return mlir::emitError(state.circuit.getLoc()) | |
<< "Unable to resolve source for pin: " << problem.first; | |
} | |
if (!problem.second.source) | |
return mlir::emitError(state.circuit.getLoc()) | |
<< "Unable to resolve source for pin: " << problem.first; |
Ditto below and for for loop.
%1 = firrtl.subfield %bar_io[out] : !firrtl.bundle<out: uint<1>> | ||
firrtl.strictconnect %0, %1 : !firrtl.uint<1> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto from above: this test can be greatly reduced in complexity.
%1 = firrtl.subfield %bar_io[out] : !firrtl.bundle<out: uint<1>> | ||
firrtl.strictconnect %0, %1 : !firrtl.uint<1> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Test can be reduced by using just ground types.
f1fb2bb
to
13b60bd
Compare
/// Convert consumed SourceAnnotation and SinkAnnotation into WiringProblems, | ||
/// using the pin attribute as newNameHint | ||
LogicalResult LowerAnnotationsPass::legacyToWiringProblems(ApplyState &state) { | ||
for (const auto &problem : state.legacyWiringProblems) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider structured binding here, if you like it better:
for (const auto &[name, problem] : state.legacyWiringProblems) {
Don't usually use ref for Attribute's but doesn't hurt and no need to copy the problem.
13b60bd
to
f2199c3
Compare
I've stuck to just emitting an error instead of calling |
f2199c3
to
cabc5e4
Compare
I also copied the example you provided to a test case in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, checking instances is simpler than all impacted values.
(apologies for slow response, was out sick last week)
I was worried about checking the instance since it changes "all instances" of the module-- but since we don't allow data taps on modules that are multiply instantiated, this does the trick AFAIK.
This is something we should improve (separately!), thank you for handing this as you have!
@@ -834,7 +844,8 @@ LogicalResult circt::firrtl::applyGCTMemTaps(const AnnoPathValue &target, | |||
combMem->getParentOfType<FModuleOp>()); | |||
if (path.size() > 1) | |||
return combMem.emitOpError( | |||
"cannot be resolved as source for MemTap, multiple paths from top " | |||
"cannot be resolved as source for MemTap, multiple paths from " | |||
"top " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: combine this with rest of the message string literal below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite sure why clang-format decided to mess up these strings, but I've reverted it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's weird. Especially unfortunate we can't get it to recombine string literals it happily breaks apart.
(thanks!)
cabc5e4
to
2f2ea25
Compare
2f2ea25
to
e2be593
Compare
I force pushed this just now to resolve merge conflicts I created. I'll merge this after tests run. Thanks for the hard work getting this together @sam-shahrestani! |
Thanks @sam-shahrestani !! |
Thanks for all the help and advice! Hopefully we'll see more projects moving to CIRCT from the SFC now. |
firrtl.passes.wiring.SinkAnnotation
,firrtl.passes.wiring.SourceAnnotation
) that stores these annotations in ApplyStateImplements #4482.