UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Highlighted
Observer ieronym
Observer
814 Views
Registered: ‎07-05-2018

Transfer various data types through a single axi4 stream interface

Jump to solution

Hello

I'd like to implement a protocol processing IP with AXI4 stream interfaces which handles double precision numbers. Consider a dummy example where in my IP read and write interfaces are 128 bit wide and each packet consists of a 128-bit header, a 128-bit payload which contains two double-precision numbers and a 128-bit footer. To construct a 128-bit wide interface in HLS I have declared the following struct:

struct axisWord
{
	ap_uint<128> data;
	ap_uint<1>   last;
};

and my main function is declared as:

void foo(stream<axisWord> *in, stream<axisWord> &out);

In the example above each packets is read or written in 3 cycles. Now consider that the header the IP reads contains an address, the payload contains two 64-bit double precision numbers and the footer signals the end of the packet. So my IP has to, for example, add the two numbers and send a packet which contains a header, a payload which contains the addition result and a footer.

The obvious problem of the example above is that this implementation cannot handle double precision numbers since the the words that are read/written are handled as uint. So, my question is, how could someone construct a packet processing system with a custom stream interface width, let's say 128-bit wide, where the header/footer words are handled as bits while the payload words are handled as two doubles or four floats?

0 Kudos
1 Solution

Accepted Solutions
Xilinx Employee
Xilinx Employee
663 Views
Registered: ‎01-09-2008

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

I added a zipfile containing the source of a project showing what I wanted to explain.

This solution synthesizes perfectly.

In typedef.h I define the types and unions that I will need

struct axisWord
{
	ap_uint<128> data;
	ap_uint<1>   last;
};

typedef union { float fpval; unsigned int uintval;} fconvert;
typedef union { double fpval; unsigned long long int uintval;} dconvert;

I kept your testbench with the exception that there are now 2 streams: 1 to enter in the top level and the other to get out of it.

The IP itself makes use of the unions defined to extract the floating point values from the input stream, and to embed them in the output stream.

The computation is just a cumulative sum.

Olivier

==================================
Olivier Trémois
XILINX EMEA DSP Specialist
0 Kudos
8 Replies
Xilinx Employee
Xilinx Employee
797 Views
Registered: ‎09-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

Hey @ieronym,

I think this is a good time to use a union. I think the syntax would be something like this:

struct axisWord {
    union data{
        ap_uint<128> headerWord;
        double doubleWord[2];
        float floatWord[4];
    };
    ap_uint<1>   last;
};

Then you can keep track of which cycle of the packet you're receiving and process the data using the member which has the correct type.

Nicholas Moellers

Xilinx Worldwide Technical Support
0 Kudos
Observer ieronym
Observer
759 Views
Registered: ‎07-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

Thanks for your response. I have declared the struct as

 

struct axisWord
{
    union{
        ap_uint<128> headerFooterWord;
        double doubleWord[2];
        float floatWord[4];
    } data;
	ap_uint<1>   last;
};

like you mentioned and I have tried to do some simple tests to see if it works. Thus, inside my program I did this

axisWord tmp;
tmp.data.doubleWord[0] = 2.5;
tmp.data.doubleWord[1] = 5.2;
tmp.last = 0;
out << tmp;

 

to see if it can generate correct data in the testbench, but I get this error in C simulation:

../../../src/main.cpp:39:12: error: use of deleted function 'axisWord::axisWord()'
   axisWord tmp;
            ^~~
../../../src/main.cpp:9:8: note: 'axisWord::axisWord()' is implicitly deleted because the default definition would be ill-formed:
 struct axisWord
        ^~~~~~~~
../../../src/main.cpp:9:8: error: use of deleted function 'axisWord::<anonymous union>::<constructor>()'
../../../src/main.cpp:11:10: note: 'axisWord::<anonymous union>::<constructor>()' is implicitly deleted because the default definition would be ill-formed:
     union{
          ^
../../../src/main.cpp:12:22: error: union member 'axisWord::<anonymous union>::headerFooterWord' with non-trivial 'ap_uint<_AP_W>::ap_uint() [with int _AP_W = 128]'
         ap_uint<128> headerFooterWord;
                      ^~~~~~~~~~~~~~~~
../../../src/main.cpp:9:8: error: use of deleted function 'axisWord::<anonymous union>::~<constructor>()'
 struct axisWord
        ^~~~~~~~
../../../src/main.cpp:11:10: note: 'axisWord::<anonymous union>::~<constructor>()' is implicitly deleted because the default definition would be ill-formed:
     union{
          ^
../../../src/main.cpp:12:22: error: union member 'axisWord::<anonymous union>::headerFooterWord' with non-trivial 'ap_uint<128>::~ap_uint()'
         ap_uint<128> headerFooterWord;
                      ^~~~~~~~~~~~~~~~
../../../src/main.cpp:39:12: error: use of deleted function 'axisWord::~axisWord()'
   axisWord tmp;
            ^~~
../../../src/main.cpp:9:8: note: 'axisWord::~axisWord()' is implicitly deleted because the default definition would be ill-formed:
 struct axisWord
        ^~~~~~~~

If I comment out the ap_uint<128> headerFooterWord; line in the union my example passes the C simulation, so I assume there is a conflict with the ap_uint type and the union. But, nevertheless, in both cases synthesis fails. In the first case it reports some incomprehensible blue an red messages, while in the second it reports this:

ERROR: [SYNCHK 200-17] Unsupported union type of argument 'out.V.data' (exampleIP/src/main.cpp:59) temporarily.
INFO: [SYNCHK 200-10] 1 error(s), 0 warning(s).
ERROR: [HLS 200-70] Synthesizability check failed.

 

0 Kudos
Xilinx Employee
Xilinx Employee
738 Views
Registered: ‎09-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

Hey @ieronym,

You're right, that's what I get for getting creative with unions and not checking myself.

I'd just recommend the range() function then. Something like this:

float floatWord[4] = {0};
for( int i = 0 ; i < 4 ; i ++ ) {
    floatWord[i] = tmp.data.range(i*32, (i+1)*32-1);
}

The ap_[u]int::range function is documented on page 647 of ug637.

It might still be cleaner to put doubleWord and floatWord into a union:

typedef union {
	float as_four_floats[4];
	double as_two_doubles[2] = {0};
} data;

 

Nicholas Moellers

Xilinx Worldwide Technical Support
0 Kudos
Observer ieronym
Observer
706 Views
Registered: ‎07-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

That was my first thought when I first encountered this issue, but the problem is that the numbers are casted to int. For example this code:

        axisWord tmp;
	tmp.data.range(31, 0) = 0.1;
	tmp.data.range(63, 32) = 1.2;
	tmp.data.range(95, 64) = 2.3;
	tmp.data.range(127, 96) = 3.4;

	float floatWord[4] = {0.0, 0.0, 0.0, 0.0};
	for(int i = 0 ; i < 4 ; i ++){
	    floatWord[i] = tmp.data.range((i+1)*32-1, i*32);
	}

	printf("%f ", floatWord[0]);
	printf("%f ", floatWord[1]);
	printf("%f ", floatWord[2]);
	printf("%f\n", floatWord[3]);

prints

0.000000 1.000000 2.000000 3.000000

0 Kudos
Xilinx Employee
Xilinx Employee
696 Views
Registered: ‎01-09-2008

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

Classes are not allowed in union as you discovered by yourself!

But you still have to use unions to copy the bits of a floating point value to and integer.

union { float fpval; unsigned int uintval};

union { double fpval; unsigned long long int uintval;};

and then use the range method to initialize your 128 bit unsigned integer.

even if you have multiple line to convert your 2 doubles into a single ap_uint<128>, it will be only wires in the hardware.

 

==================================
Olivier Trémois
XILINX EMEA DSP Specialist
0 Kudos
Observer ieronym
Observer
675 Views
Registered: ‎07-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

The problem with the range method is that it truncates the decimal part of a float number whenever I use it. I found an elegant way to do what I want with memcpy, for example

	float init_data[4]  = {0.1, 1.2, 2.3, 3.4};
	float final_data[4] = {0.0, 0.0, 0.0, 0.0};
	stream<axisWord> test_stream;
	axisWord test_i, test_o;

	memcpy(&(test_i.data), init_data, 4*sizeof(float));
	test_i.last = 1;
	test_stream << test_i;
	test_stream >> test_o;
	memcpy(final_data, &(test_o.data), 4*sizeof(float));

	printf("---test memcpy in stream---\n");
	printf("data: %.1f %.1f %.1f %.1f\nlast: %d\n\n", final_data[0], final_data[1], final_data[2], final_data[3], (int) test_o.last);

which works fine in C but it is unsynthesizable

0 Kudos
Xilinx Employee
Xilinx Employee
664 Views
Registered: ‎01-09-2008

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

I added a zipfile containing the source of a project showing what I wanted to explain.

This solution synthesizes perfectly.

In typedef.h I define the types and unions that I will need

struct axisWord
{
	ap_uint<128> data;
	ap_uint<1>   last;
};

typedef union { float fpval; unsigned int uintval;} fconvert;
typedef union { double fpval; unsigned long long int uintval;} dconvert;

I kept your testbench with the exception that there are now 2 streams: 1 to enter in the top level and the other to get out of it.

The IP itself makes use of the unions defined to extract the floating point values from the input stream, and to embed them in the output stream.

The computation is just a cumulative sum.

Olivier

==================================
Olivier Trémois
XILINX EMEA DSP Specialist
0 Kudos
Observer ieronym
Observer
660 Views
Registered: ‎07-05-2018

Re: Transfer various data types through a single axi4 stream interface

Jump to solution

I finally found what the problem was. I had declared

union payloadData{
	int intval[4];
	float floatval[4];
	double doubleval[2];
} ;

as a global variable to use it everywhere and HLS refused to synthesize it. If I declare the same union locally inside a function or define a typedef as you did in your code, it synthesizes fine. This way I can copy the values by calling range 4 times or with one memcpy.Thank you very much for your help.

0 Kudos