Maurice
Maurice

Reputation: 476

VHDL generic case statement

I am trying to instantiate a mux with a generic number of case statements. Currently my code looks like this:

In these examples data_array and selector are inputs, data is the output and the width of the mux is 4.

process(all)
begin
    case? selector is
        when "1---" => data <= data_array(3);
        when "01--" => data <= data_array(2);
        when "001-" => data <= data_array(1);
        when "0001" => data <= data_array(0);
        when others => data <= (others => '-');
    end case?;
end process;

Is there a way to have a generic number of case statements? Or is there a similar feature that I could use?

I could solve this using code generation to generate the appropriate number of case statements but I was wondering if there is a VHDL(-2008) feature that I could use to solve this.

I have rewritten the mux to use a for loop but unfortunately my implementation tool is not handling this very well. The logic that is inferred is not optimal and quite bad in terms of timing.

In this example GENERIC_WIDTH is the width of the mux.

process(all)
begin
    data_v := (others => '0');
    for i in 0 to GENERIC_WIDTH-1 loop
        if selector(i) then
            data <= data_array(i);
        end if;
    end loop;
end process;

I am targeting a Xilinx device using Vivado 2017.3. Implementation results show that using the case statement yields more efficient logic (in terms of WNS and logic depth) than using the for loop.

Upvotes: 2

Views: 3139

Answers (1)

JHBonarius
JHBonarius

Reputation: 11271

It doesn't matter anymore: modern synthesis tools will correctly optimize all logic. I made a comparison between Vivado 2017.3 outputs. The base entity is

library ieee;
use ieee.std_logic_1164.all;

entity MyMux is
    generic(
        data_width : positive := 32;
        data_depth : positive := 4
    );
    port(
        clk : in std_logic;
        data_in : in std_logic_vector(data_width-1 downto 0);
        selector : in std_logic_vector(data_depth-1 downto 0);
        data_out : out std_logic_vector(data_width-1 downto 0)
    );
end entity;

Architecture 1:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    select_output: process(all) begin
        case? selector is
            when "1---" => data_out <= data_array(3);
            when "01--" => data_out <= data_array(2);
            when "001-" => data_out <= data_array(1);
            when "0001" => data_out <= data_array(0);
            when others => data_out <= (others => '-');
        end case?;
    end process;
end architecture;

Architecture 2:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    select_output: process(all) begin
        data_out <= (others => '-');
        for i in 0 to data_depth-1 loop
            if selector(i) then
                data_out <= data_array(i);
            end if;
        end loop;
    end process;
end architecture;

Architecture 3:

architecture rtl of MyMux is
    subtype data_type is std_logic_vector(data_width-1 downto 0);
    type data_array_type is array (0 to data_depth-1) of data_type;

    signal data_array : data_array_type := (others => (others => '0'));

    function my_mux(
        selector : std_logic_vector(data_depth-1 downto 0);
        data_array : data_array_type) return data_type is
        variable data : data_type;
    begin
        data := (others => '-');
        for i in 0 to data_depth-1 loop
            if selector(i)='1' then
                data := data_array(i);
            end if;
        end loop;
        return data;
    end function;
begin
    read_data : process(clk) begin
        if rising_edge(clk) then
            for i in data_depth-1 downto 1 loop
                data_array(i) <= data_array(i-1);
            end loop;
            data_array(0) <= data_in;
        end if;
    end process;

    data_out <= my_mux(selector, data_array);
end architecture;

Output:

  • Architecture 1: 32 LUT3, 32 LUT6, 128 FDRE
  • Architecture 2: 32 LUT3, 32 LUT5, 128 FDRE
  • Architecture 3: 32 LUT3, 32 LUT5, 128 FDRE

So they are all practically the same.

The problem is you case seems to be the non deterministic component: initial placement randomization. In my experience this initial placement is based on some randomizer seed extracted from the hash of the code. The same code will always give the same implementation. But make a very small change in the code, and timing and resource use might be completely different.

You should take note that the logic you describe in you code will implement as a chain of multiplexers. When the GENERIC_WIDTH increases, so will the delay. This is inevitable.

Upvotes: 3

Related Questions