I have an HLS project that does image processing on video streams. It does some changes to the pixels through a convolution of each pixel with its nearest neighbors. The convolution requires coefficients stored in a lookup table (ROM) used to scale the neighboring pixel values in a sort of multiply-accumulate. The coefficients required vary with the 8-bit pixel values, and the neighbors distance from the pixel, so a lookup table of coefficients is required. The ROM is named GE and is coded in C++ as a three-dimensional (256 x 2 x 2) array of uint8. This should require storage for 1024 bytes of coefficients.
This convolution (multiply-accumulate with nearest neighbors) is done for each bit in the frame, so there is a big loop in the code that I have specified to be unrolled.
The filter.cpp component/design to be synthesized by HLS simply specifies an input and output stream:
void filter (stream_in_t &double_inputstream, stream_out_t &sout)
After the filter is compiled by i++ for the Arria target, the filter_inst.v (top-level module declaration) gives me the streaming interfaces I expect, and the customary start/busy/stall/done, but also gives me two extra interfaces that I did not expect:
See below. I'm guessing that the tool is not able to synthesize a ROM within the filter and is assuming the ROM is external to the filter. Perhaps the three-dimensional aspect is making this difficult? The Arria has plenty of resources left so there is room for much more RAM if it is required.
Has anyone seen something similar? Can anyone explain why I have these two extra interfaces? I am using HLS Pro v18.1.
I can answer my own question. The GE variable is a (constant) global variable in my design. It had *not* been declared as a "const". The HLS compiler considers global variables to exist outside of the component. Because the component does not own global values, the compiler is unable to make any assumptions about the value when performing optimizations. Even if the testbench maintains a global variable with a constant value, the compiler will not assume that the variable is constant in all situations. On the other hand, const qualified global variables are guaranteed to remain constant in all situations. So const qualified global variables can be folded into the component and fully optimized.
The input GE port and Avalon-MM interface are used to pass the location and fetch the value of 'GE' from an external memory. In the const qualified case the constant is optimized into the datapath and does not need to be fetched.
So, lesson learned - when declaring constants as global variables, make sure to use the "const" keyword in the variable declaration. Now that I have done this, those interfaces are no longer added to the synthesized design.