VHDL-like fixed-point in Verilog?

  • Thread starter Guenter Dannoritzer
  • Start date
On Jan 9, 3:23 am, Jonathan Bromley <jonathan.brom...@MYCOMPANY.com>
wrote:
On Tue, 8 Jan 2008 15:39:13 -0800 (PST),

Andy <jonesa...@comcast.net> wrote:
Is there no way to declare a new type that is another array of the
basic bit type in SV?

Yes, of course, but that new type would then be for all
practical purposes identical to a good old "reg [n-1:0]".
No distinction of integral types by their name.

As someone already pointed out, the only way to fix this
would be to create a struct of some kind - and ensure
that the scaling information, stored in an element of that
struct, was always used as a constant so that there would
be a reasonable chance of synthesis optimizing it away.

The only problem

[with the VHDL-200x fixed-point packages]

is that you can assign a ufixed(3 downto -4) to a
ufixed(4 downto -3), and it will take it with no errors, but the
results would not be arithmetically correct.

GRRRR, yes. The inability to overload or redefine
assignment in VHDL really comes back to bite us here.
For me, this almost completely undoes all the good work
of the fixed-point packages - it's so easy to break the
results in this way.

You almost always have to
use resize() to make sure the assignment is arithmetically correct
(with truncation/rounding, etc. if necessary).

Personally I see this as a significant weak spot in
the IEEE packages. When I did my own fixed-point
package, I "solved" this by creating a copy procedure
(actually two of them, one to copy to a variable and one
to copy to a signal). Procedures of course get to know
about their arguments' array attributes, and can do the
bit-tweaking automatically. So, for example, instead
of writing

target_variable := resize(a+b*c, target_variable'...);

one might write

Copy_V(target_variable, a+b*c);

where a,b,c are all fixed-point things of various
widths and scalings, and "target_variable" is
yet another. Rounding and truncation are defaulted in
the package (or use package generics in VHDL-2006) but
could also be supplied as optional arguments to Copy_V.
Copy_S (target_signal, ...) provided as a convenience.
Direct assignment to any variable or signal of floating-
point type is then outlawed (by convention) to avoid the
problem of accidental re-scaling.

Note, by the way, that I still support the IEEE standard
packages, which in all other respects are more complete
and better-debugged than mine were.
--
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.brom...@MYCOMPANY.comhttp://www.MYCOMPANY.com

The contents of this message may contain personal views which
are not the views of Doulos Ltd., unless specifically stated.
So, what you're saying is that SV operators do not have access to the
index range of an array operand, just it's length? OUCH!!!

Overloading assignment operators could be very powerful!

Since resize() has a version where you pass it the target argument
itself for interrogation, instead of passing left and right indices,
it works similarly to your copy_v/s procedure, but with only one
function instead of two. Plus resize() can be used to control widths
(and therefore losses) in intermediate results of one expression.

I wonder if it would be better to overload all the operators to return
an intermediate type (and have overloaded versions to take
intermediate operands), but you would always declare your storage as
ufixed/sfixed. The only way to convert the intermediate type to the
ufixed/sfixed types is with a cast (at least you had to think about
it), or the resize() function, even if it really does not "resize"
anything. Or would such a system run into ambiguity problems with
expressions like a+b+c [(a+b)+c or a+(b+c)]?

In the final analysis, I agree that a standard package with a few
minor warts, with support from tool vendors, is better than a
"perfect" package with no support.

Andy
 
On Tue, 8 Jan 2008 15:39:13 -0800 (PST),
Andy <jonesandy@comcast.net> wrote:

Is there no way to declare a new type that is another array of the
basic bit type in SV?
Yes, of course, but that new type would then be for all
practical purposes identical to a good old "reg [n-1:0]".
No distinction of integral types by their name.

As someone already pointed out, the only way to fix this
would be to create a struct of some kind - and ensure
that the scaling information, stored in an element of that
struct, was always used as a constant so that there would
be a reasonable chance of synthesis optimizing it away.

The only problem
[with the VHDL-200x fixed-point packages]
is that you can assign a ufixed(3 downto -4) to a
ufixed(4 downto -3), and it will take it with no errors, but the
results would not be arithmetically correct.
GRRRR, yes. The inability to overload or redefine
assignment in VHDL really comes back to bite us here.
For me, this almost completely undoes all the good work
of the fixed-point packages - it's so easy to break the
results in this way.

You almost always have to
use resize() to make sure the assignment is arithmetically correct
(with truncation/rounding, etc. if necessary).
Personally I see this as a significant weak spot in
the IEEE packages. When I did my own fixed-point
package, I "solved" this by creating a copy procedure
(actually two of them, one to copy to a variable and one
to copy to a signal). Procedures of course get to know
about their arguments' array attributes, and can do the
bit-tweaking automatically. So, for example, instead
of writing

target_variable := resize(a+b*c, target_variable'...);

one might write

Copy_V(target_variable, a+b*c);

where a,b,c are all fixed-point things of various
widths and scalings, and "target_variable" is
yet another. Rounding and truncation are defaulted in
the package (or use package generics in VHDL-2006) but
could also be supplied as optional arguments to Copy_V.
Copy_S (target_signal, ...) provided as a convenience.
Direct assignment to any variable or signal of floating-
point type is then outlawed (by convention) to avoid the
problem of accidental re-scaling.

Note, by the way, that I still support the IEEE standard
packages, which in all other respects are more complete
and better-debugged than mine were.
--
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 Jan 7, 1:26 pm, sh...@cadence.com wrote:
On Jan 7, 8:19 am, Guenter Dannoritzer <kratfkryk...@spammotel.com
wrote:



One thing that VHDL supports is operator overloading and this in turn
allows to create two fixed-point operators like:

SystemVerilog supports operator "overloading" also. It is not really
overloading, since you cannot overload an operator that already has a
definition for the types. You can only define an operator for types
where it would otherwise be illegal. So you would have to build your
fixed-point type as something more complex than a simple vector. For
example, you could use an unpacked struct as a wrapper for the value,
so that the arithmetic operators would not have pre-existing
definitions. But then you would need to define a different struct
type for each vector range, and a different function and operator for
each combination you wanted to handle.

Also, this is just a short-hand syntax for making a function call, so
it doesn't let you do anything that a function call would not also
allow.

You could probably do something fancier where you used a dynamic type
such as a dynamic array or string to hold the value, and tracked the
position of the binary point with an additional field in a struct or
class. However, a synthesizer is unlikely to handle that.



Now this could be bypassed with a function in Verilog. The only thing
that would complicate things is that it would be necessary to pass the
position of the binary point to the function.

reg [7:-6] a,b;
reg [8:-6] c;

c = fixp_add(a, 7, -6, b, 7, -6);

Now the fixp_add() function could do the required alignment and then do
the addition.

In VHDL this is simpler because it is possible to ask a data type for
its [msb:lsb] values by using the 'high and 'low attribute.

Is there any trick that can be done with the index values that would
simplify a fixed-point operation function in Verilog?

Nothing I can think of. SystemVerilog allows querying the left and
right bounds of a variable, but that still doesn't help. Inside a
function, querying the bounds of an argument would give you the bounds
of the formal argument, not the bounds of the actual argument that was
passed.

You could probably write a macro that invoked a function for you, and
extracted the msb and lsb values of the original variables and passed
those to the function as well.
VHDL uses different data types to do signed, unsigned, sfixed, and
ufixed arithmetic. They are all arrays of the basic bit type,
std_logic. The operators are overloaded appropriately in the packages
for those types.

Is there no way to declare a new type that is another array of the
basic bit type in SV?

Also the fixed point arithmetic is lossless (except for division):
results are always large enough to hold the exact result, with correct
binary point. You can decide how the package handles rounding/
truncation, saturation/rollover, and number of guard bits used when
necessary.

The only problem is that you can assign a ufixed(3 downto -4) to a
ufixed(4 downto -3), and it will take it with no errors, but the
results would not be arithmetically correct. You almost always have to
use resize() to make sure the assignment is arithmetically correct
(with truncation/rounding, etc. if necessary).

Andy
 
G

Guenter Dannoritzer

Guest
Hi,

I read about a fixed-point package in VHDL, that introduces data types
helping to keep track of the decimal point when doing fixed-point
arithmetic. An example for a declaration is:

signal a : sfixed (3 downto -6);

If I understand it right, it means 2+sign (3) bits are used for integer
representation and 6 bits are used for fractional representation.

I am aware that Verilog does not allow to specify new data types like
VHDL does, but I read in Thomas & Moorby's Verilog book that it is
possible to use negative index for reg or wire declarations.

So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?

I guess my missing understanding is how to use negative bit selection
values.

But how would it help me to keep track of the decimal point?

Thanks for any insight.

Cheers,

Guenter
 
On Jan 5, 10:03 am, Guenter Dannoritzer <kratfkryk...@spammotel.com>
wrote:
So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?
It will not do anything automatically. However, it might serve as a
mnemonic for the person writing the code, to help them keep track of
where the binary point is. You would presumably have the significance
of a[index] be 2**index, so the binary point would be between a[0] and
a[-1]. You could do the same thing by declaring [9:0] and keeping
track of the fact that the binary point is between a[6] and a[5], but
this could be harder to keep track of when you have different values
with different positions for the binary point. If you declare
everything with the binary point between indexes 0 and -1, then it
would be easier to keep track of.

You would still need to use that knowledge to perform the fixed-point
operations correctly yourself. For example, if you are adding two
numbers with different numbers of fractional bits (i.e. different
rightmost indexes), you would still need to use shifts or
concatenations to get the binary points to line up before adding. The
shift amount would be the difference between the rightmost indexes of
the two addends, followed by a shift of the sum to match the rightmost
index of the result variable. For a multiply, the rightmost index of
the result would be the sum of the two negative rightmost indexes of
the two factors, which would need to be adjusted with a shift to match
the rightmost index of the result variable.

Again, this will not make the language do anything special for you.
It would just be a notation to help you keep track of the position
yourself.
 
And to ampilfy on what Steve sharp@cadence.com writes:

On Jan 5, 10:03 am, Guenter Dannoritzer <kratfkryk...@spammotel.com
wrote:

So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?

It will not do anything automatically. However, it might serve as a
mnemonic for the person writing the code, to help them keep track of
where the binary point is. You would presumably have the significance
of a[index] be 2**index, so the binary point would be between a[0] and
a[-1]. You could do the same thing by declaring [9:0] and keeping
track of the fact that the binary point is between a[6] and a[5], but
this could be harder to keep track of when you have different values
with different positions for the binary point. If you declare
everything with the binary point between indexes 0 and -1, then it
would be easier to keep track of.

You would still need to use that knowledge to perform the fixed-point
operations correctly yourself. For example, if you are adding two
numbers with different numbers of fractional bits (i.e. different
rightmost indexes), you would still need to use shifts or
concatenations to get the binary points to line up before adding. The
shift amount would be the difference between the rightmost indexes of
the two addends, followed by a shift of the sum to match the rightmost
index of the result variable. For a multiply, the rightmost index of
the result would be the sum of the two negative rightmost indexes of
the two factors, which would need to be adjusted with a shift to match
the rightmost index of the result variable.
Worth emphasizing is that it will be a "binary" point and not a
"decimal" point. Thus, fractions like 1/10th will not get represented
exactly--I don't know if VHDLs fixed point numbers represent decimal
fractions exactly, but I know other languages that did.

If you want a truly scaled decimal number in Verilog, you will need to
do the bookkeeping yourself. For example, if you want monetary
values, better to represent them as integer numbers of pennies (or
mils if you want sub-penny accuracy) and then convert them into
dollars and cents for printing. The same thing would work for
measurements, for example keeping integral centimeters or milimeters
and then converting back into fractional meters only for display.

True decimal artithmetic is actually surprisingly subtle and hard to
get right, because there are corner-cases where we humans naturally
(and unconsciously) do something that isn't obvious until you've
thought about it.
 
Chris F Clark wrote:
And to ampilfy on what Steve sharp@cadence.com writes:

On Jan 5, 10:03 am, Guenter Dannoritzer <kratfkryk...@spammotel.com
wrote:
So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?
[...]

Worth emphasizing is that it will be a "binary" point and not a
"decimal" point. Thus, fractions like 1/10th will not get represented
exactly--I don't know if VHDLs fixed point numbers represent decimal
fractions exactly, but I know other languages that did.
Thanks for pointing that out. I need to watch my wording. I actually
meant binary point. It would already help to have something in Verilog
available, like the VHDL fixed-point package, that keeps track of the
binary point.
 
On Mon, 07 Jan 2008 13:41:25 +0100,
Guenter Dannoritzer wrote:

It would already help to have something in Verilog
available, like the VHDL fixed-point package, that keeps track of the
binary point.
You might like to look at http://www.fintronic.com/finmath.html

Those guys have a bunch of non-standard extensions to Verilog
that support fixed-point arithmetic in various interesting ways.

Please note, this is in no sense either an endorsement or
a criticism of Fintronics' products. I don't know enough
about them to be able to judge, and in any case my
employer is absolutely independent of any tool vendor.
--
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.
 
sharp@cadence.com wrote:
On Jan 5, 10:03 am, Guenter Dannoritzer <kratfkryk...@spammotel.com
wrote:
So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?

It will not do anything automatically. However, it might serve as a
mnemonic for the person writing the code, to help them keep track of
where the binary point is.
[...]

You would still need to use that knowledge to perform the fixed-point
operations correctly yourself. For example, if you are adding two
numbers with different numbers of fractional bits (i.e. different
rightmost indexes), you would still need to use shifts or
concatenations to get the binary points to line up before adding.
[...]

For a multiply, the rightmost index of
the result would be the sum of the two negative rightmost indexes of
the two factors, which would need to be adjusted with a shift to match
the rightmost index of the result variable.

Again, this will not make the language do anything special for you.
It would just be a notation to help you keep track of the position
yourself.
I have been doing some more reading on the fixed-point package for VHDL
and it seems like there are some language features of VHDL that help to
do some of the operations necessary for fixed point operation.

One thing that VHDL supports is operator overloading and this in turn
allows to create two fixed-point operators like:

signal a, b : sfixed (7 downto -6);
signal c : sfixed (8 downto -6);

and then do the operation

c = a + b;

Now this could be bypassed with a function in Verilog. The only thing
that would complicate things is that it would be necessary to pass the
position of the binary point to the function.

reg [7:-6] a,b;
reg [8:-6] c;

c = fixp_add(a, 7, -6, b, 7, -6);

Now the fixp_add() function could do the required alignment and then do
the addition.

In VHDL this is simpler because it is possible to ask a data type for
its [msb:lsb] values by using the 'high and 'low attribute.

Is there any trick that can be done with the index values that would
simplify a fixed-point operation function in Verilog?
 
Guenter Dannoritzer wrote:
sharp@cadence.com wrote:
On Jan 5, 10:03 am, Guenter Dannoritzer <kratfkryk...@spammotel.com
wrote:
So I could do something like this:

reg [3:-6] a;

Now I am wondering how this can be used to keep track of the decimal point?
It will not do anything automatically. However, it might serve as a
mnemonic for the person writing the code, to help them keep track of
where the binary point is.
[...]

You would still need to use that knowledge to perform the fixed-point
operations correctly yourself. For example, if you are adding two
numbers with different numbers of fractional bits (i.e. different
rightmost indexes), you would still need to use shifts or
concatenations to get the binary points to line up before adding.
[...]

For a multiply, the rightmost index of
the result would be the sum of the two negative rightmost indexes of
the two factors, which would need to be adjusted with a shift to match
the rightmost index of the result variable.

Again, this will not make the language do anything special for you.
It would just be a notation to help you keep track of the position
yourself.

I have been doing some more reading on the fixed-point package for VHDL
and it seems like there are some language features of VHDL that help to
do some of the operations necessary for fixed point operation.

One thing that VHDL supports is operator overloading and this in turn
allows to create two fixed-point operators like:

signal a, b : sfixed (7 downto -6);
signal c : sfixed (8 downto -6);

and then do the operation

c = a + b;

Now this could be bypassed with a function in Verilog. The only thing
that would complicate things is that it would be necessary to pass the
position of the binary point to the function.

reg [7:-6] a,b;
reg [8:-6] c;

c = fixp_add(a, 7, -6, b, 7, -6);

Now the fixp_add() function could do the required alignment and then do
the addition.

In VHDL this is simpler because it is possible to ask a data type for
its [msb:lsb] values by using the 'high and 'low attribute.

Is there any trick that can be done with the index values that would
simplify a fixed-point operation function in Verilog?

Guenter,
I recently wrote some code in Verilog which is similar to what you
describe. I have a fixed-point addition function to which I pass the
two inputs as well as the "types" of the inputs, which are structures
describing the number of bits and the location of the binary point. The
"structure" is really just a parameter with certain bit fields defined.
It's the same as passing multiple arguments but just a bit cleaner.

I also created the functions real2fix() and fix2real() which allow me to
drive buses with real values and to display the real representation of
a fixed-point number, respectively. If you are using fixed-point
numbers, it's imperative to be able to display them as such during
simulation either at the simulator's command line or in the wave window.
Thus, for example, the 4-bit signed number 0101 that has the binary
point to the left of bit 1 will be displayed as 1.25.

I was surprised and disappointed that some support for fixed-point
numbers wasn't built into SystemVerilog. If you are using Xilinx parts,
you can use SystemGenerator, which tracks binary points for you.
-Kevin
 
On Jan 7, 8:19 am, Guenter Dannoritzer <kratfkryk...@spammotel.com>
wrote:
One thing that VHDL supports is operator overloading and this in turn
allows to create two fixed-point operators like:
SystemVerilog supports operator "overloading" also. It is not really
overloading, since you cannot overload an operator that already has a
definition for the types. You can only define an operator for types
where it would otherwise be illegal. So you would have to build your
fixed-point type as something more complex than a simple vector. For
example, you could use an unpacked struct as a wrapper for the value,
so that the arithmetic operators would not have pre-existing
definitions. But then you would need to define a different struct
type for each vector range, and a different function and operator for
each combination you wanted to handle.

Also, this is just a short-hand syntax for making a function call, so
it doesn't let you do anything that a function call would not also
allow.

You could probably do something fancier where you used a dynamic type
such as a dynamic array or string to hold the value, and tracked the
position of the binary point with an additional field in a struct or
class. However, a synthesizer is unlikely to handle that.

Now this could be bypassed with a function in Verilog. The only thing
that would complicate things is that it would be necessary to pass the
position of the binary point to the function.

reg [7:-6] a,b;
reg [8:-6] c;

c = fixp_add(a, 7, -6, b, 7, -6);

Now the fixp_add() function could do the required alignment and then do
the addition.

In VHDL this is simpler because it is possible to ask a data type for
its [msb:lsb] values by using the 'high and 'low attribute.

Is there any trick that can be done with the index values that would
simplify a fixed-point operation function in Verilog?
Nothing I can think of. SystemVerilog allows querying the left and
right bounds of a variable, but that still doesn't help. Inside a
function, querying the bounds of an argument would give you the bounds
of the formal argument, not the bounds of the actual argument that was
passed.

You could probably write a macro that invoked a function for you, and
extracted the msb and lsb values of the original variables and passed
those to the function as well.
 

Welcome to EDABoard.com

Sponsor

Back
Top