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

Directions internals mega-refactor #617

Merged
merged 25 commits into from
Jun 27, 2017
Merged

Directions internals mega-refactor #617

merged 25 commits into from
Jun 27, 2017

Conversation

ducky64
Copy link
Contributor

@ducky64 ducky64 commented May 25, 2017

Part 1 of mega-change in #578

Major notes:

  • Input(...) and Output(...) now (effectively) recursively override their elements' directions
  • Nodes given userDirection (Input, Output, Flip - what the user assigned to that node) and actualDirection (Input, Output, None, but also Bidirectional and BidirectionalFlip for mostly Aggregates), because of the above (since a higher-level Input(...) can override the locally specified user direction).
  • DataMirror (node reflection APIs) added to chisel3.experimental. This provides ways to query the user given direction of a node as well as the actual direction.
  • checkSynthesizable replaced with requireIsHardware and requireIsChiselType and made available in chisel3.experimental.

Internal changes notes:

  • toType moved into Emitter, this makes the implementation cleaner especially considering that Vec types can't be flipped in FIRRTL. This also more clearly separates Chisel frontend from FIRRTL emission.
  • Direction separated from Bindings, both are now fields in Data, and all nodes are given hierarchical directions (Aggregates may be Bidirectional). The actualDirection at the Element (leaf) level should be the same as binding directions previously.
  • Bindings are hierarchical, children (of a, for example, Bundle) have a ChildBinding that points to their parent. This is different than the previous scheme where Bindings only applied at the Element (leaf) level.
  • Lots of small misc clean up.

Discussion points

  • Naming of requireIsHardware and requireIsChiselType
  • This separates the Input(...), Output(...), Flipped(...) from the directions, and also (by necessity) splits userDirection and actualDirection. I think this is cleaner, but thoughts?
  • requireIsHardware is currently a top-level method, as opposed to (myChiselData).requireIsHardware. Thoughts? I think getType (future PR) will be in a similar style.
  • Input(...), Output(...), Flipped(...) have been changed not to clone, for efficiency reasons since the usual use is in inline with Bundles. Thoughts? They clone again now, not cloning seems to break a lot of code.

Future PRs will address other parts of #578, including stricter direction checks that aren't a side-effect of this internal refactor, stricter checks and splitting of binding operations (Wire vs. WireInit), and node operations not introduced here (getType and deprecation of chiselCloneType). Since those shouldn't mess with internals, those should be much smaller.

Testing with rocket-chip pending.

@ducky64
Copy link
Contributor Author

ducky64 commented May 26, 2017

rocket-chip builds on this now, with one necessary change:

in (rocket-chip)/src/main/scala/jtag/JtagTap.scala
val TCK = Clock(OUTPUT)
must be changed to the new style (since JTAG is chisel3._)
val TCK = Output(Clock())

jackkoenig added a commit to chipsalliance/rocket-chip that referenced this pull request May 30, 2017
jackkoenig added a commit to chipsalliance/rocket-chip that referenced this pull request Jun 1, 2017
@jackkoenig
Copy link
Contributor

For the record, I ran this branch with rocket-chip and it passes Travis: chipsalliance/rocket-chip#770

Copy link
Contributor

@jackkoenig jackkoenig left a comment

Choose a reason for hiding this comment

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

Overall it looks good. I have a collection of primarily nitpicks and documentation questions. Great work!

@@ -68,25 +114,21 @@ object Vec {

// Check that types are homogeneous. Width mismatch for Elements is safe.
require(!elts.isEmpty)
elts foreach {requireIsHardware(_, "vec element")}
Copy link
Contributor

Choose a reason for hiding this comment

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

should always use . notation with foreach

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

if (!msg.isEmpty()) {
throw new Binding.BindingException(s"$msg '$node' must be hardware, not a bare Chisel type")
} else {
throw new Binding.BindingException(s"'$node' must be hardware, not a bare Chisel type")
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not a big deal, but in my opinion, this check for if msg is empty couple be better written as:

val prefix = if (msg.nonEmpty) s"$msg " else ""
throw new Binding.BindingException(s"$prefix'$node' must be hardware, not a bare Chisel type")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree, changed

object requireIsChiselType {
def apply(node: Data, msg: String = "") = if (node.hasBinding) {
if (!msg.isEmpty()) {
throw new Binding.BindingException(s"$msg '$node' must be a Chisel type, not hardware")
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it was like this before, but I personally would prefer using different types for the different sub BindingExceptions, then we could match on the specific exception thrown in testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

case PortBinding(_) => direction match {
case ActualDirection.Output => Output
case ActualDirection.Input => Input
case dir => throw new RuntimeException(s"Unexpected port element direction '$dir'")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a RunTimeException and not a ChiselException or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is more of a Chisel internal fault rather than something the user did. The idea here is to give a slightly more informative message than the standard "nothing matched" error, but Scala doesn't seem to have a built-in facility for specifying that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see, we should probably standardize with a ChiselInternalException or something but this is fine for now

@@ -24,7 +24,8 @@ trait RecordSpecUtils {
override def cloneType = (new MyBundle).asInstanceOf[this.type]
}
// Useful for constructing types from CustomBundle
val fooBarType = new CustomBundle("foo" -> UInt(32.W), "bar" -> UInt(32.W))
// This is a def because each call to this needs to return a new instance
Copy link
Contributor

Choose a reason for hiding this comment

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

Hilariously, my original comment implies it should be a def, I guess I just goofed.

}
// Top-level binding representing hardware, not a pointer to another binding
sealed trait TopBinding extends Binding
Copy link
Contributor

Choose a reason for hiding this comment

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

By "top" here do you mean just a bound type inside of a module? Children are fields in an aggregate? Top kind of misleads me here since we usually refer to "Top" as the top of the circuit but I'm not sure what a better word would be....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess that's one way to interpret it.
To me, it means basically "not ChildBinding", and Top means the top of a potential Aggregate hierarchy. I've clarified the comment a bit.

// For bidirectional, must issue a bulk connect so subelements are resolved correctly.
// Bulk connecting two wires may not succeed because Chisel frontend does not infer
// directions.
(vec zip elts) foreach {x => x._1 <> x._2}
Copy link
Contributor

Choose a reason for hiding this comment

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

dots for these foreaches as well. Also you generally are supposed to put spaces on each side of curly braces (although you could just use parentheses here)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed, and changed to parens

}
// TODO port technically isn't directly child of this data structure, but the result of some
// muxes / demuxes. However, this does make access consistent with the top-level bindings.
// Perhaps there's a cleaner way of accomlpishing this...
Copy link
Contributor

Choose a reason for hiding this comment

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

accomplishing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

And I had the red squigglies in my IDE too...

// TODO port technically isn't directly child of this data structure, but the result of some
// muxes / demuxes. However, this does make access consistent with the top-level bindings.
// Perhaps there's a cleaner way of accomlpishing this...
port.bind(ChildBinding(this), reconstructedResolvedDirection)
Copy link
Contributor

Choose a reason for hiding this comment

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

I see the comment but I don't really get why this is a ChildBinding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has to take the properties of (and act like) its top-level binding, but really isn't (it's more of an accessor, really).

For example, an accessor into a Reg(Vec(...)) behaves like a Reg, but isn't a top-level Reg. I'm not sure how this should be resolved cleanly.

case ActualDirection.Input => UserDirection.Input
case ActualDirection.Output => UserDirection.Output
case ActualDirection.Bidirectional | ActualDirection.Unspecified => UserDirection.Unspecified
case ActualDirection.BidirectionalFlip => UserDirection.Flip
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this match the only reason why there is both Bidirectional and BidirectionalFlip?

Copy link
Contributor

Choose a reason for hiding this comment

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

might it not be better to just store the UserDirection rather than muddying up the ActualDirections if so?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason for having BidirectionalFlip as an ActualDirection is to potentially emitting Bundle bulk-connects to FIRRTL instead of resolving everything to leaves. This makes the check easier, as it can happen at the top level instead of resolving down to leaves.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess what I'm getting at is that this seems like a super easy way to have bugs when dealing with directions because you have to remember that there are actually two bidirectional ones. What if Bidirectional were a case class instead with a field to indicated "flippedness" so that you could match on:

case ActualDirection.Bidirectional(_) =>

instead of

case ActualDirection.Bidirectional | ActualDirection.BidirectionalFlip =>

and for cases where flippedness matters:

case ActualDirection.Bidirectional(Flipped) =>
case ActualDirection.Bidirectional(Default) =>

or something

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Renamed ActualDirection.Bidirectional to ActualDirection.BidirectionalDefault
Created ActualDirection.Bidirectional object that can be matched against (like case ActualDirection.Bidirectional(_)). It takes an argument of type ActualDirection.Bidirection.Direction, so actually filling in the match is a namespace nightmare and I only expect it to be used with _. The argument is left in both as a reminder that this can match both default and flipped versions, and allowing extensibility for future versions that might relax the namespace issue.

@jackkoenig jackkoenig added this to the 3.0.0 milestone Jun 19, 2017
@jackkoenig
Copy link
Contributor

We need to check if this addresses #282

@jackkoenig
Copy link
Contributor

Check if this fixes #588

Copy link
Contributor Author

@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.

All comments should be addressed.
There should be a test to test for #588, but #282 is going to be a bigger problem.

@@ -68,25 +114,21 @@ object Vec {

// Check that types are homogeneous. Width mismatch for Elements is safe.
require(!elts.isEmpty)
elts foreach {requireIsHardware(_, "vec element")}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

// For bidirectional, must issue a bulk connect so subelements are resolved correctly.
// Bulk connecting two wires may not succeed because Chisel frontend does not infer
// directions.
(vec zip elts) foreach {x => x._1 <> x._2}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed, and changed to parens

case ActualDirection.Input => UserDirection.Input
case ActualDirection.Output => UserDirection.Output
case ActualDirection.Bidirectional | ActualDirection.Unspecified => UserDirection.Unspecified
case ActualDirection.BidirectionalFlip => UserDirection.Flip
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason for having BidirectionalFlip as an ActualDirection is to potentially emitting Bundle bulk-connects to FIRRTL instead of resolving everything to leaves. This makes the check easier, as it can happen at the top level instead of resolving down to leaves.

}
// TODO port technically isn't directly child of this data structure, but the result of some
// muxes / demuxes. However, this does make access consistent with the top-level bindings.
// Perhaps there's a cleaner way of accomlpishing this...
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

And I had the red squigglies in my IDE too...

// TODO port technically isn't directly child of this data structure, but the result of some
// muxes / demuxes. However, this does make access consistent with the top-level bindings.
// Perhaps there's a cleaner way of accomlpishing this...
port.bind(ChildBinding(this), reconstructedResolvedDirection)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has to take the properties of (and act like) its top-level binding, but really isn't (it's more of an accessor, really).

For example, an accessor into a Reg(Vec(...)) behaves like a Reg, but isn't a top-level Reg. I'm not sure how this should be resolved cleanly.

// This recursively walks the tree, and assigns directions if no explicit
// direction given by upper-levels (override Input / Output) AND element is
// directly inside a compatibility Bundle determined by compile options.
def assignCompatDir(data: Data, insideCompat: Boolean) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.
Is this something we can catch with scalastyle?

// directly inside a compatibility Bundle determined by compile options.
def assignCompatDir(data: Data, insideCompat: Boolean) {
data match {
case data: Element if (insideCompat) => data._assignCompatibilityExplicitDirection
Copy link
Contributor Author

Choose a reason for hiding this comment

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

derp

data match {
case data: Element if (insideCompat) => data._assignCompatibilityExplicitDirection
case data: Element => // Not inside a compatibility Bundle, nothing to be done
case data: Aggregate => DataMirror.userDirectionOf(data) match {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair.

case UserDirection.Unspecified | UserDirection.Flip => data match {
case data: Record if (!data.compileOptions.dontAssumeDirectionality) =>
data.getElements foreach (assignCompatDir(_, true))
case _ => data.getElements foreach (assignCompatDir(_, false))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.
Is this also something we can add as a scalastyle rule?

s"output ${e.id.getRef.name} : ${emitType(e.id)}"
case UserDirection.Flip | UserDirection.Input =>
s"input ${e.id.getRef.name} : ${emitType(e.id)}"
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point, fixed

@jackkoenig
Copy link
Contributor

The new Bidirectional implementation has the problem of not correctly detecting when you don't exhaustively match on the different subtypes of ActualDirection. For example:

  DataMirror.directionOf(io) match {
    case Direction.Bidirectional(_) => println("Bidirectional")
  }

This does not give you a warning but should because it only actually handles bidirectional directions.

@ducky64
Copy link
Contributor Author

ducky64 commented Jun 26, 2017

Seems like Scala can't properly generate an exhaustive match warning with an unapply, even if I change the type signature of the unapply target to be more specific (for example, having BidirectionalDefault and BidirectionalFlip extend abstract Bidirectional which extends ActualDirection).

It is possible to match on classes (for example, Bidirectional in the example above), but the syntax is different.

@jackkoenig
Copy link
Contributor

The exhaustive match warning would work if Bidirectional were a case class with just flip as a subfield

@ducky64
Copy link
Contributor Author

ducky64 commented Jun 27, 2017

So that does work.
But it also requires the user of the case class constructor and the direction specification, which means tons and tons of namespace qualifier fun.
Alternatives would be to eliminate the namespace qualification (but that pollutes the global namespace with things like Default and Flipped which are only used here in very specific cases) or to use a bool flipped (but that's not very intuitive and can be a source of bugs).

@jackkoenig
Copy link
Contributor

In my opinion, this way is better since users of the public API probably won't usually care if it's flipped or not so will use Direction.Bidirectional(_). Anyway, thanks for all your hard work! Looks good!

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.

2 participants