I'm trying to understand generic programming in VHDL and I've written some simple code to be able to do so. My goal is to be able to write a generic design (generic in the sense that certain things like input/output size of an entity are not specified) and then instantiate it in a top-level module and specify the design constraints at instantiation. This way, I can write a single design to satisfy many different parameter constraints depending on the needs of the user. Otherwise, I would need to write a separate design for each file where only a few modifications are made in each. An obvious use case for this sort of flexibility would be in creating a library.
These two files illustrate what I'm trying to achieve:
-- array_adder.vhd
--
-- Computes and outputs the pointwise addition of two arrays.
library ieee;
use ieee.numeric_std.all;
package array_adder_pkg is
generic (array_size : integer;
precision_size : integer
);
type array_type is array (0 to array_size-1) of signed(precision_size-1 downto 0);
component array_adder is
port (a : in array_type;
b : in array_type;
c : out array_type
);
end component;
end package;
-----main code:-----------------------------------------------------------------
library ieee;
use ieee.numeric_std.all;
--------------------------------------------------------------------------------
entity array_adder is
port (a : in array_type;
b : in array_type;
c : out array_type
);
end entity;
--------------------------------------------------------------------------------
architecture fpga of array_adder is
begin
comp: for i in 0 to array_size-1 generate
c(i) <= a(i) + b(i);
end generate;
end architecture;
and
-- top_level.vhd
--------------------------------------------------------------------------------
package small_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 5
);
package large_pkg is new work.array_adder_pkg
generic map (array_size => 15,
precision_size => 9
);
use work.small_pkg.all;
use work.large_pkg.all;
library ieee;
use ieee.numeric_std.all;
--------------------------------------------------------------------------------
entity top_level is
end entity;
--------------------------------------------------------------------------------
architecture fpga of top_level is
signal small_ina : work.small_pkg.array_type :=
(to_signed(1, work.small_pkg.precision_size),
to_signed(2, work.small_pkg.precision_size),
to_signed(3, work.small_pkg.precision_size));
signal small_inb : work.small_pkg.array_type :=
(to_signed(4, work.small_pkg.precision_size),
to_signed(5, work.small_pkg.precision_size),
to_signed(6, work.small_pkg.precision_size));
signal small_out : work.small_pkg.array_type;
signal large_ina : work.large_pkg.array_type :=
(to_signed(100, work.large_pkg.precision_size),
to_signed(110, work.large_pkg.precision_size),
to_signed(120, work.large_pkg.precision_size));
signal large_inb : work.large_pkg.array_type :=
(to_signed(50, work.large_pkg.precision_size),
to_signed(30, work.large_pkg.precision_size),
to_signed(80, work.large_pkg.precision_size));
signal large_out : work.large_pkg.array_type;
begin
small_array_adder: work.small_pkg.array_adder
port map (a => small_ina,
b => small_inb,
c => small_out);
large_array_adder: work.large_pkg.array_adder
port map (a => large_ina,
b => large_inb,
c => large_out);
end architecture;
This first set doesn't compile because array_adder
doesn't know what array_type
is. In ModelSim 10.4b, I get (vcom-1136) Unknown identifier "array_type".
That's not surprising, since I never instantiated the array_adder_pkg
.
So, I tried to instantiate a default package in array_adder.vhd
, which is mostly the same, but I'll include it to be complete. top_level.vhd
stays the same.
-- array_adder2.vhd
--
-- Computes and outputs the pointwise addition of two arrays.
library ieee;
use ieee.numeric_std.all;
package array_adder_pkg is
generic (array_size : integer;
precision_size : integer
);
type array_type is array (0 to array_size-1) of signed(precision_size-1 downto 0);
component array_adder is
port (a : in array_type;
b : in array_type;
c : out array_type
);
end component;
end package;
-----main code:-----------------------------------------------------------------
package default_array_adder_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 7
);
use work.default_array_adder_pkg.all;
library ieee;
use ieee.numeric_std.all;
--------------------------------------------------------------------------------
entity array_adder is
port (a : in array_type;
b : in array_type;
c : out array_type
);
end entity;
--------------------------------------------------------------------------------
architecture fpga of array_adder is
begin
comp: for i in 0 to array_size-1 generate
c(i) <= a(i) + b(i);
end generate;
end architecture;
Now both files compile, but when I try to simulate top_level.vhd
in ModelSim I get the error Fatal: (vsim-3714) At array depth 1, array lengths do not match. Left is 15 (0 to 14). Right is 3 (0 to 2).
which seems to indicate that ModelSim is having trouble with instantiation sizes different than the default. To test this further, I removed the second package instantiation and all code associated with it in top_level.vhd
(i.e. all the large_pkg
stuff and the component instantiation associated with it). I'll spare you the mostly redundant code this time. Again, this compiles as expected, but when I attempt to simulate it I get Fatal: (vsim-3807) Types do not match between component and entity for port "a".
The only way I can actually get this to work is to use the default package in the top-level design and forego two different sized instantiations, but this defeats the point of a generic design since I will need to write a separate design file for each set of parameter specifications.
Is there another way to do this that I'm missing? Or, is this not something that VHDL currently supports?
You were almost there, but needed to assign default values to the component in your package.
Then VHDL-2008 allows for unconstrained arrays, which can help you here.
See this example:
library ieee;
use ieee.numeric_std.all;
package array_pkg is
type signed_array is array (natural range <>) of signed;
end package;
use work.array_pkg.signed_array;
package array_adder_pkg is
generic(
array_size : positive;
precision_size : positive
);
subtype array_type is signed_array(0 to array_size-1)(precision_size-1 downto 0);
component array_adder is
generic(
array_size : positive := array_size;
precision_size : positive := precision_size
);
port (
a : in signed_array(0 to array_size-1)(precision_size-1 downto 0);
b : in signed_array(0 to array_size-1)(precision_size-1 downto 0);
c : out signed_array(0 to array_size-1)(precision_size-1 downto 0)
);
end component;
end package;
use work.array_pkg.signed_array;
entity array_adder is
generic(
array_size : positive;
precision_size : positive
);
port (
a : in signed_array(0 to array_size-1)(precision_size-1 downto 0);
b : in signed_array(0 to array_size-1)(precision_size-1 downto 0);
c : out signed_array(0 to array_size-1)(precision_size-1 downto 0)
);
end entity;
library ieee;
architecture fpga of array_adder is
use ieee.numeric_std.all;
begin
comp: for i in 0 to array_size-1 generate
c(i) <= a(i) + b(i);
end generate;
end architecture;
entity top_level is end entity;
package small_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 5
);
package large_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 9
);
library ieee;
architecture fpga of top_level is
use ieee.numeric_std.all;
signal small_ina : work.small_pkg.array_type :=
(to_signed(1, work.small_pkg.precision_size),
to_signed(2, work.small_pkg.precision_size),
to_signed(3, work.small_pkg.precision_size));
signal small_inb : work.small_pkg.array_type :=
(to_signed(4, work.small_pkg.precision_size),
to_signed(5, work.small_pkg.precision_size),
to_signed(6, work.small_pkg.precision_size));
signal small_out : work.small_pkg.array_type;
signal large_ina : work.large_pkg.array_type :=
(to_signed(100, work.large_pkg.precision_size),
to_signed(110, work.large_pkg.precision_size),
to_signed(120, work.large_pkg.precision_size));
signal large_inb : work.large_pkg.array_type :=
(to_signed(50, work.large_pkg.precision_size),
to_signed(30, work.large_pkg.precision_size),
to_signed(80, work.large_pkg.precision_size));
signal large_out : work.large_pkg.array_type;
begin
small_array_adder: work.small_pkg.array_adder
port map (a => small_ina,
b => small_inb,
c => small_out);
large_array_adder: work.large_pkg.array_adder
port map (a => large_ina,
b => large_inb,
c => large_out);
end architecture;
EDIT: I wanted to add that you can also generate the specialized packages local to your architecture. That way they will not be compiled into your library. Example:
entity top_level is end entity;
library ieee;
architecture fpga of top_level is
use ieee.numeric_std.all;
package small_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 5
);
package large_pkg is new work.array_adder_pkg
generic map (array_size => 3,
precision_size => 9
);
signal small_ina : small_pkg.array_type :=
(to_signed(1, small_pkg.precision_size),
to_signed(2, small_pkg.precision_size),
to_signed(3, small_pkg.precision_size));
signal small_inb : small_pkg.array_type :=
(to_signed(4, small_pkg.precision_size),
to_signed(5, small_pkg.precision_size),
to_signed(6, small_pkg.precision_size));
signal small_out : small_pkg.array_type;
signal large_ina : large_pkg.array_type :=
(to_signed(100, large_pkg.precision_size),
to_signed(110, large_pkg.precision_size),
to_signed(120, large_pkg.precision_size));
signal large_inb : large_pkg.array_type :=
(to_signed(50, large_pkg.precision_size),
to_signed(30, large_pkg.precision_size),
to_signed(80, large_pkg.precision_size));
signal large_out : large_pkg.array_type;
begin
small_array_adder: small_pkg.array_adder
port map (a => small_ina,
b => small_inb,
c => small_out);
large_array_adder: large_pkg.array_adder
port map (a => large_ina,
b => large_inb,
c => large_out);
end architecture;