chisel
chisel copied to clipboard
Make ChiselEnum take parameters (to make the width adjust as necessary)
Type of issue: feature request
Impact: API addition (no impact on existing code)
Development Phase: request
Other information
If the current behavior is a bug, please provide the steps to reproduce the problem:
What is the current behavior?
If I build a ChiselEnum with lots of values, the width of the signal is adjusted accordingly. But sometimes I may know that some values will never be used in given configurations, yet the width will always be the maximum necessary to accomodate all the values.
For example: https://scastie.scala-lang.org/3uueK6ybS9S3wjFrRJ6OSA
What is the expected behavior?
If setting the above true to false the code would compile and the width of the wires would only be 1 bit to accomodate the 2 enums
Please tell us about your environment:
- version: 3.5.1
What is the use case for changing the behavior?
Be more explicit about optimized hardware in terms of narrower wires/ less impossible coverage
If I build a ChiselEnum with lots of values, the width of the signal is adjusted accordingly. But sometimes I may know that some values will never be used in given configurations, yet the width will always be the maximum necessary to accomodate all the values.
What keeps you from making separate enums for the modules in which you do not need to use all values? Is the problem that you want to be able to interoperate with other modules that do use all the values?
The use case is a module which is parameterized, what is the type of its fields that might depend on the paramterization. One alternative might be (if we supported Either types in Data):
val io = IO( new Bundle {
val opcode : Either[ChiselEnumWithAllTheFields, ChiselEnumWithOnlySomeFields] = Input(Left(new ChiselEnumWithAllTheFields()))
})
?
I guess you don't need an Either for this, you can just do it with Type parameters and a gen.
The above linked example (originally authored by me) is not really illustrative of the intended use. This enum is constructed of elements which are effectively tags/select for some payload. For a parameterized module, some enum element values may not be referenced at all in the generated logic depending on the parameters to the module. If a bundle uses this enum, or uses the getWidth method to generate other hardware types, some bits in the enums encoding space will go unused. These may or may not be optimized away during future elaboration steps and optimization passes. I'm not sure what data type ChiselEnums eventually reduce to, and if they are able to have their width reduced, pretty sure the width goes unchanged.
Heres a more concrete example: https://scastie.scala-lang.org/aWwq1E2VSdWSCYKl2EbPLw
Thanks for the more detailed example @hanselmandrew-sifive. I feel like maybe this could be addressed by a firrtl compiler optimization which detects when the MSBs of a signal are constant and then reduces the size of that signal accordingly. That way we could do this optimization without any use input and without any way for the user to accidentally introduce bugs.
You can coerce this now if you are comfortable with the type of the Bundle being an inferred-width UInt. This obviously loses the type safety in the bundle declaration, but may work for your use case. Note: this then requires casting any uses of the enum with asUInt.
Here's your modified snippet @hanselmandrew-sifive: https://scastie.scala-lang.org/4aOAEtArSsqSK6iVCtzSRA
Definitely. I think a firrtl compiler optimization would be a good fit for that case. Originally, I came upon this when I was casting the ChiselEnum to a UInt for use in some bit concatenation, which produced a UInt of myEnum.getWidth width. That was a rough implementation, and I have since moved to using it properly typed and instantiated in a bundle. Unfortunately, IO still exists with greater width than necessary to support the desired enum space.
@seldridge Typing the above as you replied. Yeah. Moving to ChiselEnum specifically for the type safety and more consistent value accessors. I'm happy that it does reduce the IO width, though type safety is what I was after... shoot.
Something I was thinking would be useful in this specific case is being able to instantiate some object, val packedEnum = myEnumObject.packed(true, true, false) which expresses a ChiselEnum that contains some subset of the higher order enum. Such that accesses to packedEnum.enum0 or packedEnum.enum1 would behave normally, but accessing packedEnum.enum2 would throw some exception as not being accessible from the derived packedEnum object. Maybe allowing nice type safety along with explicit minimization of the enum's bitwidth?
Part of this, not really captured by the myEnumOject.packed(...) is that you may be able to do something like val packedEnum = myEnumObject.packed(true, false, true) and still have UInt(1.W) wires generated. I don't think I exactly captured the nuance that would be required with the type system, the packed method and its arguments, but just my stab at what I thought would be useful to me in this instance.
Mid-term we need to capture user-defined Chisel types in FIRRTL IR. (We are already running into a need for this now that aggregate preservation is coming online with CIRCT and FIRRTL IR doesn't capture any information about a Chisel Bundle other than its structural shape.) I think it's reasonable to look at user-defined enumerated types and then to see what width inference could look like for that. The more general problem is an enumerated type where the compiler is free to choose the encoding. There is some precedence here with synthesis and state machine optimizations.
I do think an important design concern is to make any width changes unsurprising to users.