Search code examples
vhdlmodelsim

How to instantiate a component that takes a generic package?


I have the following situation: I have modules X and Y in my VHDL design which can be customized according to a large set of parameters. For that, I include these parameters as Generics that are part of the declarations of X and Y. Moreover, X must be instantiated inside Y.

However, since this parameter list is quite large, I thought it best to enclose them all inside a generic package, and this is where the trouble starts. All goes well when I try to compile module X with the generic package. However, when I try to compile module Y (and instantiating module X inside) I cannot find a way to map the top-level generic package in Y to the X module.

To clarify, here's an MWE of my scenario:

The package:

package generic_parameters is
    Generic(
        PARAM_A : natural;
        PARAM_B : natural);

end package generic_parameters;

Module X:

library ieee;
use ieee.std_logic_1164.all;

entity module_X is
    generic(package p is new work.generic_parameters generic map (<>));
    port(
        A : in std_logic_vector(p.PARAM_A-1 downto 0);
        B : out std_logic_vector(p.PARAM_A-1 downto 0));
end entity module_X;

architecture RTL of module_X is

begin

    B <= not(A);

end architecture RTL;

Module Y:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity module_Y is
    generic(package p is new work.generic_parameters generic map (<>));
    port(
        A : in std_logic_vector(p.PARAM_A-1 downto 0);
        B : out std_logic_vector(p.PARAM_A-1 downto 0));
end entity module_Y;

architecture RTL of module_Y is

begin

    instance_X: entity work.module_X
        generic map(p => p)
        port map(
            A => A,
            B => B);

end architecture RTL;

The two errors I get are (ModelSim 2020.1, in file module_Y.vhd):

  • Bad expression in actual part of association element 'p'.
  • Actual parameter for interface package declaration 'p' is not a package instantiation.

Is what I am trying to accomplish impossible? What alternative is there?


Solution

  • Not having access to Modelsim or EDA playground the sample code was analyzed with ghd which produces errors similar to Modelsim's incidentally describing the actual problem:

    ghdl -a --std=08 mbrandalero.vhdl
    mbrandalero.vhdl:14:35:error: no declaration for "param_a" in package interface "p"
    mbrandalero.vhdl:15:36:error: no declaration for "param_a" in package interface "p"
    mbrandalero.vhdl:18:14:error: entity 'module_x' was not analyzed
    mbrandalero.vhdl:33:35:error: no declaration for "param_a" in package interface "p"
    mbrandalero.vhdl:34:36:error: no declaration for "param_a" in package interface "p"
    mbrandalero.vhdl:37:14:error: entity 'module_y' was not analyed
    ghdl:error: compilation error
    

    (The design units lumped all in one file with the errors occurring for port signal declarations depending on PARAM_A in Module_X and Module_Y.)

    The visibility issue is specified in IEEE Std 1076-2008:

    4.7 Package declarations

    Items declared immediately within a simple or a generic-mapped package declaration become visible by selection within a given design unit wherever the name of that package is visible in the given unit. Such items may also be made directly visible by an appropriate use clause (see 12.4). Items declared immediately within an uninstantiated package declaration cannot be made visible outside of the package.

    Here the generics are declared immediately within an uninstantiated package not instantiated until elaboration. The name of instantiated package (p) is visible but not the generic constants (PARAM_A and PARAM_B) until elaboration of the component instantiation of instance_X in Module_Y and presumably entity work.Module_Y in a higher point in a design hierarchy. This includes visibility by use of selected names.

    It's apparent that generic instantiated packages are not intended to be used in port clause declarations.

    Is there an alternative to mapping an instantiated package as a generic?

    Yes. Addressing the problem exhibited in the example code and noting you're trying to use the same instantiated package down through a design hierarchy you could instantiate package p as a primary design unit and add that design unit to the various context clauses (Module_X, Module_Y, the next higher level in the design hierarchy):

    package generic_parameters is
        generic (PARAM_A: natural; PARAM_B: natural);
    end package generic_parameters;
    
    package p is new work.generic_parameters  -- ADDED primary unit declaration
        generic map (PARAM_A => 4, PARAM_B => 2);
    
    library ieee;
    use ieee.std_logic_1164.all;
    use work.p.all;  -- ADDED use clause
    
    entity module_X is
        -- generic (package p is new work.generic_parameters generic map (<>));
        port (
            A:  in  std_logic_vector(PARAM_A - 1 downto 0); -- WAS p.PARAM_A
            B:  out std_logic_vector(PARAM_A - 1 downto 0)  -- WAS p.PARAM_A
        );
    end entity module_X;
    
    architecture RTL of module_X is
    begin
        B <= not(A);
    end architecture RTL;
    
    library ieee;
    use ieee.std_logic_1164.all;
    -- use ieee.numeric_std.all;    -- NOT USED
    use work.p.all;                 -- ADDED USE CLAUSE
    
    entity module_Y is
        -- generic (package p is new work.generic_parameters generic map (<>));
        port (
            A:  in  std_logic_vector(PARAM_A - 1 downto 0); -- WAS p.PARAM_A
            B:  out std_logic_vector(PARAM_A - 1 downto 0)  -- WAS p.PARAM_A
        );
    end entity module_Y;
    
    architecture RTL of module_Y is
    begin
    instance_X:
        entity work.module_X
            -- generic map (p => p)
            port map (
                A => A,
                B => B
            );
    end architecture RTL;
    
    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use work.p.all;
    
    entity module_y_tb is
    end entity;
    
    architecture foo of module_y_tb is
        signal A: std_logic_vector (PARAM_A - 1 downto 0);
        signal B: std_logic_vector (PARAM_A - 1 downto 0);
    begin
    UUT:
        entity work.module_y
            port map (
                A => A,
                B => B
            );
    STIMULUS:
        process
        begin
            for i in 0 to 15 loop
                A <= std_logic_vector (to_unsigned(i, A'LENGTH));
                wait for 10 ns;
            end loop;
            wait;
        end process;
    end architecture;
    

    For lack of a reason to have generic clauses here they were simply commented out, the package instantiations were the element.

    The testbench produces:

    module_y_tb.jpg

    The only drawback to making all the various primary units dependent on the same analyzed library unit (package p) is that if that changes all design units with analysis order dependencies on it require re-analysis.

    There are several ways to deal with that:

    • Provide multiple packages to reduce the analysis order dependency.

    • Use a generic constant of a record type (where supported by the entire tool chain). It's possible to add 'spare' elements to use prior to re-analyzing the entire affected portion of a design hierarchy. Pass the generic constant of the record type to reduce the text complexity of generic clauses.

    • A record constant can have it's value defined using an impure function reading a file via textio. This occurs at elaboration time and is widely supported by synthesis and simulation vendors.

    Questions on these potential solutions can deal with specific problems. The modified example code shown above meets the requirements of this question which doesn't require elaboration time binding. All the package instantiations would use a top level package.

    Using a record constant instead of an instantiated package

    package generic_parameters is
        generic (PARAM_A: natural; PARAM_B: natural);
        type param_rec is record   -- list of parameters
            A: natural;
            B: natural;
        end record;
        constant PARAM: param_rec := (PARAM_A, PARAM_B);
    end package generic_parameters;
    
    package p is new work.generic_parameters
        generic map (PARAM_A => 4, PARAM_B => 2);
    
    library ieee;
    use ieee.std_logic_1164.all;
    use work.p.all;
    
    entity module_X is
        generic (param: param_rec);
        port (
            A:  in  std_logic_vector(param.A - 1 downto 0);
            B:  out std_logic_vector(param.A - 1 downto 0)
        );
    end entity module_X;
    
    architecture RTL of module_X is
    begin
        B <= not A;
    end architecture RTL;
    
    library ieee;
    use ieee.std_logic_1164.all;
    use work.p.all;
    
    entity module_Y is
        generic (param: param_rec);
        port (
            A:  in  std_logic_vector(param.A - 1 downto 0);
            B:  out std_logic_vector(param.A - 1 downto 0)
        );
    end entity module_Y;
    
    architecture RTL of module_Y is
    begin
    instance_X:
        entity work.module_X
            generic map (param => param)
            port map (
                A => A,
                B => B
            );
    end architecture RTL;
    
    library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;
    use work.p.all;
    
    entity module_y_tb is   -- TOP LEVEL of design hierarch
    end entity;
    
    architecture foo of module_y_tb is
        signal A: std_logic_vector (PARAM_A - 1 downto 0);
        signal B: std_logic_vector (PARAM_A - 1 downto 0);
        constant tb_param: param_rec := (A => 4, B => 2);
    begin
    UUT:
        entity work.module_y
            generic map (param => param)
            port map (
                A => A,
                B => B
            );
    STIMULUS:
        process
        begin
            for i in 0 to 15 loop
                A <= std_logic_vector (to_unsigned(i, A'LENGTH));
                wait for 10 ns;
            end loop;
            wait;
        end process;
    end architecture;
    

    This would allow substitutions at every generic map much as a package instantiation would during elaboration, subject to limitations requiring matching elements between formals and actuals in port association lists.