Electronic – VHDL: Use a type, dependent on entity generics, for other entity ports/generics

vhdl

I'm trying to design a memory emulation entity for simulation. To make it as versatile as possible,
it uses generics to define data and address widths. Additionally, one should be able to supply an
initialization memory image via a third generic:

entity memory is
    generic (
        DATA_WIDTH : positive;
        ADDR_WIDTH : positive;
        INIT_DATA  : image_t
    );
    port (
        [...]
    );
end memory;

Where image_t would look something like this:

type image_t is
    array(0 to (2 ** ADDR_WIDTH - 1)) of
    std_logic_vector(DATA_WIDTH-1 downto 0);

But where could I possibly define this type? Entities don't have an is ... begin block like
architectures that could be used. After reading another question on here,
I've pondered on using a generic package:

package memory_pkg is
    generic (
        DATA_WIDTH : positive;
        ADDR_WIDTH : positive
    );

    type image_t is
        array(0 to (2 ** ADDR_WIDTH - 1)) of
        std_logic_vector(DATA_WIDTH-1 downto 0);
end package;

This just moves the problem one level deeper though, because I can't find a way to instantiate
the package such that

  1. it uses the entity generics (possible by instantiating it in the architecture head) AND
  2. its contents are available in the entity declaration (possible by instantiating it on the file level, before the entity)

So it seems like a dead end. Is there any way (maybe using VHDL-2019) to do what I'm trying to accomplish?

Best Answer

As mentioned by user8352 in the comments, VHDL-2008 indeed allows to solve the problem using an unconstrained array of unconstrained elements. In a normal, non-generic package, I added this type declaration:

package memory_image_pkg is
    type image_t is array(natural range <>) of std_logic_vector;
end package;

Then, in the entity, this type can be constrained directly in the generic definition:

use work.memory_image_pkg.all;

entity memory is
    generic (
        DATA_WIDTH : natural;
        ADDR_WIDTH : natural;
        INIT_DATA  : image_t(0 to 2 ** ADDR_WIDTH - 1)(DATA_WIDTH - 1 downto 0) := (others => (others => '0'))
    );
    port (
        [...]
    );
end memory;

To generate an instance of this entity with an initialization image, it's easiest to create a bounded constant and pass that to the generic map:

use work.memory_image_pkg.all;

architecture rtl of ram is
    constant INIT_DATA : image_t(0 to 65535)(7 downto 0) := (
        x"00",
        [...]
    );
begin
    mem: entity work.memory
        generic map (
            DATA_WIDTH => 8,
            ADDR_WIDTH => 16,
            INIT_DATA => INIT_DATA
        )
        port map (
            [...]
        );
end rtl;