3

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

Morten Zilmer
  • 15,586
  • 3
  • 30
  • 49
user2971971
  • 183
  • 6
  • 11

3 Answers3

4

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);
Kevin Thibedeau
  • 3,299
  • 15
  • 26
1

You can implement a mux without port element having a specific data array type:

library ieee;
use ieee.std_logic_1164.all;


entity generic_mux is
    generic (
        entries:    natural := 3;
        data_width: natural := 8        
    );
    port (
        signal inp:     in  std_logic_vector (entries*data_width-1 downto 0);
        signal sel:     in  natural range 0 to entries-1;
        signal outp:    out std_logic_vector (data_width-1 downto 0)
    );
end entity;

architecture foo of generic_mux is
    type mux_array is array (natural range 0 to entries-1) of 
                std_logic_vector(outp'range);
    signal array_val: mux_array;
begin

GEN: for i in array_val'range generate
        array_val(i) <= inp (outp'LEFT+(i*data_width) downto i*data_width);
    end generate;

    outp <= array_val(sel);

end architecture;

The above generic mux instead depends on generics (entries, data_width) to convey partition information for a single dimensional array type (std_logic_vector in this case) because these can be known at the place of instantiation:

library ieee;
use ieee.std_logic_1164.all;

entity instantiation is
end entity;

architecture foo of instantiation is
    constant entries:   natural := 4;
    constant data_width: natural := 8;
    signal a:    std_logic_vector (data_width-1 downto 0) := X"FE";
    signal b:    std_logic_vector (data_width-1 downto 0) := X"ED";
    signal c:    std_logic_vector (data_width-1 downto 0) := X"FA";
    signal d:    std_logic_vector (data_width-1 downto 0) := X"CE";
    signal sel:  natural range 0 to 3;
    signal inp:  std_logic_vector (entries*data_width-1 downto 0);
    signal outp: std_logic_vector (data_width-1 downto 0);
begin
    inp <= d & c & b & a;
MUX: entity work.generic_mux
    generic map (entries => 4)
    port map (
        inp => inp,
        sel => sel,
        outp => outp
    );

STIMULUS:
    process
    begin
        for i in 1 to entries-1 loop
            wait for 10 ns;
            sel <= i;
        end loop;
        wait for 10 ns;
        wait;
    end process;

end architecture;

This has the saving grace of not needing a type declaration visible to the block where the mux is instantiated nor counting on VHDL 2008 features currently not universally supported by synthesis.

Note that the generic mux uses an array type declaration and a generate statement type conversion (by signal assignment) to create a the actual multiplexer. This reflects how you would operate a read interface on a memory. It could have been done with a variable declared in a process, assigned to outp.

There is little advantage to implementing instantiated multiplexers other than to allow the design description to reflect a structural model. Synthesis tools require all choices be represented in a case statement, a selected signal assignment or conditional signal assignment.

0

For combinatorial mux operation only, and without any special type for data, then a function like this may be convenient:

function mux(constant ENTRIES : natural;
                      sel     : std_logic_vector;
                      data    : std_logic_vector)  -- Data for sel = 0 at left position
  return std_logic_vector is
  constant DATA_WIDTH   : natural := data'length / ENTRIES;
  alias    data_norm    : std_logic_vector(0 to data'length - 1) is data;
  type     data_split_t is array (0 to ENTRIES - 1) of std_logic_vector(0 to DATA_WIDTH - 1);
  variable data_split_v : data_split_t;
  variable res_v        : std_logic_vector(0 to DATA_WIDTH - 1);
begin
  for i in 0 to ENTRIES - 1 loop
    data_split_v(i) := data(i * DATA_WIDTH to (i + 1) * DATA_WIDTH - 1);
  end loop;
  res_v := data_split_v(to_integer(unsigned(sel)));
  return res_v;
end function;

The DATA_WIDTH is derived from the entire data length, based on the number of specified entries.

For use, the function can be called as concurrent function call, like:

dut_out <= mux(ENTRIES,
               dut_sel,
               dut_in_0 &
               dut_in_1 &
               dut_in_2);
Morten Zilmer
  • 15,586
  • 3
  • 30
  • 49