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
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);