francof2a / fxpmath

A python library for fractional fixed-point (base 2) arithmetic and binary manipulation with Numpy compatibility.
MIT License
183 stars 21 forks source link

Declaration of negative number with different format is bugged #81

Closed davide-giacomini closed 9 months ago

davide-giacomini commented 1 year ago

Hello,

I am sorry for the title, it's not straightforward. I think saw an issue in the declaration of negative fixed points. I am using Python for interfacing the PS with the PL of the FPGA Pynq Z2, and i am dealing with arrays of fixed point numbers. In particular, I am converting my fixed points to unsigned long (64 bits) maintaining the bits equal, and I would like to re-cast the array of uint64 once it comes back from the FPGA.

In C++ I can easily do that by:

ap_ufixed<W_Q, I_Q> result_fixed = *((ap_ufixed<W_Q, I_Q>*) &result_uint);

In Python there is not this convenience for custom types (like this library) and I thought I could easily take the raw elements and recast them throuhg the declaration. Although, this form that I am going to show below doesn't work:

W=64
I=32
d_fixed_t = Fxp(None, signed=True, n_word=W, n_frac=W-I)
x1 = Fxp(-3.4).like(d_fixed_t)
print(x1)
print(x1.bin())

This code outputs:

-3.3999999999068677
1111111111111111111111111111110010011001100110011001100110011010

Instead this code works:

x1 = Fxp(0)
x1 = x1.like(d_fixed_t)(-392.458)
print(x1)
x1.bin()
-234.11999999987893
'1111111111111111111111110001010111100001010001111010111000010101'

Another issue is that I cannot give as input a string to a numpy array fo fixed point, like that:

X_FPGA = Fxp(X_FPGA.like(d_fixed_t)(binary_strings)).like(d_fixed_t)

Hence I have to do a for loop like this:

for t in range(T):
    for n in range(N):
        X_FPGA[t][n] = Fxp(X_FPGA.like(d_fixed_t)(bin(buffer_X_FPGA[t][n]))).like(d_fixed_t)

Which is EXTREMELY slow. It is unpractible.

Does someone know how to solve these issues?

Thank you so much

davide-giacomini commented 1 year ago

I solved with this script, which is almost instantaneous:

int64_array = np.empty((T,N), dtype=np.int64)

# Cast the array to int64 using a loop
for t in range(T):
    for n in range(N):
        int64_array[t][n] = ctypes.c_int64(buffer_X_FPGA[t][n]).value

X_FPGA.set_val(int64_array, raw=True)

But still I think that I found a bug.

Let me know if anyone understands it.

Thank you, Davide

francof2a commented 11 months ago

Hello,

There are a couple of things here I wanna mention:

  1. What is the OS that your are using? If it's Windows, there are some problems with numpy with numbers over 32 bits. I did some workarounds to deal with this situation that can generate some wrong values but in some cases those workarounds do not cover all situations. I got different outcomes of the first cases, please let me know your OS, python and numpy version.
  2. When you use like method you are creating a new Fxp object copying the raw value of the original object. These new creation could lead in long execution time if you repeat lot of time, just like your nested for example.
    
    x1 = Fxp(-3.4).like(d_fixed_t) # you are creating twice a Fxp object

equivalent to

x1 = Fxp(3.4) x1 = x1.like(d_fixed_t)

better is

x1 = Fxp(3.4, like=d_fixed_t)

Check above a better way to create new Fxp using a previously defined Fxp. For more information check [copy](https://github.com/francof2a/fxpmath?tab=readme-ov-file#copy).

3. In the version `0.4.8` the way you need to use a binary string as input is with prefix `'0b'` (in the next version will be a new method and function to deal with other formats).

```python
# instead of
X_FPGA = Fxp(X_FPGA.like(d_fixed_t)(binary_strings)).like(d_fixed_t)   # you're creating 3 Fxp objects here!

# do
X_FPGA = Fxp('0b' + binary_string, like=d_fixed_t)
  1. For your nested for example consider do the following code:
# just an example to get buffer_X_FPGA
T = 1000
N = 1000
buffer_X_FPGA = np.random.randint(0, 100, size=(T,N))

# a function to convert to binary strings
bin_func = np.vectorize(lambda x: bin(x))

# convert to Fxp from binary strings in an array
x_fpga = Fxp(bin_func(buffer_X_FPGA), like=d_fixed_t)

I don't know the T and N dimension values, but the code above run fast for me. But be careful about using bin function of python because it the number of bits is variable and does not consider sign bit. Using the int64_array like in your last post is a much better way.