[RFC] Bundle Literals
See original GitHub issue(supersedes #418 with a more detailed proposal of syntax and thoughts on implementation concerns, supersedes #694 with an alternative, I think cleaner, implementation path)
Motivation
I don’t think much needs to be said here. Main use cases would be in testing (for concise and simple testvector specification) and in initializing Bundles with RegInit and WireInit. Maybe also ROMs, but there’s a whole other issue for that.
Note: it’s possible to construct Bundle-literal-like objects by casting from a UInt with asTypeOf, but specifying anything but all zeros requires knowledge of how Bundles are bit-packed, which is not great. This also requires a Builder context, making it incompatible with testers.
Proposed syntax
Given a concrete instance of a Bundle type, an autogenerated (by macro annotation) Lit function would return a new literal of that type.
For example, if I have
@bundle
class MyBundle(val width: Int) extends Bundle {
val a = Bool()
val b = UInt(width.W)
}
I would be able to do:
myBundleIo := (new MyBundle(8)).Lit(a=true.B, b=255.U)
Edge cases
It is not necessary to be in a Chisel Builder context to construct a Bundle literal (barring some logic construction being in the Bundle definition, which we have no control over). This allows its use in testers.
Using keyword arguments allows the partial specification of literals. Current proposal is for everything not specified to be treated as DontCare
.
Bundle literals will take a literal interpretation of DontCare
, that is, DontCare
means DontCare
(and might show up as X-like in simulation) instead of DoesntExist
(where the previous value on the wire, if any, would not be overwritten).
Open questions
Should keyword arguments be used? On the plus side, it allows a partial specification, however, it is incompatible with def macros, which are used for source locators. Note that currently, literals do not provide source locators, but this could prevent compatibility with def macros in the future. (current proposal will be to use keyword arguments)
Because Bundles can take parameters, the current proposal requires a literal to be constructed given a Bundle instance (so Lit
would be a method on an instance, instead of on a companion object). Does it make sense to have a shorthand version in the companion object when no parameters are required? For example, if I defined:
@bundle
class MyBundle extends Bundle {
val a = Bool()
val b = UInt(8.W)
}
should MyBundle.Lit(a=true.B, b=255.U)
be allowed?
Implementation concerns
Dealing with LitArg
Literal values are currently stored in the Data subtype objects with an immutable litArg
. However, this requires literals to be known at instantiation time, which is not possible because fields are instantiated in the Bundle itself, before the literal constructor is run.
Proposal is to move the litArg
data into LitBinding, which probably makes more sense anyways. Note that this would result in a loss of (internal, within Chisel, not user-visible) type safety, since LitBinding would need to take values for UInt, Bool, and FixedPoint.
Bindings
Currently, hardware is bound either as a ChildBinding (element of a larger Aggregate) or some kind of leaf binding like LitBinding (indicating an Element is a literal). There are two proposals for binding Bundle literals:
Bindings stay top-level
In this approach, LitBinding could contain a map of subelements to their arguments. This works very well with the current structure implementation-wise, however, requires indirection to look up the value of a literal given its Data subtype object. Getting a sub-Bundle literal may also be costly.
It may be possible to have an inconsistent map: containing missing literal values, or extra literal values. Missing literal values could be interpreted as DontCare, or cause an error.
Allow intermediate hierarchy nodes to have top-level bindings
The possibilities here are to either have a ChildLitBinding (which has a parent pointer and a literal value) or to drop the parent pointer (ChildBinding) entirely and only have a LitBinding on leaves.
The main issue with the current structure is that Data objects can’t be rebound. It might make sense to allow additional bindings: for example, attempting to rebind a Data that has an existing ChildBinding with a new LitBinding will replace the ChildBinding with a ChildLitBinding. Obviously, it’s also possible to allow rewriting Bindings, but isn’t an elegant solution.
Structural inconsistency is a possibility here, some leaves may be missing their additional LitBinding. In those cases, the system can either raise an error (strict mode, guarding against implementation errors), or treat it as DontCare. But unlike the ‘Bindings stay top-level’ proposal, it is impossible to have extra literal values.
Fixing n^2 Bundle instantiation
A somewhat related issue is that Bundle instantiation could be n^2 in clones. That is, each Bundle clone must clone its fields, done recursively. Because of the immutable Data objects abstraction we want to present in Chisel and the possibility of aliasing, even in Bundles (a great example would be the use of genType in multiple fields, perhaps through an Input(...)
or Output(...)
binding) I think we want to retain cloning behavior, and I’m not sure if there’s a way to avoid the n^2 clones.
A larger refactor that changes the behavior of Bundle-specific Input(...)
, Output(...)
or (the proposed) Field(...)
in an intelligent manner might address this problem, though that’s future work. When this becomes a serious bottleneck.
Thoughts, ideas, and suggestions on anything in this proposal are appreciated.
Issue template
Type of issue: other enhancement
Impact: API addition (no impact on existing code)
Development Phase: proposal
Issue Analytics
- State:
- Created 5 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
@mwachs5 I think getting a literal Bundle back as its bits numeric representation could be made to work. Might not be in the first version, but the foundation for it is there
Using the syntax above and testers2 literal extractors, it would probably look like
(new MyBundle(8)).Lit(a = true.B, b = 255.U).litToBigInt
=>BigInt(0x1ff)
The yak shaving continues!
One issue is the use of _id, which does not produce useful results outside a Builder context. I think it would make sense to change it to a globally valid incrementing ID (does not reset between runs, so we’d want some kind of overflow detection - this could also mean long-running SBT instances would occasionally crash).
Note: the two current uses I see for _id are: