numeric_std ADD missing one bit in the answer?!

P

Patrick Dubois

Guest
Hello,

I just realized something quite strange with the ADD operator of
numeric_std. Say I try to add 2 numbers:

signal A,B unsigned:(8 downto 0);
signal C unsigned(9 downto 0);

C <= A + B;

This does not work because apprently, the ADD operator returns a bit
width that is the largest of A or B, missing one bit from the
"normally expected" behavior. The simulator returns a "incompatible
range" error. Now, I tried this at first:

C <= resize(A+B, C'length);

It simulates but doesn't give the right answer because A+B is only 9
bits (according to the ADD operator) so resizing it to 10 bits after
the fact just adds one 0. The correct way of doing this is therefore:

C <= (resize(A, C'length) + resize(B, C'length));

Doesn't this seem a little crazy? Why doesn't ADD return one more bit
in the first place? This is so tricky that even Ray Andraka fell into
the trap:
http://tinyurl.com/3akf3o


Patrick
 
On Feb 3, 9:54 am, Patrick Dubois <prdub...@gmail.com> wrote:
Hello,

I just realized something quite strange with the ADD operator of
numeric_std. Say I try to add 2 numbers:

signal A,B unsigned:(8 downto 0);
signal C unsigned(9 downto 0);

C <= A + B;

This does not work because apprently, the ADD operator returns a bit
width that is the largest of A or B, missing one bit from the
"normally expected" behavior. The simulator returns a "incompatible
range" error. Now, I tried this at first:

C <= resize(A+B, C'length);

It simulates but doesn't give the right answer because A+B is only 9
bits (according to the ADD operator) so resizing it to 10 bits after
the fact just adds one 0. The correct way of doing this is therefore:

C <= (resize(A, C'length) + resize(B, C'length));

Doesn't this seem a little crazy? Why doesn't ADD return one more bit
in the first place? This is so tricky that even Ray Andraka fell into
the trap:http://tinyurl.com/3akf3o

Patrick
It's not uncommon to ignore the carry out bit. For details of how
ieee.numeric_std extends things see:
http://www.synthworks.com/papers/vhdl_math_tricks_mapld_2003.pdf

Chris
 
On Sun, 3 Feb 2008 06:54:51 -0800 (PST), Patrick Dubois <prdubois@gmail.com>
wrote:

Hello,

I just realized something quite strange with the ADD operator of
numeric_std. Say I try to add 2 numbers:

signal A,B unsigned:(8 downto 0);
signal C unsigned(9 downto 0);

C <= A + B;

This does not work because apprently, the ADD operator returns a bit
width that is the largest of A or B, missing one bit from the
"normally expected" behavior. The simulator returns a "incompatible
range" error. Now, I tried this at first:

C <= resize(A+B, C'length);

It simulates but doesn't give the right answer because A+B is only 9
bits (according to the ADD operator) so resizing it to 10 bits after
the fact just adds one 0. The correct way of doing this is therefore:

C <= (resize(A, C'length) + resize(B, C'length));
Or

C <= ('0' & A) + ('0 & B);

possibly wrapped in a function.

There may be times where it is worth defining new types for A, B, C and
overloading "+" for those types to get the mathematical properties you need.

Or defining A,B as wider and re-sizing data on the way into them.

Doesn't this seem a little crazy? Why doesn't ADD return one more bit
in the first place? This is so tricky that even Ray Andraka fell into
the trap:
Any way you do it has untidiness somewhere; IMO basic mathematical operators
that return different subtypes from their inputs by default would be at least
equally "crazy".

IMO the best approach is the one which keeps the type conversion etc "noise" to
a minimum (or at least, highly localised, e.g. in a package, where it can be
referenced as necessary and otherwise ignored) while still alowing the design to
be accurately specified.

I certainly don't want implicit type conversions and re-sizings and all the
accidental bugs and misunderstandings that arise from them; I am truly glad that
these important aspects of the design can be checked for consistency by the
compiler or at latest, at the elaboration stage

- Brian
 
Patrick Dubois wrote:

Doesn't this seem a little crazy? Why doesn't ADD return one more bit
in the first place?
Sometimes I need a carry:
subtype vec_small_t is unsigned(small_c downto 0); -- msb is carry

Sometimes I don't:
subtype vec_big_t is unsigned(q_c'range); -- no carry needed

For details see:
http://home.comcast.net/~mike_treseler/count_enable.vhd


-- Mike Treseler
 
On 3 fév, 10:50, Brian Drummond <brian_drumm...@btconnect.com> wrote:

There may be times where it is worth defining new types for A, B, C and
overloading "+" for those types to get the mathematical properties you need.

Or defining A,B as wider and re-sizing data on the way into them.

Doesn't this seem a little crazy? Why doesn't ADD return one more bit
in the first place? This is so tricky that even Ray Andraka fell into
the trap:

Any way you do it has untidiness somewhere; IMO basic mathematical operators
that return different subtypes from their inputs by default would be at least
equally "crazy".

IMO the best approach is the one which keeps the type conversion etc "noise" to
a minimum (or at least, highly localised, e.g. in a package, where it can be
referenced as necessary and otherwise ignored) while still alowing the design to
be accurately specified.

I certainly don't want implicit type conversions and re-sizings and all the
accidental bugs and misunderstandings that arise from them; I am truly glad that
these important aspects of the design can be checked for consistency by the
compiler or at latest, at the elaboration stage

- Brian
Thanks for your input. I can see a little bit better why ADD works
that way. I still think that it should generate the carry bit by
default (like the Matlab fixed-point toolbox does) but I understand
both points of view.

Let me just explain my point of view on this. If the carry bit was
automatically generated, then the answer would be correct by design.
If you don't include this bit, there is a possibility of an overflow.
If you _know_ that this overflow can't happen (or that you don't care)
then it's because you have extra information that the ADD function
doesn't have about A and B. If ADD generated this extra bit, you would
be free to trim it of course, but you would be doing so explicitly.
IMHO this would be safer than the current implementation. Ignoring
carry bits is dangerous.

Basically, I think that ADD should generate the best answer from the
knowledge it has, and that would mean generate one more bit than the
largest operator.

One last thing, you say:
Any way you do it has untidiness somewhere; IMO basic mathematical operators
that return different subtypes from their inputs by default would be at least
equally "crazy".
Then, do you consider the multiply operator crazy because its answer
length is A'length+ B'length? The growth issue is obviously considered
there. Why not for the ADD operator? This doesn't seem consistent to
me.

Patrick
 
Patrick Dubois wrote:

I can see a little bit better why ADD works
that way. I still think that it should generate the carry bit by
default (like the Matlab fixed-point toolbox does) but I understand
both points of view.
Interesting subject.
numeric_std is the way it is.
The fact that it is always the same
is more important to me than the
design details of say, a rollover counter
vs a phase accumulator. When I get annoyed,
I just write my own function.

If I am working out pure math for synthesis,
I start with a python prototype. Some use Matlab.
Both vhdl and verilog are lacking in this area.

Let me just explain my point of view on this. If the carry bit was
automatically generated, then the answer would be correct by design.
Not for a 2**n rollover counter.

If you don't include this bit, there is a possibility of an overflow.
If you _know_ that this overflow can't happen (or that you don't care)
then it's because you have extra information that the ADD function
doesn't have about A and B. If ADD generated this extra bit, you would
be free to trim it of course, but you would be doing so explicitly.
IMHO this would be safer than the current implementation. Ignoring
carry bits is dangerous.
Doing math without synthesis support is dangerous however I do it.
One man's carry bit is another man's MSB.

Then, do you consider the multiply operator crazy because its answer
length is A'length+ B'length? The growth issue is obviously considered
there. Why not for the ADD operator? This doesn't seem consistent to
me.
There are two interpretations of a sum.
A multiply has only one.

-- Mike Treseler
 
On 3 fév, 13:02, Mike Treseler <mike_trese...@comcast.net> wrote:
Interesting subject.
numeric_std is the way it is.
The fact that it is always the same
is more important to me than the
design details of say, a rollover counter
vs a phase accumulator. When I get annoyed,
I just write my own function.
I agree, I'm not suggesting to change numeric_std. I was just
surprised to see that behavior and wanted to see what others thought
about it.

If I am working out pure math for synthesis,
I start with a python prototype. Some use Matlab.
Both vhdl and verilog are lacking in this area.
Yes, usually I use a Matlab model but in that particular case I was
just adding two fifo counters... I'm surprised that I had not noticed
that particular behavior of the + operator before (or maybe I just
forgot about it).

Any way you do it has untidiness somewhere; IMO basic mathematical operators
that return different subtypes from their inputs by default would be at least
equally "crazy".

Then, do you consider the multiply operator crazy because its answer
length is A'length+ B'length? The growth issue is obviously considered
there. Why not for the ADD operator? This doesn't seem consistent to
me.

There are two interpretations of a sum.
A multiply has only one.
Apparently my interpretation of a sum differed from the creators of
numeric_std then. Alright, in any case what matters is to understand
what the operator does and act accordingly.

Thanks for everyone's comments.

Patrick
 
Patrick
Thanks for your input. I can see a little bit better why ADD works
that way. I still think that it should generate the carry bit by
default (like the Matlab fixed-point toolbox does) but I understand
both points of view.
The new fixed point packages gives full precision results. For details
see the Fixed and Floating point papers at:

http://www.synthworks.com/papers/

Cheers,
Jim
 
On Sun, 3 Feb 2008 08:23:44 -0800 (PST), Patrick Dubois <prdubois@gmail.com>
wrote:

On 3 fév, 10:50, Brian Drummond <brian_drumm...@btconnect.com> wrote:


Thanks for your input. I can see a little bit better why ADD works
that way. I still think that it should generate the carry bit by
default (like the Matlab fixed-point toolbox does) but I understand
both points of view.

Let me just explain my point of view on this. If the carry bit was
automatically generated, then the answer would be correct by design.
Which would be nice, in a way, but lead to sometimes surprising word growth.
Where I need to avoid overflow I do see word growth, but with minimum surprise
because I put it there... (sometimes it needs some explanation for future
maintenance)


One last thing, you say:
Any way you do it has untidiness somewhere; IMO basic mathematical operators
that return different subtypes from their inputs by default would be at least
equally "crazy".

Then, do you consider the multiply operator crazy because its answer
length is A'length+ B'length?
Well, ... yes, but given the alternative, I can forgive that! (The
double-sign-bit trap in signed multiplication is sometimes harder to deal with
though)

- Brian
 
On Feb 4, 8:21 pm, Jim Lewis <j...@synthworks.com> wrote:
Patrick> Thanks for your input. I can see a little bit better why ADD works
that way. I still think that it should generate the carry bit by
default (like the Matlab fixed-point toolbox does) but I understand
both points of view.

The new fixed point packages gives full precision results. For details
see the Fixed and Floating point papers at:

http://www.synthworks.com/papers/

Cheers,
Jim
I agree, if you want std_logic (not std_logic_vector!) based,
arithmetically accurate operators, use the fixed point package with no
fractional bits.

Otherwise, and if you don't need more than 31 bits unsigned, or 32
bits signed, use integer subtypes for arithmetic. Simulates and
synthesizes arithmetically accurately, and simulates MUCH faster
anyway.

With integers or fixed point, A + 1 > A is always true, but the result
may not fit into the same storage as A. Fixed point and integers
require different means of making it fit (resize vs mod), but are
really quite similar in use, except for simulation performance. I
wouldn't mind seeing fixed point eventually supersede/replace
numeric_std, but momentum probably won't let that happen.

Andy
 
On Sun, 3 Feb 2008 06:54:51 -0800 (PST),
Patrick Dubois <prdubois@gmail.com> wrote:

C <= resize(A+B, C'length);

It simulates but doesn't give the right answer because A+B is only 9
bits (according to the ADD operator) so resizing it to 10 bits after
the fact just adds one 0. The correct way of doing this is therefore:

C <= (resize(A, C'length) + resize(B, C'length));
Yes. But since "+" performs its addition at the larger
bit width of its two operands, you need only do the
resize explicitly on one of them - the other will be
implicitly resized to match:

C <= resize(A, C'length) + B;

I find this relatively inoffensive, and fairly clear.

Verilog tries very hard to make this easy for you,
by propagating the width of the TARGET variable into
the how-many-bits-do-I-use calculation. Ask any
seasoned Verilog expert about whether this really
makes life easier, or simply confuses the issue.
If that doesn't give you enough entertainment, ask
him to explain to you the difference between a
context-determined and a self-determined expression,
and what the rules are for signed and unsigned
arithmetic. After you have wiped all the dribble
off the floor and the nice men in white coats have
come to take the poor chap away, you can calmly relax
back into the simpler, saner, but occasionally less
convenient world of VHDL.

You can, of course, always write your own package
for overflow-preserving arithmetic. If you take
care to base it on numeric_std, it will synthesise.
Then you can create a new data type that supports the
style of arithmetic you seek. For example:

type arith_unsigned is
array(natural range <>) of std_logic;

function "+"(L: arith_unsigned; R: arith_unsigned)
return arith_unsigned is
constant width: natural := max(L'length, R'length) + 1;
begin
return arith_unsigned(
resize(unsigned(L), width) + unsigned(R)
);
end;

The definition of function max() is left as an exercise :)
and of course you'll need many more other functions,
for example

arith_unsigned + unsigned
arith_unsigned + natural

and all the other arithmetic and comparison operators.
It's tedious, but fairly straightforward.
--
Jonathan Bromley, Consultant

DOULOS - Developing Design Know-how
VHDL * Verilog * SystemC * e * Perl * Tcl/Tk * Project Services

Doulos Ltd., 22 Market Place, Ringwood, BH24 1AW, UK
jonathan.bromley@MYCOMPANY.com
http://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
 
On Feb 11, 2:32 pm, Jonathan Bromley <jonathan.brom...@MYCOMPANY.com>
wrote:

Verilog tries very hard to make this easy for you,
by propagating the width of the TARGET variable into
the how-many-bits-do-I-use calculation. Ask any
seasoned Verilog expert about whether this really
makes life easier, or simply confuses the issue.
If that doesn't give you enough entertainment, ask
him to explain to you the difference between a
context-determined and a self-determined expression,
and what the rules are for signed and unsigned
arithmetic. After you have wiped all the dribble
off the floor and the nice men in white coats have
come to take the poor chap away, you can calmly relax
back into the simpler, saner, but occasionally less
convenient world of VHDL.
Thanks for the good laugh. I don't know Verilog but take your word for
it.

You can, of course, always write your own package
for overflow-preserving arithmetic.
I could, but I think we agree that it would be quite a bad idea to
create a package just to avoid adding one bit to the largest operand
(as you pointed out). Not to mention that another person reading my
code would have to read through and understand most of the package
before understanding what my code does.

I'll happily live with the way numeric_std does the add operation now
that I know the rules. Apparently I'm alone in the camp that thinks
it's a little strange that the carry is not preserved by default but
that's okay :)

Thanks for your comments.

Patrick
 

Welcome to EDABoard.com

Sponsor

Back
Top