Search code examples
floating-pointvhdlfixed-point

Multliplication of std_logic_vector with Floating Point


I have 32 bit std_logic_vector signal and want to multiply it by floating point .

e.g

     signal Input : std_logic_vector (31 downto 0 );
     signal number = 0.2 ;
     signal Output: std_logic_vector (31 downto 0 ); 
     Output <= 0.2 * Input ;

What can be the best solution to do this kind of multiplication ? I have read somewhere that floating point arithmetic operation is not synthesizable so better to use fixed point numbers . How to do it ?

It would be appreciated if anyone can tell me the proper idea to do that sort of operation .

Thanks


Solution

  • The "best" solution depends on the precision you need and resources you can afford (logic vs DSPs). Using floating point requires to convert input to floating point, loosing up to 8 bits in the process, do the multiplication and convert back. It cost a lot, too much if you ask me.

    Using fixed point is pretty straightforward. Multiply number by 2^N, round that number, use it to multiply input and divide the result by 2^N. Take N = 8 for example:

    number = 0.2
    number_fixed = round(0.2*2^8) = 51
    output = floor(51*input/2^8) -- If you add 2^N-1 before floor, you reduce your error
    

    That last comment is because floor(m + 0.5) = round(m), it is usually cheap to do so, espicially in Xilinx since their multiplier have also an embedded adder/accumulator.

    The problem with fixed point is the error. We multiplied by 51/256 instead of 0.2, yielding a difference of 0.00078125. If input was 1000000, then the error committed is 781. This can be fixed by using a bigger N, which cost more hardware. Also, 0.2 does not have a closed form in binary: 0.2 = 0.0011001100110011.... The same problem affects floating point by the way.

    A third solution would be to use a divider by 5. It would cost around 32 3-bits adder depending on the algorithm, which is really not that bad. It also gives you the full precision, i.e. an error only on the lsb. Xilinx has cores for division available.

    Update

    I am quite sorry if my fixed point explanations are unclear, I will freely admit I'm not sure about the best way to explain it.

    First, do not scale by 10, or any other non-power-of-2. While not wrong, you usually have to multiply/divide by your scale factor, which is trivial for powers of 2, but not for other numbers.

    Consider the theory first. Let's say you have real numbers a and b, you should see that:

    a * b = c
    a*2^N * b*2^M = d = c * 2^(N+M)
    

    So if you scale 2 numbers before a multiplication, it only changes the result by another scaling factor. That is the basis of fixed-point arithmetic, scale the operands and keep track of the scaling of the output. However, we operate only on integers, so we don't use a, b, c or d, but a trucated, rounded or approximated version of these values:

    round(a*2^N) * round(b*2^N) = e approx d approx round(c*2^(N+M))
    

    In subsequent calculations, you keep e. When you need to get a real number back, usually outside the FPGA when you interpret you data, you would divide e by 2^(N+M). Of course, you made an error by using fixed point (or floating point for that matter), it's equal to c - e/2^(N+M), using larger N and M will reduce the error.

    Back to your example, using a scaling of 2^8. Wherever you multiply by 0.2, you multiply by 51 (round(0.2*2^8)) instead. Your result will be scaled by 2^8, it's up to you if you want to keep it that way or scale to another scaling factor (like 2^0).