Many times I created VHDL blocks that may handle data of different types. One example is a heap stream data sorter - https://opencores.org/projects/heap_sorter , another one is the data concentrator for high-speed data acquisition - https://arxiv.org/abs/2309.13690 .
To enable reusing those blocks for handling data of different types in the same design, I usually place the data type definition in a separate package and then create an individual library for the particular data type, each with its own version of that package.
A demonstration of that approach for my concentrator is available as a GHDL simulation in https://gitlab.com/WZabISE/xconcentrator/-/tree/8d094449b79a9318708dd34e59b7455bf097dcb5/sim_complex_type .
Of course, that approach, while working, is far from being convenient. It would be much better to pass the type to the entity, which in theory should be possible in VHDL 2008. As an MWE we may use the multiplexer block.
The file mux.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library work;
entity mux is
generic (
type DATA_T;
NOF_INPUTS : integer);
port(
data_i : in array(NOF_INPUTS-1 downto 0) of DATA_T;
sel : in integer range 0 to NOF_INPUTS-1;
data_o : out DATA_T;
clk : in std_logic
);
end entity mux;
architecture rtl of mux is
begin -- architecture rtl
psel: process (clk) is
begin -- process psel
if clk'event and clk = '1' then
data_o <= data_i(sel);
end if;
end process psel;
end architecture rtl;
The testbench mux_tb.vhd:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mux_tb is
end entity mux_tb;
architecture sim of mux_tb is
-- First mux
constant NOF_INPUTS1 : integer := 4;
subtype DATA1_T is integer;
type DATA1_INS_T is array (NOF_INPUTS1-1 downto 0) of DATA1_T;
signal din1 : DATA1_INS_T := (10,-132,45,76);
signal dout1 : DATA1_T;
signal sel1 : integer range 0 to NOF_INPUTS1-1;
-- Second mux
constant NOF_INPUTS2 : integer := 3;
subtype DATA2_T is unsigned(4 downto 0);
type DATA2_INS_T is array (NOF_INPUTS2-1 downto 0) of DATA2_T;
signal din2 : DATA2_INS_T := (x"1",x"7",x"9");
signal dout2 : DATA2_T;
signal sel2 : integer range 0 to NOF_INPUTS2-1;
-- clock
signal clk : std_logic := '1';
begin -- architecture sim
-- component instantiation
DUT1: entity work.mux
generic map (
DATA_T => DATA1_T,
NOF_INPUTS => NOF_INPUTS1)
port map (
data_i => din1,
sel => sel1,
data_o => dout1,
clk => clk);
-- component instantiation
DUT2: entity work.mux
generic map (
DATA_T => DATA2_T,
NOF_INPUTS => NOF_INPUTS2)
port map (
data_i => din2,
sel => sel2,
data_o => dout2,
clk => clk);
-- clock generation
clk <= not clk after 10 ns;
-- waveform generation
WaveGen_Proc: process
begin
-- insert signal assignments here
wait until clk = '1';
sel1 <= 0;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 1;
sel2 <= 1;
wait until clk = '0';
wait until clk = '1';
sel1 <= 2;
sel2 <= 2;
wait until clk = '0';
wait until clk = '1';
sel1 <= 3;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 0;
sel2 <= 1;
wait;
end process WaveGen_Proc;
end architecture sim;
The makefile for GHDL:
STD=synopsys
VSTD=08
ENTITY=mux_tb
RUN_OPTIONS= --stop-time=200ns --wave=${ENTITY}.ghw
SOURCES = \
mux.vhd \
mux_tb.vhd \
OBJECTS=$(SOURCES:.vhd=.o)
all: $(OBJECTS)
%.o : %.vhd
ghdl -a -g -C --std=${VSTD} --ieee=${STD} $<
all: ${ENTITY}.ghw test
show: ${ENTITY} ${ENTITY}.ghw
gtkwave ${ENTITY}.ghw ${ENTITY}.sav
${ENTITY}: $(SOURCES:.vhd=.o)
ghdl -e -g --mb-comments --std=${VSTD} -fexplicit --ieee=${STD} ${ENTITY}
${ENTITY}.ghw: ${ENTITY}
./${ENTITY} ${RUN_OPTIONS}
clean:
rm -f *.o *.vcd *.ghw *.cf ${ENTITY}
Unfortunately, that approach doesn't work because I can't use an array as a port. The following error is produced:
mux.vhd:12:18:error: type mark expected in a subtype indication
data_i : in array(NOF_INPUTS-1 downto 0) of DATA_T;
^
mux.vhd:12:18:error: ';' or ')' expected after interface
data_i : in array(NOF_INPUTS-1 downto 0) of DATA_T;
The type of the input data must be declared first. So maybe a solution is passing that type as another generic:
The file mux.vhd:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library work;
entity mux is
generic (
type DATA_T;
type DATA_INS_T;
NOF_INPUTS : integer);
port(
data_i : DATA_INS_T;
sel : in integer range 0 to NOF_INPUTS-1;
data_o : out DATA_T;
clk : in std_logic
);
end entity mux;
architecture rtl of mux is
begin -- architecture rtl
psel: process (clk) is
begin -- process psel
if clk'event and clk = '1' then
data_o <= data_i(sel);
end if;
end process psel;
end architecture rtl;
The testbench mux_tb.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mux_tb is
end entity mux_tb;
architecture sim of mux_tb is
-- First mux
constant NOF_INPUTS1 : integer := 4;
subtype DATA1_T is integer;
type DATA1_INS_T is array (NOF_INPUTS1-1 downto 0) of DATA1_T;
signal din1 : DATA1_INS_T := (10,-132,45,76);
signal dout1 : DATA1_T;
signal sel1 : integer range 0 to NOF_INPUTS1-1;
-- Second mux
constant NOF_INPUTS2 : integer := 3;
subtype DATA2_T is unsigned(4 downto 0);
type DATA2_INS_T is array (NOF_INPUTS2-1 downto 0) of DATA2_T;
signal din2 : DATA2_INS_T := (x"1",x"7",x"9");
signal dout2 : DATA2_T;
signal sel2 : integer range 0 to NOF_INPUTS2-1;
-- clock
signal clk : std_logic := '1';
begin -- architecture sim
-- component instantiation
DUT1: entity work.mux
generic map (
DATA_T => DATA1_T,
DATA_INS_T => DATA1_INS_T,
NOF_INPUTS => NOF_INPUTS1)
port map (
data_i => din1,
sel => sel1,
data_o => dout1,
clk => clk);
-- component instantiation
DUT2: entity work.mux
generic map (
DATA_T => DATA2_T,
DATA_INS_T => DATA2_INS_T,
NOF_INPUTS => NOF_INPUTS2)
port map (
data_i => din2,
sel => sel2,
data_o => dout2,
clk => clk);
-- clock generation
clk <= not clk after 10 ns;
-- waveform generation
WaveGen_Proc: process
begin
-- insert signal assignments here
wait until clk = '1';
sel1 <= 0;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 1;
sel2 <= 1;
wait until clk = '0';
wait until clk = '1';
sel1 <= 2;
sel2 <= 2;
wait until clk = '0';
wait until clk = '1';
sel1 <= 3;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 0;
sel2 <= 1;
wait;
end process WaveGen_Proc;
end architecture sim;
Unfortunately, that doesn't work as well. The DATA_INS_T type is treated as a non-array.
mux.vhd:28:23:error: type of prefix is not an array
data_o <= data_i(sel);
Another possible solution available in VHDL 2008 is to use generic packages. So I defined the generic package mux_pkg.vhd:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
package mux_pkg is
generic (
type DATA_T;
constant NOF_INPUTS : in integer
);
type DATA_INS_T is array (NOF_INPUTS-1 downto 0) of DATA_T;
end package;
I modified the mux.vhd accordingly:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library work;
--use work.mux_pkg.all;
entity mux is
generic (
type DATA_T;
NOF_INPUTS : integer;
package my_pkg is new work.mux_pkg generic map (DATA_T => DATA_T, NOF_INPUTS => NOF_INPUTS)
);
port(
data_i : in my_pkg.DATA_INS_T;
sel : in integer range 0 to NOF_INPUTS-1;
data_o : out DATA_T;
clk : in std_logic
);
end entity mux;
architecture rtl of mux is
begin -- architecture rtl
psel: process (clk) is
begin -- process psel
if clk'event and clk = '1' then
data_o <= data_i(sel);
end if;
end process psel;
end architecture rtl;
The testbench mux_tb.vhd was modified as well:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mux_tb is
end entity mux_tb;
architecture sim of mux_tb is
-- First mux
constant NOF_INPUTS1 : integer := 4;
subtype DATA1_T is integer;
type DATA1_INS_T is array (NOF_INPUTS1-1 downto 0) of DATA1_T;
signal din1 : DATA1_INS_T := (10,-132,45,76);
signal dout1 : DATA1_T;
signal sel1 : integer range 0 to NOF_INPUTS1-1;
-- Second mux
constant NOF_INPUTS2 : integer := 3;
subtype DATA2_T is unsigned(3 downto 0);
type DATA2_INS_T is array (NOF_INPUTS2-1 downto 0) of DATA2_T;
signal din2 : DATA2_INS_T := (x"1",x"7",x"9");
signal dout2 : DATA2_T;
signal sel2 : integer range 0 to NOF_INPUTS2-1;
-- clock
signal clk : std_logic := '1';
begin -- architecture sim
-- component instantiation
DUT1: entity work.mux
generic map (
DATA_T => DATA1_T,
NOF_INPUTS => NOF_INPUTS1)
port map (
data_i => din1,
sel => sel1,
data_o => dout1,
clk => clk);
-- component instantiation
DUT2: entity work.mux
generic map (
DATA_T => DATA2_T,
NOF_INPUTS => NOF_INPUTS2)
port map (
data_i => din2,
sel => sel2,
data_o => dout2,
clk => clk);
-- clock generation
clk <= not clk after 10 ns;
-- waveform generation
WaveGen_Proc: process
begin
-- insert signal assignments here
wait until clk = '1';
sel1 <= 0;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 1;
sel2 <= 1;
wait until clk = '0';
wait until clk = '1';
sel1 <= 2;
sel2 <= 2;
wait until clk = '0';
wait until clk = '1';
sel1 <= 3;
sel2 <= 0;
wait until clk = '0';
wait until clk = '1';
sel1 <= 0;
sel2 <= 1;
wait;
end process WaveGen_Proc;
end architecture sim;
The makefile also needed small changes:
STD=synopsys
VSTD=08
ENTITY=mux_tb
RUN_OPTIONS= --stop-time=200ns --wave=${ENTITY}.ghw
SOURCES = \
mux_pkg.vhd \
mux.vhd \
mux_tb.vhd \
OBJECTS=$(SOURCES:.vhd=.o)
all: $(OBJECTS)
%.o : %.vhd
ghdl -a -g -C --std=${VSTD} --ieee=${STD} $<
all: ${ENTITY}.ghw test
show: ${ENTITY} ${ENTITY}.ghw
gtkwave ${ENTITY}.ghw ${ENTITY}.sav
${ENTITY}: $(SOURCES:.vhd=.o)
ghdl -e -g --mb-comments --std=${VSTD} -fexplicit --ieee=${STD} ${ENTITY}
${ENTITY}.ghw: ${ENTITY}
./${ENTITY} ${RUN_OPTIONS}
clean:
rm -f *.o *.vcd *.ghw *.cf ${ENTITY}
Unfortunately, this attempt also lead to an error:
mux_tb.vhd:37:17:error: can't associate 'din1' with port "data_i"
data_i => din1,
^
mux_tb.vhd:37:17:error: (type of 'din1' is data1_ins_t)
mux.vhd:15:5:error: (type of port "data_i" is data_ins_t)
mux_tb.vhd:48:17:error: can't associate 'din2' with port "data_i"
data_i => din2,
^
mux_tb.vhd:48:17:error: (type of 'din2' is data2_ins_t)
mux.vhd:15:5:error: (type of port "data_i" is data_ins_t)
ghdl:error: compilation error
So, my question is. Do the new features available in VHDL 2008 help to solve the problem of reusing the same block to process data of different types?
As I have mentioned in my post, I have a solution based on using separate libraries (one for each type of processed data). However, it doesn't require VHDL 2008. In that solution I put the definition of types into separate packages.
file data1_pkg.vhd:
file data2_pkg.vhd:
Additionally I prepare packages for mux entity, that use the above definitions:
File my_pkg1.vhd
and file my_pkg2.vhd:
They both deliver the package my_pkg, but they will be compiled into different libraries.
The mux.vhd file is now simple:
The testbench mux_tb.vhd uses muxes defined in separate libraries depending on the type of processed data:
The compilation and simulation is now run via a shell script build.sh:
This approach works perfectly. However it does not use the new VHDL 2008 features...