-
Notifications
You must be signed in to change notification settings - Fork 602
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
Direct to firrtl #829
Direct to firrtl #829
Conversation
This works around an assumption in FIRRTL that Literals never have UnknownWidth
// TODO refactor | ||
def convert(cmds: Seq[Command], ctx: Component): fir.Statement = { | ||
@tailrec | ||
def rec(acc: Queue[fir.Statement], |
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.
What does rec
mean?
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.
You've probably considered this but could you create a trait like HasToStatement that most of the Commands could extend. Then the convert of command to statement could handle most of the cases with
case m: HasToStatement =>
rec(acc :+ m.toStatement, scope)(cmds.tail)
It changes the encapsulation of information, since for example, DefWire has to know how to make a fir.DefWire but it sure cleans up the massive match. I can see it's a bit ugly though since toStatement would need access to the Converter object for info, clocks etc.
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.
rec
is the name I give to inner recursive functions. It's arbitrary, I'm open to whatever. Scalaz uses go
I think.
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 I have thought about it a bit but your point is well taken and I ought to share some of my thinking.
In prototyping this, I wanted to touch as little code as possible which is why the Converter is wholly self-contained, I am open to some amount of encapsulation though. toStatement
would have to take the Component
as an argument, but that's how def fullName
currently works for converting Args
to Expressions
.
A big question related to performance I have that goes back to serialize
in FIRRTL is whether large match statements or inheritance are better for performance. Serialization functions in this case are almost certainly megamorphic which is generally terrible for performance, but when we moved serialize from a match statement to a method on FIRRTLNode
, it still got faster. Perhaps the dynamic method lookup in megamorphic state is still faster in practice than large match blocks. I'm really not sure.
Currently the performance of this is terrible because the |
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 possible to have Chisel directly generate FIRRTL IR nodes, instead of going through chisel-firrtl? (was this a limitation of the different when structures, and can that be addressed with a restructuring of either Chisel or FIRRTL)?
I'm happy with the structure of this as a minimal-effort fix, but I'd prefer to avoid hax and clean things up properly if possible. Because I'd imagine that eventually we would merge the two IRs, might as well do it right the first time?
I haven't closely examined this for correctness.
Can you also quantify the performance gains - I'd guess the interesting metrics are runtime and memory consumption?
|
||
// Whens markers are flat so scope must be inferred | ||
// TODO refactor | ||
def convert(cmds: Seq[Command], ctx: Component): fir.Statement = { |
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.
There's quite a bit of code here, can you write a summary of the algorithm in comments?
This mostly seems to be a straightforward 1:1 map of Chisel to FIRRTL types except with some funny stuff for whens.
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 this definitely needs a summary, will add
val con = fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx)) | ||
rec(acc :+ con, scope)(cmds.tail) | ||
case BulkConnect(info, loc, exp) => | ||
val con = fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx)) |
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.
Are FIRRTL PartialConnect semantics equivalent to Chisel BulkConnect semantics? Or should this exception out because this should never be seen?
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.
FIRRTL PartialConnect semantics are closer to Chisel2 (and the current Chisel._
<>
) semantics actually. It's still used by the compatibility wrapper.
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'm in favor of this. It seems a lot cleaner to convert to a FIRRTL circuit and then (assumedly optionally) convert to a serialized String representation.
Are there any implications as it relates to CHIRRTL removal, i.e., chipsalliance/firrtl#727?
I'm also curious on the performance numbers...
val lit = bp.asInstanceOf[KnownBinaryPoint].value | ||
fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType) | ||
case lit: ILit => throwException(s"Internal Error! Unexpected ILit: $lit") | ||
} |
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.
Stylistic nits. Per the style guide, you've got 120 characters to play with. For readability, you may consider putting some of these on one line. Additionally, I have a very hard time parsing dense match structures. You may consider doing an alignment on =>
. Same for other large match blocks.
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 I agree the density is pretty tough. I ended up deciding to have all of the cases be multi-line. The matches were so different in length that alignment on =>
wasn't working very well
def convert(bp: BinaryPoint): fir.Width = bp match { | ||
case UnknownBinaryPoint => fir.UnknownWidth | ||
case KnownBinaryPoint(value) => fir.IntWidth(value) | ||
} |
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 was confused on the exact replication of convert(BinaryPoint)
and convert(Width)
, but this is how it's done in chisel3.internal.firrtl
(with associated replication there). That may benefit from a refactor, but that is clearly out of scope of this encapsulated PR.
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 was my thinking, a later refactor can clean a lot of stuff related to this and the Emitter
I guess I should write down some of my thinking on this and let y'all know where I'm going with this: It appears that the current ANTLR Parser for FIRRTL is the main culprit in the high memory use for FIRRTL. There are two (obvious) possible solutions:
I spent some time trying to improve the parser (see chipsalliance/firrtl#824), but due to past experience and pain with the parser, I've been working on number 2. For this, I saw two main ways to do it:
I originally was working on number 1 (see this PR), but it turns out to be a pretty complex game of wack-a-mole with static references to Chisel objects (in Chisel projects like rocket-chip) that prevent garbage collection (harming memory use substantially). To illustrate, using a large design, I get the following memory stats:
When I invoke them in the same JVM, the elaborated Chisel graph sticks around the whole time. It's probably possible to fix them all, but due to this issue as well as the general notion that it is easier on people's existing build systems to keep Chisel and FIRRTL split, I decided to refocus on number 2. I am in the process of providing a translator from FIRRTL to ProtoBuf. The plan is to just implement FIRRTL<->Protobuf and use this PR to translate Chisel to FIRRTL for serialization. For the record, the Chisel object is still massive but I suspect there are easy gains there. Even the FIRRTL representation has plenty of room for improvement. |
At the moment, the Converter is very fast, but FIRRTL serialize is much slower than the Chisel Emitter
Also rename optName to optFile in dumpProto (didn't change dumpFirrtl because it is an existing public API)
Split out simple (1:1) Command -> Statement translations. Add comments to clarify how flat When Commands are converted to nested FIRRTL Conditionals
I've refactored the style of the convert method for Commands to Statements (since that is by far the densest part). I split out conversion for Commands that map 1:1 to Statements from the when scope handling logic which is much more complicated. I also added clarifying comments to describe the basic process and noted things that I feel are non-obvious (or inconsistent). I also added I have also posted performance numbers in chipsalliance/firrtl#832. I backed out of using FIRRTL serialize because it is like 4x slower than the Chisel Emitter (as noted in chipsalliance/firrtl#838). However, the converter is still used by |
retest this please |
This is ready to go |
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.
Looks awesome! Great job @jackkoenig !
I think this still needs a little work, in particular the function converting
Command
s toStatement
s is a bit of a beast. I would appreciate suggestions on how to improve it! I'll provide some benchmarking numbers soon so that we can ensure this doesn't hurt performance (although for larger designs, skipping the parser should only help).I should note that the "Converter" as currently implemented is kind of a transliteration of the existing Emitter. I would greatly appreciate thoughts on improvements. It is slightly more verbose than the Emitter because some emission logic lives within the Chisel IR and at least initially I decided to keep all conversion logic contained.
Type of change: other enhancement
Impact: API addition (no impact on existing code) -- You can now get FIRRTL directly in Chisel!
Development Phase: implementation
Release Notes
Directly translate from ChiselIR to FIRRTL without using the FIRRTL ANTLR Parser