Open edwardcwang opened 5 years ago
Apparently this dates back to Chisel 2 design decisions. Some old discussion here: https://github.com/ucb-bar/chisel2-deprecated/issues/672. This is more about suggesting that +
should map to +&
as opposed to +%
.
This situation here is subtly different as the intuitive behavior when adding literals is that width expands and it may be okay if that's different from the default behavior when adding hardware types. No clue how to handle addition that mixes literals and hardware types and to do that in an intuitive way.
There was some discussion a while ago about literals having infinite width or a way of expressing literals differently. Some exploration of that might help.
If anything, at least the current situation has consistent behavior. +
always means +%
. Perhaps it's worth being very explicit about this, and how literal widths are determined, in the bootcamp.
Maybe you should change the bootcamp to explicitly state the widths of UInt literals. Is that what users are expected to do...?
Infinite width operations were proposed in #867 with a PR in #869. Ultimately, that PR broke backwards compatibility with what I would call a bug, so it kind of stalled. (I vaguely recall other technical issues with infinite width? though not sure)
I'd argue this is less of a truncating/non-truncating plus issue and more of width inference rules, especially for literals without an explicit width. For example, I'd imagine it would be uncontroversial to write:
4.U(3.W) + 4.U(3.W)
and expect zero.
But less intuitive are these cases:
4.U(3.W) + 4.U
4.U + 4.U
even if you knew the width resolution rules for +
, you'd need to know the width assigned to the literal, and that 4.U doesn't mean a number, it's actually filling in a concrete width implicitly - which may be the underlying point of confusion.
Also worth considering would be:
val a = RegInit(4.U(3.W))
a := a + 4.U
though perhaps isn't confusing since you're updating a register with a defined width (there's no expected inference on a := a + 4.U
).
I don't know the right answer to this (and lots of people will probably have different opinion), but my thoughts:
(4+4).U === 4.U + 4.U
?
4.U + 4.U === 8.U
, I think infinite width is a good way of conceptually explaining it.Regarding "what should this do in Scala-land":
BigInt((1 << 30) + (1 << 30)) != BigInt(1 << 30) + BigInt(1 << 30)
Analogous issues exist, it's just that Scala's integral types are more spaced out than ours, so people are a lot less likely to run into them.
What about similar behavior in Verilog and VHDL (is there even literal width inference there at all? - if not, they completely sidestep this problem)? If we expect new users to mainly come from a hardware engineering background, preserving similar behavior might practically result in the least confusion.
I would say that their solution is ... not great.
The number of bits that make up an unsized number (which is a simple decimal number or a number without the size specification) shall be at least 32. -- IEEE 1364-2001
I agree that there are two separate facets here: inferred widths of literals and widths of intermediate expressions. For the inferred widths of literals, Verilog shows its bizarre affinity for 32-bit values. On the "widths of intermediate expressions" side, Verilog has a remarkably complex set of rules that sometimes Does What I Mean but sometimes produces real head-scratchers.
Perhaps the most reasonable approach is documentation and to stylistically encourage use of +&
and +%
over +
, especially in the bootcamp. Then when people encounter behavior they find surprising there is a pretty big hint in the code.
We discussed this at the dev meeting today. Highlights:
+
in favor of making people explicitly use +%
or +&
. Probably not practical, at all.@edwardcwang if you think this is the right solution, you've been volunteered to PR this :trollface: but feel free to delegate or reassign if you'd like.
If I may add my own two cents; this is something that has (very rarely) come up in my team, and we added this pattern to our code-review process. I.e., if someone writes + 1.U
, especially as part of compound statement (x < a + 1
), put cognitive attention towards it. Since this shows up in <0.1% of our code base, this isn't onerous (we often rely/prefer wraparound behavior of the default +
anyways, and in other cases we write helper functions to manage more complex wraparound semantics). I'd also like to point out that it doesn't really matter what the default behavior is, or even if you completely eliminate +
from the language, this cognitive overhead during code review will be ever-present.
It does feel like a sufficiently complex Linter could catch something here (e.g., for x < a + 1
, and x
and a
are the same width, you almost certainly have a problem!).
And yes, I think Chisel could benefit from a style-guide and a code-review guide that should be introduced fairly early in the curriculum.
Type of issue: bug report / other enhancement
Impact: unknown
Development Phase: request
Other information:
Food for thought.
Consider the following simple Chisel expression (which is actually present in the bootcamp):
4.U + 4.U
. Surprisingly, it evaluates to0
not8
, since4.U
is assigned a width of3
by Chisel (i.e.4.U
is identical to4.U(3.W)
), and the default+
operator is width-truncating.We actually need to use the expression
4.U +& 4.U
to express8.U
, which can be a bit counter-intuitive. It would be good if4.U + 4.U
could mean4.U(inf.W) + 4.U(inf.W) --> 8.U(inf.W)
rather than4.U(3.W) + 4.U(3.W)
, for which the+
operator is doing a spectacular job.(Do width-less UInts actually exist?)
Generated circuits for reference below: