I need to convert a float64 value into a fixed point <16,15> (16 bit with 15 bit in the fractional part and 1 in the integer part).
I have already read many solutions:
However I have not really understood the "type" I need in my specific case.
To explain this better, I have implemented a code that generates a simple sine wave inside PYNQ (the Xilinx framework based on Python):
import numpy as np
###
fs = 44100
n_samples = 1024
f_sig = 130 #selected to get almost exactly a single frequency bin (43.066 * 3)
A = 1
seconds = 1
t = np.arange(0,seconds, step=1/fs)
#resolution of fft = fs/n_samples -> 44100/1024 = 43.066
data_in = A * np.sin(2*np.pi*f_sig*t)
data_in = data_in[:1024]
###
Which generates a simple sine wave and then I select the first 1024 samples. The goal of this selection is to send those samples to a FFT logicore block (FFT logicore documentation). In my specific case, the FFT has been configured to accept 16 bit fixed point inputs <16,15> for real and imaginary part (you can find more info in the documentation at page 18) and perform a 1024 points FFT. So basically I need to convert this sinusoid from float64
to ap_fixed<16,15>
However, I am not sure of which Python datatype I should use in my conversion. For example, in the second approach, it uses a uint16
to store the data after conversion. In the third approach the library returns an object.
When calling the function to copy the input to the DMA buffer and compute the FFT I have the following code:
from pynq imort Overlay
from pynq import allocate
dma = overlay.axi_dma_0
buff_in = allocate(1024, 'uknown type') # which type should I use here?
buff_in[:] = data_in[:]
np.copyto(buff_in, data_in, casting="unsafe")
dma.sendchannel.transfer(buff_in)
I understood the basic algorithm which should be something like:
import numpy as np
my_casting_int = int32(np.round(data_in * 2**15)))
my_casting_uint = uint32(np.round(data_in * 2**15)))
But again, I don't know if I need a signed or unsigned casting, considering that the input data is a float64
sinusoid with sign.
To sum everything up:
ap_fixed<16,15>
for each input sample, one for the real part and one for the imaginary part. Being a real input sample, I will have 16 bits with the real part and 16 bits which are basically all zeros for the imaginary part.float64
into a suitable format for my FFT core (which is ap_fixed<16,15>
).float64
.I suppose that FFT expects ap_fixed<16,15>, where MSB is the sign bit. In your example you have signed samples (because sinusoidal between -1.0 and 1.0), so your casting must be int
(signed int). But if you need a two-complement representation of signed int, it's right if you cast with uint
. In both cases, cast with 16 bits is enough.
np.round(data_in*2**15).astype(np.int16) # returns -16384
np.round(data_in*2**15).astype(np.uint16) # returns 49152
If you use float64
and need to translate to ap_fixed<16,15> (fxp-s16/15
or S1.15
type) take care about rounding and truncation methods.
If you use fxpmath, it's more clear what conversion you are doing. It's true that an object is returned, but you can extract the int
(raw) value by raw
method, or uint
by uraw
method:
from fxpmath import Fxp
#...
data_in_fxp = Fxp(data_in, dtype='fxp-s16/15') # or dtype='S1.15'
raw_data_int = data_in_fxp.raw()
raw_data_uint = data_in_fxp.uraw() # two-complement
A numpy array is returned by those method. If you need a python list
of int
, just use tolist
method, for example: data_in_fxp.raw().tolist()
Rounding and overflow behaviors could be modified, for more info visit fxpmath#behaviors