Search code examples
arraysgenericstypesvhdlmux

Creating a generic multiplexer


I want to create a generic multiplexer, meaning it can have a variable number of inputs and variable data_width. This means that for declaring the data input I need an array which would look like this:

type data is array(entries-1 downto 0) of std_logic_vector(data_width-1 downto 0);

However, I am not sure how I can accomplish this. I am confusing regarding where should I declare the type "data", as I must use it in the input port declaration


Solution

  • You can implement a generic multi-bit mux as follows:

    type data is array(natural range <>) of
        std_ulogic_vector(data_width-1 downto 0);
    
    --## Compute the integer result of the function ceil(log(n))
    --#  where b is the base
    function ceil_log(n, b : positive) return natural is
      variable log, residual : natural;
    begin
    
      residual := n - 1;
      log := 0;
    
      while residual > 0 loop
        residual := residual / b;
        log := log + 1;
      end loop;
    
      return log;
    end function;
    
    
    function mux_data(Inputs : data; Sel : unsigned)
      return std_ulogic_vector is
    
      alias inputs_asc    : data(0 to Inputs'length-1) is Inputs;
      variable pad_inputs : data(0 to (2 ** Sel'length) - 1);
      variable result     : std_ulogic_vector(inputs_asc(0)'range);
    begin
    
      assert inputs_asc'length <= 2 ** Sel'length
        report "Inputs vector size: " & integer'image(Inputs'length)
          & " is too big for the selection vector"
        severity failure;
    
      pad_inputs := (others => (others => '0'));
      pad_inputs(inputs_asc'range) := inputs_asc;
      result := pad_inputs(to_integer(Sel));
    
      return result;
    end function;
    
    
    signal mux_in  : data(0 to entries-1);
    signal mux_out : std_ulogic_vector(data_width-1 downto 0);
    signal mux_sel : unsigned(ceil_log(entries, 2)-1 downto 0);
    ...
    
    mux_out <= mux_data(mux_in, mux_sel);
    

    The mux_data function works by creating a temporary array pad_inputs which is guaranteed to be a power of 2 and greater than or equal to the number of entries. It copies the inputs into this array with any unoccupied positions defaulting to (others => '0'). It can then safely use integer indexing to pull out the selected input. The alias is present to ensure the function gracefully handles non-0-based arrays.

    The type data has been defined as an unconstrained array of std_ulogic_vector. The mux_data function will automatically adapt to any size without needing to know the entries generic. The function is written on the assumption that an ascending range array is passed in. A descending array will still work but selected indices won't match the binary value of the select control. The unsigned select control is automatically configured to be the required size with the ceil_log function. In this way the logic will adapt to any value for entries and data_width. For the doubters out there this will synthesize.

    It is not possible (prior to VHDL-2008) to put a signal of type data on the port since it needs to be declared with a constraint set by a generic. The standard way to handle this is to flatten your inputs into a 1-D array:

    port (
      mux_in_1d : std_ulogic_vector(entries*data_width-1 downto 0);
      ...
    );
    ...
    
    -- Expand the flattened array back into an array of arrays
    process(mux_in_1d)
    begin
      for i in mux_in'range loop
        mux_in(i) <= mux_in_1d((i+1)*data_width-1 downto i*data_width);
      end loop;
    end process; 
    

    With VHDL-2008 you can declare a fully unconstrained type data and use it on the port:

    -- Declare this in a package 
    type data is array(natural range <>) of std_ulogic_vector;
    ...
    
    port (
      mux_in : data(0 to entries-1)(data_width-1 downto 0);
      ...
    );
    ...
    
    -- Substitute this line in the mux_data function
    variable pad_inputs : data(0 to (2 ** Sel'length) - 1)(inputs_asc(0)'range);