MEP | 106 |
Author | Jan Decaluwe |
Status | Final |
Created | 25-May-2011 |
Rationale
Recently, there has been a lot of activity regarding "wrap-around" types. I have looked at the proposals. My own thoughts on the matter have evolved also. Here is my analysis and proposal.
First some background. One of my main critiques on the mainstream use of Verilog/VHDL is the focus on "representational" types for integer arithmetic. In other words, the starting point is a bit vector representation to which an integer interpretation is added later. The hidden assumption is that this is necessary for implementation efficiency.
I believe that MyHDL has convincingly demonstrated that there is a better way by taking a top-down perspective. In MyHDL, integer arithmetic simply works as expected. The convertor takes care of representational details as required by the Verilog/VHDL target.
This is not just a semantic discussion. In MyHDL, you don't need castings, manual sign extensions or resizings to get integer arithmetic to work. In Verilog/VHDL, that is the name of the game. Best of all, there is no implementation efficiency price to pay.
Understandably, I would like to use a similar approach when considering to introduce new types: first look at the modelling aspect, that is, how a designer would prefer to use a type, and only then move to conversion and efficiency concerns.
Wrap-around type proposals
There is no question that there is a genuine need for the elegant modeling of wrap-around behavior in hardware modeling. The question is whether this is important enough to warrant dedicated language support.
In the past, I have suggested that this need may not be that high. For example, in the common case of an incrementer or decrementor, it is often necessary to decode the end count anyway:
if count == N-1: count.next = 0 some_decode_action() else: count.next = count + 1
However, I would agree that even then it could be clearer to keep the incrementer description separate from the decoding:
if count == N-1: some_decode action() count.next = (count + 1) % N
I have also argued that the modulo operator offers an explicit and elegant
high-level solution to describe wrap-around behavior in a one-liner. However,
it has been pointed out that this doesn't work for the case of a "signed"
integer. A wrap()
method was proposed as a solution.
Both the modulo operator and a wrap()
method have some disadvantages. Though
they are explicit in good Python style, there is a conflict with another good
principle: DRY (Don't Repeat Yourself). If wrap-around is the desired behavior,
it is probably a property of an object and its type, not of a particular
assignment.
The two techniques have another problem that I have successfully managed to keep out of spotlights in earlier discussions :-). I believe a common use case for wrap-around behavior would be to describe an incrementor/decrementer using augmented asssignment with a variable:
count += 1
The two techniques discussed above cannot support this.
Another proposal is a wrap
parameter to the intbv constructor, with default
False. When True, it would modify the assignment behavior as desired.
This technique addresses the issues mentioned above, but has some problems itself. An additional parameter makes object construction heavier. Also, the use of a default value suggests that one type of behavior is the rule, and the other the exception. I don't like the idea of modifying the fundamental behavior of a type in such a way. If wrap-around behavior is considered important enough to warrant direct language support, it should be as easy to use as other behaviors.
Finally, a parameter cannot support the following conversion trick:
a = intbv(0)[N:]
to construct an "N bits wide" positive intbv, even though such "closer to hardware" objects are probably popular candidates for wrap-around behavior.
The most important problem that I have with the discussed proposals is still elsewhere: they go against the MyHDL-specific philosophy that I described earlier. The starting point of the proposals is that wrap-around behavior will only supported on "full range" bit-vectors. This is likely influenced by implicit assumptions of efficient hardware implementation.
In my modeling work, I use wrap-around counters all the time. However, more
often than not, their bounds are not powers of 2. I suspect this is the same
for many designers. As I have argued earlier, we should look at modeling first,
and at hardware efficiency later. In previous cases, such as the intbv
type,
we have been able to achieve much easier modeling without having to pay a price
in efficiency.
Ada modular types
Recently I got introduced to Ada modular types. These are natural integer types with wrap-around behaviour.
The wrap-around behavior of modular types is based on the sound mathematical concept of modulo arithmetic. Therefore, the modulus is not limited to powers of 2.
The rationale behind modular types is interesting. For example:
"The modulus of a modular type need not be a power of two although it often will be. It might, however, be convenient to use some obscure prime number as the modulus perhaps in the implementation of hash tables." [Ref]
Another reference says:
"Modular types don't need to be powers of two, but if they are the Ada compiler will select especially efficient operations to implement them." [Ref]
This is exactly the kind of reasoning I'm looking for. Ada considers module arithmetic important enough to warrant direct language support, but in the general, mathematically sound sense. On the other hand, it recognizes that powers of 2 bounds are very important. There is actually a predefined package with power of 2 based subtypes. Moreover, the compiler can select "especially efficient operations" to implement them.
Interestingly, Ada even defines and/or/xor bit operations on modular types,
unlike other integer types. Apparently it chooses modular types for this
purpose because their "unsigned" interpretation poses less problems than bit
operations on "signed" representations. In any case, the dual nature of the
type - both high-level and representation-friendly - is similar to what MyHDL
does with the intbv
. When reading all this, I can only conclude that VHDL
lost a big opportunity by forgetting about its origins.
I believe the Ada example is the route to follow. Two issues still have to be addressed:
- Ada modular types are restricted to natural values. We want to support integer values in general.
- Ada modular types are types, which means that conversions would be needed to let them work with other integer-like types. (Disclaimer: I am not sure that this statement is true.)
A proposal for MyHDL: the modbv type
Ada's modular type is called mod
. By analogy, I am proposing a modbv
type
in MyHDL. Behaviorally, the only difference with intbv
would be how bounds
are handled: out-of-bound values result in an error with intbv
, and in
wrap-around with modbv
. In particular, modbv
would have exactly the same
interface as the intbv. The wap-around behavior would be defined as follows,
with val
denoting the current value and min
/max
the bounds:
val = (val - min) % (max - min) + min
Unlike Ada, intbv
and modbv
would be subtypes of a general
integer-bitvector type. Therefore, they can be mixed transparantly in
expressions. This should be not problem as the bound handling only occurs at
assignment time in the assigned object.
This proposal addresses the issues mentioned earlier. In particular:
count += 1
and
count = modbv(0)[32:]
would have wrap-around behavior.
Implementation notes
Technically, the cleanest implementation would probably be to make both intbv
and modbv
a subtype of some general integer-bitvector class. In a first
phase, it would probably be OK to simply subclass intbv.
To make this kind of subclassing possible, the intbv class has to be made more
robust. For example, at some points were it returns a literal intbv
object,
it should use the actual subtype itself.
The basic difference with intbv
is bound handling. Currently, this is already
done in a separate method called intbv._checkBounds()
. This method should
probably be renamed to intbv._handleBounds()
, and then overwritten with the
proper behavior in the subclass.
Conversion notes
It is expected that conversion would work directly for the case of "full range"
modbv
objects. (Note that it is not sufficient that both bounds are powers of
2.) In a first phase, the convertor could impose that restriction.
To demonstrate the advantage of the chosen approach, we could attempt to lift some restrictions for interesting use cases. For example, a common use case for wrap-around behavior would be incrementors and decrementors. The analyzer could detect such cases and mark them. For such patterns, we could then support general wrap-around for non full-range objects also. For example:
count.next = count + 1
would be converted to the Verilog/VHDL equivalent of:
if count == MAX-1: count.next = MIN else: count.next = count + 1
It is probably possible to remove more restrictions in a gradual process. When doing so, we would be adding items to the list of useful features that MyHDL can fully support, even though they have no native support in Verilog/VHDL. At some point, this list of features, unique to MyHDL, could become very attractive to Verilog/VHDL designers.
Status
A draft implementation has been developed in the 0.8-dev branch in the mercurial repo.
Acknowledgements
Chris Felton has kept this feature on the map. Thanks to Chris Felton, Tom Dillon and BenoƮt Allard for proposals, tests, and discussions