-2

I'm trying to accomplish this with HLS, not with "normal" C++, so most libraries (STL, boost, etc.) won't work as they can't be synthesized (manual memory management is not allowed). I think this should be possible with template metaprogramming, but I'm a little stuck.

I want to create an array of shift registers, each with a variable depth. I have N inputs, and I want to create N shift registers, with depths 1 to N, where N is known at compile time. My shift register class basically looks like

template<int DEPTH>
class shift_register{
    int registers[DEPTH];
    ...
};

I tried following this and adapting it: Programmatically create static arrays at compile time in C++ , however, the issue is with the last line. Each templated shift register is going to be a different type, and so can't be put together in an array. But I do need an array, as there wouldn't be a way to access each shift register.

Any help would be appreciated!

Kartik Prabhu
  • 411
  • 1
  • 4
  • 16
  • Why would your approach work in your "HLS" when `std::array` doesn't? – Swordfish Nov 12 '18 at 01:26
  • You can't have arrays of different types per element. – Swordfish Nov 12 '18 at 01:28
  • 3
    Sounds like an x/y-problem to me. Explain what "HLS" is and what you want to accomplish (not how). Up to now you said nothing but showed a trivial class containing an array. – Swordfish Nov 12 '18 at 01:28
  • HLS is high level synthesis, which is basically creating digital circuits from C++ code. In this context, malloc/new won't work, since if you think about it, you can't really synthesize them to hardware. So because of that, manual memory management won't work, which is why `std::vector` is out, and why I'm trying to accomplish this with template metaprogramming. – Kartik Prabhu Nov 12 '18 at 01:38
  • 1
    yes, but what you showed is basically `std::array<>` so why not use that? – Swordfish Nov 12 '18 at 01:41
  • In terms of hardware, what needs to happen is that delays need to be added, in order to line up elements in a stream, which is what a shift register would do. I have it working where I manually create 4 shift registers, ex. `shift_register<1> sr1; shift_register<2> sr2; shift_register<3> sr3; shift_register<4>;` But the issue here is how do I from this to some construct where I can write something like `shift_register_generator<4> arr;` – Kartik Prabhu Nov 12 '18 at 01:44
  • And yes, you're right, `shift_register<>` is basically `std::array<>`. But the issue is how can I create an array of arrays at compile time, where each one has a different length? – Kartik Prabhu Nov 12 '18 at 01:46
  • 1
    You can't have an array of elements which types are different from another. – Swordfish Nov 12 '18 at 01:46
  • 1
    You introduced a term "HLS" which you didn't explain and now you want to add "what needs to happen is that delays need to be added" some mysterious delays ... you aren't making any sense. – Swordfish Nov 12 '18 at 01:47
  • C++98, C++11, C++14 or C++17? – max66 Nov 12 '18 at 01:58
  • I apologize if I'm being unclear. I thought I explained HLS in my first comment, let me know if there's something about it that's unclear. In terms of the problem, maybe it's easier to understand this problem: I have 2 arrays, A, B, and a scalar value X. I want to multiply all the elements in A by X, and then add B. In hardware, I would have two units, a multiplier and an adder. A is being fed into the multiplier, and B into the adder. The result of A[i] * X would only be ready to the adder after one cycle, in which case, I need to delay B by one cycle. Let me know if I'm still unclear. – Kartik Prabhu Nov 12 '18 at 02:06
  • This is C++11 . – Kartik Prabhu Nov 12 '18 at 02:07
  • A solution that would sort of work would be to give each shift_register an arbitrarily high number of registers, and set then the bound for each individually, but I want to see if I can do better than this. – Kartik Prabhu Nov 12 '18 at 02:12
  • If you have four shift registers only, why you do not use the same size 4 for all of them? – 273K Nov 12 '18 at 03:46
  • 2
    So "array" means packed buffer of uniform type that can be accessed by runtime index. I have no idea what subset of this you need, but apparently you don't want uniform types. We cannot solve a problem without aidea what the problem is. – Yakk - Adam Nevraumont Nov 12 '18 at 04:03
  • Neither do I understand the question. You have four `shift_registers` that can't be put in the same array, obviously. What do you want to do with them? – Passer By Nov 12 '18 at 04:45
  • Sorry guys! Looks like my question wasn't very clear. I was able to find a solution, which I'll add as an answer. Basically, my situation was that I wanted a way to generate `shift_registers<0>...shift_registers`, for any constant N. I was thinking to use an array, as there had to be a way to access any individual shift_register. I was looking for some sort of metaprogramming trick that would let me do this, as this had to be done at compile time, given the constraints of HLS. Thanks commentors for trying to understand the question! I'll try to be more clear next time. – Kartik Prabhu Nov 12 '18 at 07:02

3 Answers3

0

Just to clarify, my problem was the following: generate N shift_registers, templated from 1 to N, where N is a compile time constant.

For example, if I had N=4, I could easily write this as:

shift_register<1> sr1;
shift_register<2> sr2;
shift_register<3> sr3;
shift_register<4> sr4;

But this wouldn't be easy to change, if I wanted a different value for N in the future.

I ended up using the preprocessor and took the solution from here: How do I write a recursive for-loop "repeat" macro to generate C code with the CPP preprocessor?

I used the macros from that solution like this:

#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__

#define BODY(i) shift_register<i> CAT(sr,i) 
REPEAT_ADD_ONE(BODY, N, 1);

And then something similar to that in order to access the shift registers, in a sort of array fashion.

This let me achieve the compile time generation that I was looking for, and get the array type access I needed.

Kartik Prabhu
  • 411
  • 1
  • 4
  • 16
0

Your question was somewhat difficult to understand but I'll do my best...

template <typename ... Args>
constexpr auto make_array(Args && ... pArgs)
{
    using type = std::common_type_t<std::decay_t<Args>...>;
    return std::array<type, sizeof...(Args)>{ (type)pArgs ... };
}

Then use it like this:

auto constexpr var_array_of_arrays = std::make_tuple
(
    make_array(1, 2, 3, 3),
    make_array(2, 3, 4),
    make_array(1, 2, 3 ,4 ,3, 5)
);

To get the M'th element you access it like this, n has to actually be a compile-time constant:

std::get<M>(var_array_of_arrays);

To access the Nth element in the Mth array:

auto constexpr value = std::get<M>(var_array_of_arrays)[N]

An to improve the interface:

template <size_t M, size_t N, typename T >
constexpr decltype(auto) get_element(T && pInput)
{
    return std::get<M>(std::forward<T>(pInput))[N];
}

Used like this:

auto constexpr element0_1 = get_element<0, 1>(var_array_of_arrays);

This will allow you to use an array of variable length arrays, or atleast something that behaves like that and is identical to that in memory. A full example is here: Online compiler

David Ledger
  • 2,033
  • 1
  • 12
  • 27
0

Whenever I hear "compile time number sequence" I think std::index_sequence

namespace detail {
    template <typename>
    struct shift_registers;

    template <std::size_t ... Is> // 0, 1, ... N-1
    struct shift_registers<std::index_sequence<Is...> > {
        using type = std::tuple<shift_register<Is + 1>...>;
    };

    template <typename T>
    using shift_registers_t = typename shift_registers<T>::type
}

template <std::size_t N>
using shift_registers = detail::shift_registers_t<std::make_index_sequence<N>>;
Caleth
  • 52,200
  • 2
  • 44
  • 75