Managing ap_fixed in PYNQ

I am relatively new to PYNQ, I am using it for my MSc thesis.
In particular, I developed a design in Vivado using the available FFT logicore.

The FFT is configured to have a single channel, 1024 FFT size, Pipelined architecture, 16 bit input width, fixed point data format, unscaled scaling, truncation rounding, non real time throttle.

I wanted to test my design in PYNQ, the overlay import ecc… works fine.
However I do not really know how to manage ap_fixed (the fft input is a fix16_15) in Python. My idea was to create a simple sinusoid using numpy, something like:

import numpy as np
x = np.linspace(0,2 * np.pi, 1024)
sinewave =np.sin(x)

And pass it to my fft to test it and check if the spectrum is coherent with the input signal.
Unfortunately I don’t really know how should I pass my sinusoid to my fft.
I should create a sinusoid with type ap_fixed<16,15>, but I didn’t understand how to do it properly.

I already read the following discussions: How to use ap_fixed data type to communicate with the ip made by the vivado hls? - #3 by dongzw and Using Fixed-Point Data in Python - #6 by pynqzen
But I am still a bit confused. Could anyone provide some examples or help me with the “conversion”?

1 Like

There is some good information in the other posts. Try check them again, including a response I’ll link below.
In your example, sinewave will be a list of floating point numbers. If you use fixed point in your FPGA design you need to convert from float to fixed.
If you have programmed in other languages you will have come across casting between types which is similar to what you need to do.

To go from float to fixed point is reasonably straight forward.
Fixed point numbers are like integers, but with a decimal point implied.

E.g.
0x0001 is integer 1
In fixed point, with 1 fractional bit the same value, 0x0001 is 0.5
With 2 fractional bits it is 0.25, and so on.
The number representation doesn’t change, but how the number is interpreted changes.

If you wanted to convert integers to fixed point you can just left shift the integer by the number of fractional bits.
E.g.
Integer 1 in fixed point with 1 fractional bit is 0x0010 (left shift 1 or multiply by 2^1)
With 2 fractional bits 1 is 0x0100 (left shift 2 or multiply by 2^2)

As integers are whole numbers, this would only allow you to generate whole fixed point numbers. i.e. All the fractional bits would be 0. This is why floats are usually used to generate the data initially, (as you want to do with your sinewave).

In this post, PeterOgden shows how to go from float to fixed:
How to use ap_fixed data type to communicate with the ip made by the vivado hls? - #5 by PeterOgden.

import numpy as np
fixed_ar = np.ndarray((1024,), 'i4')
float_ar = np.arange(-512, 512, dtype='f4')
fixed_ar[:] = float_ar * 256

Try test some numbers to make sure you understand what is happening.

This example uses 8 fractional bits. If you have for example 0.125 in floating point, multiply by 2^8 (256).
0.125 → 32.0 (float)
Converting to an integer gives 32, which is 0x20 in hex. If this is a fixed point with 8 fractional bits, it would be 0.125.

Converting from fixed to float is a little tricker, but suggested code has been posted by Peter in the link above.

Cathal

1 Like

So in my case if I need to convert the sinusoid from float64 to ap_fixed<16,15> type, is the following line reasonable?
my_casting_int = np.int16(np.round(data_in * 2**15))

I don’t think you need the round, and you won’t be handling negative numbers correctly if you use an int16 (instead of uint16).
With ap_fixed<16,1> you have 1 integer bit and 15 fractional bits. This allows you to represent -1 (0x8000) to +0.999969482421875 or (2**15-1)*1/2**15 (0x7fff) with a resolution of 1/2**15.

I think you want this for your conversion:
np.uint16(data_in * 2**15)

Test a few values and check it gives you what you want/expect.

Cathal

Anyway in the end I used this library: GitHub - francof2a/fxpmath: A python library for fractional fixed-point (base 2) arithmetic and binary manipulation with Numpy compatibility.

1 Like

@cathalmccabe do you mean ap_fixed<16,1> ?
because with ap_fixed<16,15> you have 16 bits in total; 1 signed bit, 14 integer bits and 1 fractional bit.

Yes, apologies, I mixed up the integer bit. (Edited the post above to correct it).
Just to be clear, it isn’t a signed bit. Signed/unsigned will determine how the integer bit is interpreted - just link c types.

For reference,
ap_[u]fixed<W,I,Q,O,N>

https://www.xilinx.com/html_docs/xilinx2020_2/vitis_doc/use_arbitrary_precision_data_type.html

2 Likes