-
Notifications
You must be signed in to change notification settings - Fork 603
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
Module Hierarchy Refactor #469
Conversation
A lot of places take a BaseModule (like in connect signature) when they should be taking the more descriptive UserModule. Also, mandating reflection works and having multiple IOs seems a bit too risky: would likely be best to still have 1 IO. Different Module types just may interpret that IO differently (for example, in a raw module, that IO is the true IO whereas in ImplicitModule that IO is a subpart io of the true io which includes clock and reset). As noted, the current approach with IO() is inconsistent with BlackBoxes (and would make it tricky to have programmatic IOs using Record....) |
Yeah, I guess the context_mod should be UserModules since it really doesn't make sense to be issuing connects in a BlackBox. Will fix. Mandating reflection: we already do that in Bundles, so there is precedent. It also uses very similar infrastructure. Whether we should have multiple IO (or whether it's worth having another necessary reflection based system) is a larger question. There have been requests for not needing the Record support is a good point for Blackbox IOs. Though style-wise current Blackbox IOs are already inconsistent with Module IOs (and it shows through the code structure). |
My discussions with other people who use Scala and have done DSL work (but are not involved with Chisel) have reaffirmed avoiding using reflection (particularly runtime reflection) for things. Also, as discovered, the Bundle reflection is already somewhat fragile and people are more likely to have complex Modules than complex Bundles. |
Addendum: This is a good opportunity to bring BlackBox IO's in line with other Module IO's. The desires are also similar: for both you want fine-grained control of IO naming. |
We can have the old Blackbox behavior preserved in the compatibility layer. Though what should the new behavior be? We don't want the |
Submodules of BaseModule decide their IO naming structure. For BlackBox and RawModule, we have them dump their io contents as the raw port names. |
Isn't that what we have now? |
I haven't worked through your code but my impression is that: class TestModuleR extends RawModule {
val io = IO(new Bundle {
val clk = Input(Clock())
val in = Input(Bool())
val out = Output(Bool())
})
} in my proposal would result in 3 ports called "clk", "in", and "out" |
Ah, I see, RawModule is a sibling class to ImplicitModule rather than users inheriting UserModule. It could be done, though it's also inconsistent with ImplicitModule, which I expect to see the most usage because of the implicitly threaded clock and reset. Would be nice to have some way to unify all three, though that might involve mangling the clock and reset naming for ImplicitModule in the event of a conflict. Which all things considered, might not be all that bad? |
Updated with latest resolution. |
Also added exposed RawModule (which is just a UserModule). I'm not sure what kind of tests we want to see there, it's more absence of features than presence of any feature. |
Bump on review... |
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.
Lacks tests. Certain constructs make it clear that there was never an attempt to instantiate an ImplicitModule or LegacyModule inside a non-ImplicitModule.
@@ -121,12 +121,12 @@ object BiConnect { | |||
|
|||
// This function checks if element-level connection operation allowed. | |||
// Then it either issues it or throws the appropriate exception. | |||
def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: Module): Unit = { | |||
def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: UserModule): Unit = { |
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 can drastically simplify this function (at least for the sink := source behavior variant) by just asserting that the sink is writeable and the source is readable then issuing. Then, you don't need to match on all these cases and subcases.
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 think this code improvement is orthogonal to the module refactor, this can be done as a follow-on PR.
@@ -152,16 +152,43 @@ package object Chisel { // scalastyle:ignore package.object.name | |||
object Bool extends BoolFactory | |||
val Mux = chisel3.core.Mux | |||
|
|||
type BlackBox = chisel3.core.BlackBox | |||
import chisel3.core.Param | |||
abstract class BlackBox(params: Map[String, Param] = Map.empty[String, Param]) extends chisel3.core.BlackBox(params) { |
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.
Comments on what this is supposed to be doing distinct from BlackBox would be good.
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 (this auto wraps IO)
|
||
val names = nameIds(classOf[ExtModule]) | ||
|
||
// Name ports based on reflection |
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 is a step backwards to now be relying on the reflection to work in order to get a functionally correct circuit.
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.
But you get consistency in return, in that the io.
prefix isn't being silently dropped.
Again, this is experimental, and we'll see whether people like this or the old style more. I don't think reflection has been a problem, and checks ensure that it will never silently drop a port.
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 refuse to accept the claim that use of reflection has not been a problem. One came up with Bundles (which classically have less implementation trickery than modules since users would come to understand that reflection was being used to inspect them) literally days before you proposed expanding the use of reflection (#456).
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.
And we will fix them as they come up, just like with any Chisel bugs. I don't know of any fundamental issues with reflections that would preclude some functionality.
* val O = IO(Output(Clock())) | ||
* val I = IO(Input(Clock())) | ||
* val IB = IO(Input(Clock())) | ||
* } |
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.
Having to separate each IO into its own val with IO wrapping is more verbose than before....
What is the benefit?
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.
Does this mean users will now have to dot into arbitrary class members, rather than just io?
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.
Furthermore, why are all the I/Os clock? There's nothing clock-related with IBUFDS.
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.
ExtModule is an experimental API and aims for consistency with regular Module (where you have the io.
prefix for io members; old BlackBox dropped the io.
prefix when emitting Verilog).
Under this scheme, users will dot into arbitrary class members. This makes the interface consistent - since the verilog blackboxes don't have io.* prefixes on their IOs, Chisel users won't see those either.
The example is a stock example taken from the current BlackBox class, but refactored for the ExtModule API. I don't know why there are clocks there - that would also be an orthogonal fix.
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 a strong demand for a BlackBox that automatically has io on all members?
That you use this case to justify the feature is bizarre.
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 idea is that if there is no required .io
and have multi top-level IO support, then you don't need to generate the io.
prefix in the FIRRTL/Verilog.
} | ||
|
||
// All suggestions are in, force names to every node. | ||
for (id <- _ids) { |
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 a blackbox so it has no implementation. What nodes need ids?
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.
Because the original BlackBox implementation had it. I think _onModuleClose is needed for Aggregates to do some kind of recursive naming / reference setting. I've added a comment there.
|
||
override_clock match { | ||
case Some(override_clock) => clock := override_clock | ||
case _ => clock := Builder.forcedClock |
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 if the parent module can't resolve forcedClock?
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.
Ditto, crash as would be the case if you create a Reg where you can't resolve forcedClock.
this | ||
override_reset match { | ||
case Some(override_reset) => reset := override_reset | ||
case _ => reset := Builder.forcedReset |
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 if the parent module can't resolve forcedReset?
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.
Ditto
@@ -0,0 +1,84 @@ | |||
// See LICENSE for license details. |
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.
Remove this test or fix 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'm not sure where this came from, probably git add'd something from another branch. Gone now.
@@ -2,8 +2,12 @@ | |||
|
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 file should be split into multiple files.
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.
Split Module.scala (object Module, class BaseModule) and UserModule.scala (class UserModule, class ImplicitModule, class LegacyModule)
} | ||
|
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 if the parent module cannot resolve forcedClock or forcedReset?
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.
Then you get a crash, just like what would happen if you tried to instantiate a Reg inside a non-ImplicitModule.
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 reg not being instantiated in a non-implicit module is a problem because there is no way to later supply that reg a clock. In this case, there is since the module already exposes clock and reset pins.
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 would argue the stylistic way to do this is to use withClockAndReset, rather than assigning the clock and reset pins as connect statements.
Do you have a good example use case where you connect the clock and reset ports, but withClockAndReset is a bad fit?
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.
All comments addressed.
Major points:
- some suggested code improvements are orthogonal to the module hierarchy refactor - I don't want this mega-PR to become even more mega. Can you open an issue so those can be discussed and addressed separately?
- added tests for RawModule
- Overall, if you instantiate an ImplicitModule inside a RawModule without a withClockAndReset block, it will fail. This is consistent with what would happen if you instantiated a Reg inside the same.
- the benefits vs. drawbacks of allowing multiple top-level IO has been debated, and the resolution (which this implements) is to try allowing multiple IOs in experimental. The debate included whether it was appropriate to have reflection name IO signals and consistency between ExtModule and other regular module types.
- refactoring to limit access to the internal mutable variables like _ids, _commands, _ports
- other general (mostly style / comments) cleanup
@@ -121,12 +121,12 @@ object BiConnect { | |||
|
|||
// This function checks if element-level connection operation allowed. | |||
// Then it either issues it or throws the appropriate exception. | |||
def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: Module): Unit = { | |||
def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: UserModule): Unit = { |
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 think this code improvement is orthogonal to the module refactor, this can be done as a follow-on PR.
* val O = IO(Output(Clock())) | ||
* val I = IO(Input(Clock())) | ||
* val IB = IO(Input(Clock())) | ||
* } |
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.
ExtModule is an experimental API and aims for consistency with regular Module (where you have the io.
prefix for io members; old BlackBox dropped the io.
prefix when emitting Verilog).
Under this scheme, users will dot into arbitrary class members. This makes the interface consistent - since the verilog blackboxes don't have io.* prefixes on their IOs, Chisel users won't see those either.
The example is a stock example taken from the current BlackBox class, but refactored for the ExtModule API. I don't know why there are clocks there - that would also be an orthogonal fix.
|
||
val names = nameIds(classOf[ExtModule]) | ||
|
||
// Name ports based on reflection |
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.
But you get consistency in return, in that the io.
prefix isn't being silently dropped.
Again, this is experimental, and we'll see whether people like this or the old style more. I don't think reflection has been a problem, and checks ensure that it will never silently drop a port.
} | ||
|
||
// All suggestions are in, force names to every node. | ||
for (id <- _ids) { |
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.
Because the original BlackBox implementation had it. I think _onModuleClose is needed for Aggregates to do some kind of recursive naming / reference setting. I've added a comment there.
private[core] def initializeInParent() { | ||
implicit val sourceInfo = UnlocatableSourceInfo | ||
|
||
_ports.foreach { x: Data => |
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.
val names = super.nameIds(rootClass) | ||
|
||
// Allow IO naming without reflection | ||
names.put(io, "io") |
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 standard names get priority (HashMap updated after the reflection naming).
|
||
override_clock match { | ||
case Some(override_clock) => clock := override_clock | ||
case _ => clock := Builder.forcedClock |
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.
Ditto, crash as would be the case if you create a Reg where you can't resolve forcedClock.
this | ||
override_reset match { | ||
case Some(override_reset) => reset := override_reset | ||
case _ => reset := Builder.forcedReset |
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.
Ditto
@@ -152,16 +152,43 @@ package object Chisel { // scalastyle:ignore package.object.name | |||
object Bool extends BoolFactory | |||
val Mux = chisel3.core.Mux | |||
|
|||
type BlackBox = chisel3.core.BlackBox | |||
import chisel3.core.Param | |||
abstract class BlackBox(params: Map[String, Param] = Map.empty[String, Param]) extends chisel3.core.BlackBox(params) { |
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 (this auto wraps IO)
@@ -0,0 +1,84 @@ | |||
// See LICENSE for license details. |
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 not sure where this came from, probably git add'd something from another branch. Gone now.
I find these arguments to 'consistency' are weak, particularly because you are exposing very new functionality. Also, the argument is not compelling. I can similarly argue that it is inconsistent that ImplicitModule and BlackBox IO must be accessed wholly through io but RawModule and ExtModule io must access through arbitrary members. Similarly, the user of a module must now comb through the implementation of RawModule and ExtModule to find which fields are actually ports. (This is even setting aside designers being 'clever' and parametrically making certain fields IOs versus internal nodes as demonstrated in the prior issue.) |
With regards to the multi IO behavior, we can have another discussion about it during the next Chisel meeting, but the resolution from the last discussion was that it does have benefits and it's worth trying. With regards to consistency: I'd argue that (assuming people like the Multi IO idea) the MultiIOModule and ExtModule style will be the new style. |
type BaseModule = chisel3.core.BaseModule | ||
type MultiIOModule = chisel3.core.ImplicitModule | ||
type RawModule = chisel3.core.UserModule | ||
type ExtModule = chisel3.core.ExtModule |
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.
Move to experimental
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 in experimental ...
(GitHub diff apparently can't handle inner classes / objects / scopes when telling you the enclosing scope of a block of code)
|
||
// Avoid collisions with regular BlackBox tests by putting ExtModule blackboxes | ||
// in their own scope. | ||
package ExtModule { |
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 this just be a new package inside of chiselTests including the directory structure?
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 just need this to deconflict the scope, and the current implementation seems lightweight and fine.
import chisel3.experimental._ | ||
import chisel3.testers.BasicTester | ||
import chisel3.util._ | ||
//import chisel3.core.ExplicitCompileOptions.Strict |
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.
Remove 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.
Fixed.
@sdtwigg @jackkoenig issues fixed, inheritance composition with multiple top level IOs test added. Turns out the new stuff was in experimental all along, just GitHub doesn't do a good job of showing inner enclosing scopes. |
Have you got a simple example to test this pull request ? I tried it with a simple blinking led project : https://github.com/Martoni/blp/tree/master/platforms/kintex7/chisel3 But I can't manage to generate verilog: |
@Martoni oversight in forgetting to refactor the Driver methods; fixed now. |
Heh, I made the same oversight internally. |
Thanks @ducky64, I pulled the last commits. But I can't find how to encapsulate my modules correctly in withClockAndReset. (How to do reset if I don't have one ?). |
You can pass in any Bool for the reset signal, including |
Hi, Works for me. |
Rebased to latest master. |
This is probably fine as long as the controversial parts actually remain in experimental. |
Hooray! |
retest this please |
Broke no longer broken Fix blackbox Moar fix Refactor mod_context -> UserModule Stricter checks on ImplicitModule MultiIO Module extmodule support MultiIOTester Expose RawModule
retest this please |
1 similar comment
retest this please |
Another car in the mega-PR train bites the dust? |
That's actually it for outstanding mega-PRs right now, the rest of them are mostly around 100 lines changes. So the entire PR train is in the station ... for now ... |
That works for me :) Then I could respond to my stackoverflow question : http://stackoverflow.com/questions/41427717/how-to-delete-clock-signal-on-chisel3-top-module/43651603#43651603 |
Implementation for the proposal in #427
Major changes:
val myIo = IO(...)
. IO names are no longer hardcoded.Debate points:
io
be required in ImplicitModule? Perhapsio
should be more a convention (or even have IOs turned into the wild wild west)?io
handling is inconsistent with Moduleio
. Blackboxes don't emit theio
prefix when elaborating; this was likely to allow it to do something useful before we even considered having IOs not inio.
. Now that this can change - should it? (as in, instead of putting things in anio
bundle, just dump it directly into the BlackBox asIO(...)
?io
(except in compatibility mode, the auto-wrap handles things properly), because it seems that every time you defineval io = IO(...)
, even if overridden, it calls the IO and creates a port. The overridden ones are unable to get a name through reflection and fails.port_1
, ... On the other hand, some use cases may require good names, like if you had a top-level Verilog file instantiating a Chisel-generated module. Additionally, we rely on reflection for Bundle elements.