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

Direct to firrtl #829

Merged
merged 13 commits into from Jul 2, 2018
Merged

Direct to firrtl #829

merged 13 commits into from Jul 2, 2018

Conversation

jackkoenig
Copy link
Contributor

@jackkoenig jackkoenig commented Jun 7, 2018

I think this still needs a little work, in particular the function converting Commands to Statements 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

// TODO refactor
def convert(cmds: Seq[Command], ctx: Component): fir.Statement = {
@tailrec
def rec(acc: Queue[fir.Statement],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does rec mean?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

@jackkoenig
Copy link
Contributor Author

Currently the performance of this is terrible because the Seq of Commands is an ArrayBuffer, so repeatedly calling .tail on it is super bad. Simple solution is to convert to List before recursing, considering other options as well.

Copy link
Contributor

@ducky64 ducky64 left a 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 = {
Copy link
Contributor

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.

Copy link
Contributor Author

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))
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Member

@seldridge seldridge left a 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")
}
Copy link
Member

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.

Copy link
Contributor Author

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)
}
Copy link
Member

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.

Copy link
Contributor Author

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

@jackkoenig
Copy link
Contributor Author

jackkoenig commented Jun 14, 2018

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:

  1. Reduce the parser memory use
  2. Don't use the parser

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:

  1. Convert from Chisel to FIRRTL in memory and invoke FIRRTL in the same JVM
  2. Use ProtoBuf (or other library) for serialization and deserialization

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:

  • Elaborated Chisel Objects: 16 GB
  • FIRRTL Representation: 3.5 GB

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
@jackkoenig
Copy link
Contributor Author

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 Driver.dumpProto (which is why this is currently failing, it will pass once chipsalliance/firrtl#832 is merged).

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 Driver.execute and Driver.dumpProto. I think speeding up FIRRTL serialization so that the Chisel Emitter can be removed is desirable but beyond the scope of this PR

@jackkoenig
Copy link
Contributor Author

retest this please

@jackkoenig
Copy link
Contributor Author

This is ready to go

Copy link
Contributor

@azidar azidar left a 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 !

@jackkoenig jackkoenig merged commit 3d8064a into master Jul 2, 2018
@jackkoenig jackkoenig deleted the direct-to-firrtl branch July 2, 2018 22:27
@jackkoenig jackkoenig added this to the 3.2.0 milestone Jul 13, 2018
@ucbjrl ucbjrl modified the milestones: 3.2.0, 3.1.4 Dec 4, 2018
@ucbjrl ucbjrl modified the milestones: 3.1.4, 3.2.0 Dec 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants