-
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
Strong enums #892
Strong enums #892
Conversation
…num" will automatically generate annotations that HDL backends can use to mark components as enums Removed "override val width" constructor parameter from "Element" so that classes with variable widths, like the new strong enums, can inherit from it Changed the parameter types of certain functions, such as "switch", "is", and "LitArg.bindLitArg" from "Bits" to "Element", so that they can take the new strong enums as arguments
…that the correct types of exceptions are thrown
With regards to "Casting a non-literal UInt to an enum" --> what if this casting operation returned a tuple of the enum and a valid bit? i.e. as follows: val myUInt: UInt = io.myInput
val (asEnum, valid) = MyEnumClass.fromUInt(myUInt) Then if |
@edwardcwang The problem is that we would need to insert circuitry that constantly monitors the value of |
Well, if we don't use the val asEnum = MyEnumClass.fromUIntRaw(myUInt)
// or
val (asEnum, unused) = MyEnumClass.fromUInt(myUInt, raw=true) Honestly, I would just recommend generating both and letting FIRRTL/synthesis tools prune away unused logic. Having this extra option could be a good balance between safety and performance (if you care about safety or if fault tolerance is important, then use |
@edwardcwang That makes sense. I'll look into implementing this feature. It should be pretty easy. |
…s used in multiple circuits Made styling changes to StrongEnum.scala
Why not just add an assertion instead of a valid signal? That makes it easier to use and automates the simulation-only checking. |
It might legitimately be invalid on some clock cycles, so you'd have to gate the assertion. |
True! |
Added an isValid function that checks whether or not enums have valid values Calling getWidth on an enum's companion object now returns a BigInt instead of an Int
…unction is now simply a wrapper for StrongEnum.apply(n)
OK, I've added an
I suppose if users want, they can |
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 you should change the test
test/scala/cookbook/FSM.scala
to use your new Enums, people look at the tests to
see the right way to do something
* Renamed "castFromNonLit" to "fromBits"
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 this looks amazing, great work. I have some comments, suggestions, etc.
One specific thing is I am wondering how we should think about inheritance with enumerations; not because I think it should be allowed, but this makes it kind of look like you can use it. The following leads to an error:
import scala.reflect.ClassTag
abstract class Parent[T <: EnumType : ClassTag] extends StrongEnum[T] {
val e0, e1, e2 = Value
}
class Bar extends EnumType
object Bar extends Parent[Bar] {
val e3 = Value
}
[error] Caused by: chisel3.core.EnumExceptions$IllegalDefinitionOfEnumException: chiselTests.JackTest$Bar defined illegally. Did you forget to call Value when defining a new enum
?
[error] at chisel3.core.StrongEnum.<init>(StrongEnum.scala:336)
I suspect inheritance should just not be allowed but just wanted to bring this up.
val mirror = runtimeMirror(this.getClass.getClassLoader) | ||
|
||
// We use Java reflection to get all the enum fields, and then we use Scala reflection to sort them in declaration | ||
// order. TODO: Use only Scala reflection 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.
@ducky64 Could we use this mechanism for Bundle field ordering? I suspect Scala reflection was too flakey back in the day for this to work, but maybe it does now?
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.
Yes, there were cases where Scala reflection just pukes all over the place. I think this had to do with inner classes and fun like that, so we're still stuck with looking at id order, which is kind of hacky and nasty.
We use Scala reflection in autoclonetype, but that's fine because that's best effort - it's more or less accepted to tell the user that they need to overload cloneType (though we might not have if we were doing clean-slate design).
@jackkoenig Your comments sound good and I can start implementing them. However, last night, I discovered the magic of macro annotations, and I wrote an (ugly) macro that converts this: @ChiselEnum
object State {
val sIdle, s1, s2 = Value
} to something that behaves just like class State extends EnumType
object State extends StrongEnum[State] {
val sIdle, s1, s2 = Value
} I'm thinking of changing this to the official API, since it reduces boilerplate. The disadvantage is that IntelliJ won't be able to provide users with code completion in this case, but it may still be worth it. I also think I might be able to remove the "only static enums" limitation if we use this annotation instead. Do you (or anyone else) have any thoughts on this? Does it look elegant to you? Edit: I can confirm that macro annotations remove the "only static" restriction. In some ways, they make the code uglier, but in others, they make it cleaner. In particular, the recursive constructor will be no longer be needed. |
Expanding the use of annotations is interesting, though probably warrants discussion. The current place where annotations are used is only in chiselName, which doesn't affect the generated logic. There is also discussion to use annotations for templating the |
…th a class and a companion object for each strong enum * Strong enums do not have to be static any longer
Sorry for doing this after my code had already been reviewed, but I've changed the API again, one last time: object State extends ChiselEnum {
val sIdle, s1, s2 = Value
} This is just as concise as Scala Enumerations, and preserves type-safety. Additionally, with this API, enums do not have to be static. Macro annotations were fun, but IntelliJ can't handle them, so I got rid of them. |
Thanks for keeping IntelliJ in mind! |
…into strong-enums * Renamed ChiselEnum.E to ChiselEnum.Value to better match Scala enums
…ll it outside of a ChiselEnum definition * Renamed ChiselEnum.Value type to ChiselEnum.Type so that we can give it a companion object just like UInt and Bool do
The new enums work by creating a different Here's how they can be used (it's nearly the same as the old version): object State extends ChiselEnum {
val sIdle, s1, s2 = Value
}
val state = RegInit(sIdle)
// or, alternatively:
val state = Reg(State())
state := sIdle
// or, alternatively:
val state = Reg(State.Type())
state := sIdle There is one downside to this implementation, which is shared by Scala enumerations: def foo(e: MyEnum.Type) = ???
def foo(e: OtherEnum.Type) = ??? // This causes a compiler error Basically, you can't overload functions with different types of enums as parameters. However, it's important to note that type-safety is preserved in the following sense: def foo(e: MyEnum.Type) = ???
foo (MyEnum.s1)
foo (OtherEnum.sOther) // This will cause a compiler error So during compilation, Scala will prevent users from passing in the wrong types of enums to functions. Personally, I don't think there are going to be many use cases where users want to overload functions that take enums as parameters, so I think the reduced verbosity of this new style is worth 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.
It all looks good to me. My only concern is the change to Element
s signature could have upstream consequences. I am not sure if the testing on this branch has run the chisel-testers test suite with this branch. If it has then I will approve.
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 comments about the implementation.
I second @jackkoenig - we should expose this in chisel3.experimental
instead of the base chisel3
package.
|
||
def all: Seq[Type] = enum_instances.toSeq | ||
|
||
protected def Value: Type = macro EnumMacros.ValImpl |
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 probably have some documentation, preferably ScalaDoc
@hngenc can you update the branch to up to date with master and this can be merged. |
* Non-literal UInts can now be cast to enums with apply() rather than fromBits() * Reduced code-duplication by moving some functions from EnumType and Bits to Element
…into strong-enums
This is an implementation of the strongly-typed enums proposed in #885. It satisfies all the conditions laid out there, along with these extra features:
StrongEnum.all
functionCaveats
The enums do have two primary caveats that should be considered:
API Modifications
master
branch,Element
inherits fromData
and override'sData
'sdef width
with aval width
in the constructor. I removed this constructor parameter fromElement
, so thatEnumType
could inherit fromElement
without having to specify the width directly in the constructor (sinceEnumType
's width isn't actually known until allEnumType
s have been instantiated). Now, other subclasses ofElement
, likeBits
andClock
, must explicitly overrideData
'swidth
with their own implementations (which I have already taken care of).switch
,is
andbindLitArg
require parameters of typeBits
in themaster
branch. However, sinceEnumType
must also work with these functions, and sinceEnumType
isn't a subclass ofBits
, I modified these functions so that they instead take in parameters of typeElement
.Neither of these API modifications should break any existing user code. All the old tests still pass.
Related issue: #885
Type of change: feature request
Impact: API modification
Development Phase: implementation
Release Notes