Using the same entity to process data of different types - are the new VHDL 2008 features usable for that?

84 Views Asked by At

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?

1

There are 1 best solutions below

4
wzab On

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:

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

package data1_pkg is
  constant NOF_INPUTS1 : integer := 4;
  subtype DATA1_T is integer;
  type DATA1_INS_T is array (NOF_INPUTS1-1 downto 0) of DATA1_T;
end package;

file data2_pkg.vhd:

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

package data2_pkg is
  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;
end package;

Additionally I prepare packages for mux entity, that use the above definitions:

File my_pkg1.vhd

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

package my_pkg is 
    subtype DATA_T is DATA1_T;
    subtype DATA_INS_T is DATA1_INS_T;
    constant NOF_INPUTS : integer := NOF_INPUTS1;
end package;

and file my_pkg2.vhd:

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

package my_pkg is 
    subtype DATA_T is DATA2_T;
    subtype DATA_INS_T is DATA2_INS_T;
    constant NOF_INPUTS : integer := NOF_INPUTS2;
end package;

They both deliver the package my_pkg, but they will be compiled into different libraries.

The mux.vhd file is now simple:

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

library work;
use work.my_pkg.all;

entity mux is
  port(
    data_i : in 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 uses muxes defined in separate libraries depending on the type of processed data:

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

library lib1;
use lib1.data1_pkg.all;

library lib2;
use lib2.data2_pkg.all;

entity mux_tb is

end entity mux_tb;

architecture sim of mux_tb is

  -- First mux
  signal din1 : DATA1_INS_T := (10,-132,45,76);  
  signal dout1 : DATA1_T;
  signal sel1    : integer range 0 to NOF_INPUTS1-1;

  -- Second mux
  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 lib1.mux
    port map (
      data_i => din1,
      sel    => sel1,
      data_o => dout1,
      clk    => clk);

  -- component instantiation
  DUT2: entity lib2.mux
    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 compilation and simulation is now run via a shell script build.sh:

#!/bin/bash

# Do cleanup
rm -rf *.o *.cf lib1 lib2
mkdir lib1
mkdir lib2

STD=synopsys
VSTD=02
ENTITY=mux_tb
RUN_OPTIONS="--stop-time=200ns --wave=${ENTITY}.ghw"

LIB1_SRC="\
  data1_pkg.vhd \
  my_pkg1.vhd \
  mux.vhd \
  "

LIB2_SRC="\
  data2_pkg.vhd \
  my_pkg2.vhd \
  mux.vhd \
"

SOURCES=" \
  mux_tb.vhd \
"

for f in ${LIB1_SRC}; do 
   ghdl -a -g -C  --work=lib1 --workdir=lib1 --std=${VSTD} --ieee=${STD} $f
done
echo Done lib1

for f in ${LIB2_SRC}; do
   ghdl -a -g -C  --work=lib2 --workdir=lib2 --std=${VSTD} --ieee=${STD} $f
done
echo done lib2

for f in ${SOURCES}; do
   ghdl -a -P./lib1/ -P./lib2/ --std=${VSTD} -g -C --ieee=${STD} $f
done
echo Analyzed

ghdl -e  -g -P./lib1/ -P./lib2/ --mb-comments  --std=${VSTD} -fexplicit --ieee=${STD} ${ENTITY} 

./${ENTITY} ${RUN_OPTIONS} 

This approach works perfectly. However it does not use the new VHDL 2008 features...