-
Notifications
You must be signed in to change notification settings - Fork 642
Add multiple dimensions to VecInit fill and iterate #2065
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
Add multiple dimensions to VecInit fill and iterate #2065
Conversation
…to vec-init-branch
|
Can you update the PR title? |
| myVec | ||
| } | ||
|
|
||
| /** Creates a new 4D [[Vec]] of length `n by m by p by q` composed of the result of the given |
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 there any particular reason you went to 4D, not 3D or 5D? Is this paralleling something else in the codebase and/or scala?
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.
Scala seqs can go to 4D, so I think its to follow that pattern (https://www.scala-lang.org/api/2.13.3/scala/collection/SeqFactory.html)
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.
Ah, I See that scala Seq.tabulate and so on have up to 5D versions. But there they don't have different names (tabulate vs tabulate2D), they just have different numbers of parameters to distinguish... any reason to not follow that pattern
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 I might not encourage people to write 5D versions in hardware, and might stop at 3D for chisel... but willing to be convinced otherwise... what does the code even look like for a 4D or 5D generated vec?
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.
Ah oh I guess I forgot that it went to 5D, @jackkoenig suggested to go to 4D and I assumed thats kinda where it came from... I could add a 5D if you want 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.
i guess i'd be more inclined to go lower, not higher, but I'd defer to @jackkoenig on what he thinks is best. I'm not sure how realistic 5-d generated hardware would be / how happy tools like VCS would be about trying to do something with 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 was thinking 4D because it's what Seq did in Scala 2.12. I agree that 5D is a bit much and maybe 4D is too much. Maybe let's go to 3D for the time being?
src/test/scala/chiselTests/Vec.scala
Outdated
| if (n == 0) assert(arr(n).asUInt() === compVec.asUInt) | ||
| else { | ||
| assert(arr(n).asUInt() === compVec.asUInt()) | ||
| assert2DIsCorrect(n-1, arr, compVec) | ||
| } |
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.
could this and other tests be simplified to:
| if (n == 0) assert(arr(n).asUInt() === compVec.asUInt) | |
| else { | |
| assert(arr(n).asUInt() === compVec.asUInt()) | |
| assert2DIsCorrect(n-1, arr, compVec) | |
| } | |
| assert(arr(n).asUInt() === compVec.asUInt() | |
| if (n != 0) assert2DIsCorrect(n-1, arr, compVec) |
? Not sure if that will work, but seems equivalent and less repeat-y
mwachs5
left a comment
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.
could you add something to the docs / cookbook about the 2d usage, so that it's more discoverable?
jackkoenig
left a comment
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 bit of a long review because there are some subtleties here. I'd also like to see tests that check for bidirectional support.
| def do_tabulate[T <: Data](n: Int, m: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { | ||
| val myVec = Wire(Vec(n, Vec(m, cloneSupertype((0 until m).map(i => gen(i)), "Vec")))) | ||
| myVec.foreach { | ||
| _ := VecInit.tabulate(m)(gen) | ||
| } | ||
| myVec | ||
| } |
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.
Using := is actually not correct--VecInit.tabulate needs to support bidirectional aggregates and := is a unidirectional connection. Now unfortunately, just switching this implementation to using <> also won't work due to the fact that <> is commutative and thus it is ambiguous which direction connections should go if both sides of a <> are wires (if one side is a port, that side "anchors" the connection making it unambiguous). For example:
This works (both sides ports)
val enq = IO(Vec(4, Decoupled(UInt(8.W))))
val deq = IO(Flipped(Vec(4, Decoupled(UInt(8.W)))))
deq <> enqThis works (one side port, one side wire)
val enq = IO(Vec(4, Decoupled(UInt(8.W))))
val deq = IO(Flipped(Vec(4, Decoupled(UInt(8.W)))))
// Effectively what VecInit does
val wire = Wire(Vec(4, Decoupled(UInt(8.W))))
wire <> enq
deq <> wireBut this doesn't work (both sides wire):
val enq = IO(Vec(4, Decoupled(UInt(8.W))))
val deq = IO(Flipped(Vec(4, Decoupled(UInt(8.W)))))
// Effectively what VecInit does
val wire = Wire(Vec(4, Decoupled(UInt(8.W))))
wire <> enq
val wire2 = Wire(Vec(4, Flipped(Decoupled(UInt(8.W)))))
wire2 <> deq
wire2 <> wireIn order to make this higher dimensional fills and tabulates work, they cannot create a Wire and delegate to a function that also creates a Wire. I think the only way to do this correctly is to create the wire in each tabulate and then connect every single element (no delegation).
Also, looking at the fact that the 1D tabulate delegates to do_apply which has this weird logic, I think we should preserve that functionality as well. I'm envisioning factoring that out into a private higher-order function like so:
private def getConnectOpFromDirectionality[T <: Data](proto: T): (T, T) => Unit = proto.direction match {
case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified =>
// When internal wires are involved, driver / sink must be specified explicitly, otherwise
// the system is unable to infer which is driver / sink
(x, y) => x := y
case ActualDirection.Bidirectional(_) =>
// 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.
(x, y) => x <> y
}Then you could use that for this function as:
| def do_tabulate[T <: Data](n: Int, m: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { | |
| val myVec = Wire(Vec(n, Vec(m, cloneSupertype((0 until m).map(i => gen(i)), "Vec")))) | |
| myVec.foreach { | |
| _ := VecInit.tabulate(m)(gen) | |
| } | |
| myVec | |
| } | |
| // Also gen should take more Ints, 1 per dimensionality | |
| def do_tabulate[T <: Data](n: Int, m: Int)(gen: (Int, Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { | |
| // TODO make this lazy (requires LazyList and cross compilation, beyond the scope of this PR) | |
| val elts = Seq.tabulate(n, m)(gen) | |
| val flatElts = elts.flatten | |
| // These checks are copied from do_apply, perhaps some room for factoring out | |
| require(flatElts.nonEmpty, "Vec hardware values are not allowed to be empty") | |
| flatElts.foreach(requireIsHardware(_, "vec element")) | |
| val tpe = cloneSupertype(flatElts, "Vec.tabulate") | |
| val myVec = Wire(Vec(n, Vec(m, tpe))) | |
| val op = getConnectOpFromDirectionality(tpe) | |
| // We could get fancy here to have reuse across the multiple dimensions, but being more verbose is more clear | |
| for { | |
| (xs1D, ys1D) <- myVec zip elts | |
| (x, y) <- xs1D zip ys1D | |
| } do op(x, y) | |
| myVec | |
| } |
I know this is a lot, and this is made complex by the lack of a non-commutative <>, but it is what it 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.
Instead of the nested for, we could actually just flatten and iterate:
for ((x, y) <- myVec.flatten zip flatElts) {
op(x, y)
}This requires fewer intermediate names and would work basically the same way for the higher dimensional versions (with extra .flattens)
| def do_fill[T <: Data](n: Int, m: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { | ||
| val myVec = Wire(Vec(n, Vec(m, cloneSupertype(Seq.fill(m)(gen), "Vec")))) | ||
| myVec.foreach { | ||
| _ := VecInit.fill(m)(gen) | ||
| } | ||
| myVec | ||
| } |
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.
Note that fill is just a special case of tabulate (where you ignore the indices) so this can be implemented as just:
| def do_fill[T <: Data](n: Int, m: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { | |
| val myVec = Wire(Vec(n, Vec(m, cloneSupertype(Seq.fill(m)(gen), "Vec")))) | |
| myVec.foreach { | |
| _ := VecInit.fill(m)(gen) | |
| } | |
| myVec | |
| } | |
| def do_fill[T <: Data](n: Int, m: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = | |
| do_tabulate(n, m)((_, _) => gen) |
| myVec | ||
| } | ||
|
|
||
| /** Creates a new 4D [[Vec]] of length `n by m by p by q` composed of the result of the given |
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 thinking 4D because it's what Seq did in Scala 2.12. I agree that 5D is a bit much and maybe 4D is too much. Maybe let's go to 3D for the time being?
jackkoenig
left a comment
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.
Some nits and then a couple of suggestions on the tests.
|
|
||
| } | ||
| ``` | ||
|
|
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 good to actually run this test too:
| ```scala mdoc:invisible | |
| // Hidden but will make sure this actually compiles | |
| ChiselStage.emitVerilog(new Foo) | |
| ``` |
macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala
Outdated
Show resolved
Hide resolved
| } | ||
| stop() | ||
| } | ||
| } |
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 are no tests for the .tabulate. I'd do something similar but it might be useful to factor in the indices in the tabulate into the test somehow.
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 fills call tabulate, bc they are a special case of tabulate, so I figured by calling 2D/3D fill I would be catching tabulate within 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.
They do right now, but part of the goal of tests is catching if someone were to refactor things and get it slightly wrong. Perhaps after a refactor fill won't call tabulate anymore and I'll bet the person doing the refactor won't bother to check the tests.
jackkoenig
left a comment
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! Great work @anniej-sifive!
Contributor Checklist
docs/src?Type of Improvement
API Impact
This extends the tabulate and fill functions for VecInit to create multi-dimensional Vecs
Ex. Vec[Vec[UInt]]
Backend Code Generation Impact
This should not change the backend code generation as it only provides different ways to fill a multi-dimensional Vec.
Desired Merge Strategy
Release Notes
This PR adds builds upon companion object factory methods for the VecInit class, .fill and .iterate, to include multi-dimensional Vecs.
Reviewer Checklist (only modified by reviewer)
Please Merge?