cnl icon indicating copy to clipboard operation
cnl copied to clipboard

how to change precision to match shift operator?

Open vanossj opened this issue 6 years ago • 5 comments

Seems like the way to change precision is:

auto x = fixed_point<int, -4>{1.374};
auto y = fixed_point<int, -2>{x};

But I came across this case

auto x = from_rep<fixed_point<int, -14>>(-4899488);
auto y = fixed_point<int, 0>{x}; // rep value -299

My use case is a c model of FPGA design.

When the VHDL designers want to change from Q.14 to Q.0, they are going to take bits 31:14 and drop the last 14 bits. This matches nicely to -4899488>>14. Which results in -300.

cnl does this precision change with -4899488/(1<<14), which is -299. Granted, its more accurate, but I'm trying to match the FPGA and those guys are never going to use a divide when a shift works (almost) as good.

Is there a way in cnl to change precision to match shift behaviour instead of multiply/divide behaviour?

hopefully something nicer than

auto x = from_rep<fixed_point<int, -14>>(-4899488);
auto y = _impl::from_rep< fixed_point< int, 0 > >( _impl::to_rep( x ) >> 14);

Thanks for taking the time to answer all my questions

vanossj avatar Apr 05 '19 17:04 vanossj

Indeed, CNL is excellent tool for bit exact implementation reference for IC / FPGA design.

Thinking again I would have expected that type conversion example to be arithmetic shift as well. I'm a bit surprised to find that it is division. This makes a difference if the operand is negative.

This works however for getting the integer part correctly in your example:

auto z = cnl::floor(x);
std::cout << cnl::_impl::to_rep(z) << std::endl;

I think we had discussion about shift operations earlier. I'll scan the past CNL issues on this. One result of those discussions was birth of cnl::floor.

hbe72 avatar Apr 05 '19 18:04 hbe72

If you are using CNL for VHDL reference I suggest to consider using cnl::elastic_number or the type I suggested in other thread i.e. rounding_elastic_number. You can find that type from cnl/src/test/fixed_point/elastic/rounding. With these you can specify the word length exactly to a bit. With fixed_point type you are limited to native type widths. Some other pitfalls are avoided as well. However fixed_point gives you faster code if you wish to run proper algorithm simulations to check performance.

hbe72 avatar Apr 05 '19 18:04 hbe72

Yup, this was a thorny decision!

Division is chosen instead of right-shift because of correctness. The user will expect truncation when converting to a lower-precision number. For https://godbolt.org/z/V1tnLh

int f(cnl::fixed_point<int, -2> f) {
    return int(f);
}

f(.25) should return 0 and not 1. I think you'll find that right right-shift, you'll always tend towards -1 and not 0.

As @hbe72 points out this is only different when the value is negative. So if you use an unsigned type or otherwise tell the compiler that it's not a negative value then right-shift and division are equivalent.

I have not yet looked into modelling the sort of hardware you describe but I expect it can be done by:

  • adding a new rounding tag, fastest_rounding_tag
  • using the tag in fixed_point types, e.g. fixed_point<rounding_integer<int, -4>, fastest_rounding_tag>
  • specializing cnl::scale for the rounding_integer<T, fastest_rounding_tag>.

fastest_rounding_tag would in all other ways be the same as native_rounding_tag and compile away to native integer code.

Note that this tag would be not generally advisable. For example, when fixed_point is instantiated with a Radix other than two, behavior would change!

johnmcfarlane avatar Apr 05 '19 23:04 johnmcfarlane

This is very tricky, but I agree with @vanossj that VHDL design (just dropping the LSB bits) is needed. There are so many other errors in DSP systems that scaling by truncation, division or rounding gets lost into other system noise. But it is very specific to the algorithm and must be judged for each case separately.

Btw. I suspect that the rounding_elastic_number rounds towards -inf and inf. Not as typical DSPs and VHDL designers do it, round half up. https://en.wikipedia.org/wiki/Rounding#Round_half_up. In DSP and ASICs typically rounding is done by adding "half of the LSB value of the target" to the argument and then truncating from correct position. I need to dig in deeper on this.

hbe72 avatar Apr 06 '19 10:04 hbe72

Here are some different modes than can be implemented: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0105r1.html#RoundMode I doubt our desired mode is any of these because it's an exception that's applied to scaling only, i.e. conversion from floats and to/from other fixed_point exponents.

(This issue should be closed when the scale specialization mentioned above is implemented.)

On Sat, 6 Apr 2019 at 11:28, Heikki Berg [email protected] wrote:

This is very tricky, but I agree with @vanossj https://github.com/vanossj that VHDL design (just dropping the LSB bits) is needed. There are so many other errors in DSP systems that scaling by truncation, division or rounding gets lost into other system noise. But it is very specific to the algorithm and must be judged for each case separately.

Btw. I suspect that the rounding_elastic_number rounds towards -inf and inf. Not as typical DSPs and VHDL designers do it, round half up. https://en.wikipedia.org/wiki/Rounding#Round_half_up. In DSP and ASICs typically rounding is done by adding "half of the LSB value of the target" to the argument and then truncating from correct position. I need to dig in deeper on this.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/johnmcfarlane/cnl/issues/405#issuecomment-480493216, or mute the thread https://github.com/notifications/unsubscribe-auth/AAszNz7ckX_U67a5sCW94MB53wboepb_ks5veHbZgaJpZM4cfYKe .

johnmcfarlane avatar Apr 06 '19 12:04 johnmcfarlane