-
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
Directions internals mega-refactor #617
Conversation
rocket-chip builds on this now, with one necessary change: in (rocket-chip)/ |
For the record, I ran this branch with rocket-chip and it passes Travis: chipsalliance/rocket-chip#770 |
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.
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")} |
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 always use .
notation with foreach
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.
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") |
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'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")
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.
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") |
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 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.
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.
fixed.
case PortBinding(_) => direction match { | ||
case ActualDirection.Output => Output | ||
case ActualDirection.Input => Input | ||
case dir => throw new RuntimeException(s"Unexpected port element direction '$dir'") |
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.
Why is this a RunTimeException and not a ChiselException or something else?
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.
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.
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 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 |
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.
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 |
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.
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....
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 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} |
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.
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)
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.
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... |
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.
accomplishing
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.
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) |
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 see the comment but I don't really get why this is a ChildBinding
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.
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 |
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 this match the only reason why there is both Bidirectional and BidirectionalFlip?
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.
might it not be better to just store the UserDirection rather than muddying up the ActualDirections if so?
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.
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.
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 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
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.
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.
We need to check if this addresses #282 |
Check if this fixes #588 |
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.
@@ -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")} |
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.
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} |
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.
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 |
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.
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... |
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.
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) |
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.
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) { |
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.
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 |
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.
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 { |
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.
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)) |
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.
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)}" | ||
} |
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.
good point, fixed
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. |
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. |
The exhaustive match warning would work if Bidirectional were a case class with just flip as a subfield |
So that does work. |
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 |
Part 1 of mega-change in #578
Major notes:
Internal changes notes:
Discussion points
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.