Multiplexers are very common in hardware design and generally FPGA configurable logic blocks have the hardware to support multiplexers natively. To take advantage of the dedicated hardware for the multiplexer we know and love the simple case statement. The case statement is simple, elegant, and most of all doesn’t instantiate priority encoder logic like an if-elsif statement.
But what if a case statement isn’t good enough? What happens when we need a little more programmability. Maybe we would like to have a little more configurability in the cases and we can not state them statically in the case conditions?
Conditions for a selector
A selector is useful when you would like to change a signal assignment based on some run time data. For example in an AXI-streaming interface a user field could control data flow. If you are unfamiliar with AXI-Streaming you can find more information from the AXI user guide. Similar logic can be used while using the Avalon Specification.
In data on a side-band channel is used to parse data in some way. In this scenario it is difficult to find a way to define static states in which to assign the signal outputs.
The selector is in many ways the same as a multiplexer however the select lines in the traditional multiplexer are used as the array indices of a vector. In this way the data can be selected based on the dynamic data.
In its basic form the selector looks like a for loop in VHDL. For loops are of course quite useful constructs in VHDL if you can remember how to use them properly (make sure to take off you software hat). Inside the for-loop we use the loop variable as a stand in for the dynamic data and we can condition on the loop variable equaling the input data. Once we know the loop variable is the correct value we can perform the signal assignment. For this to work we also need to set the default value above the for loop to ensure some assignment is made.
It is a good practice what will be instantiated in hardware as you write your code. In the case of the above for-loop with an if-statement inside we will be instantiating a replication of the signal assignments based on each of the iterations of the for loop. All of these path will be calculated in each valid clock cycle. But at the end a multiplexer is going to be used to selector which output is assigned the output value.
As our example say we have a selection input of two bits. The two bit value can be thought of as how many 4-bit nibbles will be copied to a data bus. The values will be copied from a user bus and assigned in the data bus. To make things little more interesting we will assign the Least Significant Bits of data with the Most Significant Bits of the user field.
Below we present an entity declaration for the selector block of VHDL. We have a streaming input and a streaming output and we have a select signal that is going to be used to determine the number of user nibbles copied to the data bus.
entity selector is port( i_clk : in std_logic; i_rst : in std_logic; i_data : in std_logic_vector(15 downto 0); i_user : in std_logic_vector(15 downto 0); i_valid : in std_logic; o_rdy : out std_logic; -- Selects how much data from user to put on data -- user is pass through -- sel | data map -- 00 | data(15:0) -- 01 | data(15:4) user(15:12) -- 10 | data(15:8) user(15:8) -- 11 | data(15:12) user(15:4) i_sel : in std_logic_vector(1 downto 0); o_data : out std_logic_vector(15 downto 0); o_user : out std_logic_vector(15 downto 0); o_valid : out std_logic; i_rdy : in std_logic ); end entity selector;
The constant and signal declarations are provided below. The constants are used for read ability. Eventually these could be generics and a configurable bus width and symbol width could be supported. For the sake of this example we choose to keep it simple.
-- Constants constant k_sl : integer := 4; -- Symbol Length constant k_ns : integer := i_data'length/k_sl; -- Number of symbols -- Input Registers signal f_data : std_logic_vector(15 downto 0); signal f_user : std_logic_vector(15 downto 0); signal f_valid : std_logic;
The selector process is posted below. We see under the ready signal we have a default assignment of the input data being assigned to the output register then after that a for-loop is used to select the correct output signal concatenation.
p_selector : process(i_clk) begin if rising_edge(i_clk) then if i_rst = '1' then f_valid <= '0'; else if i_rdy = '1' then -- Register Outputs f_data <= i_data; f_user <= i_user; f_valid <= i_valid; for i in 0 to 2**i_sel'length loop if i = to_integer(unsigned(i_sel)) then f_data(k_sl*i-1 downto 0) <= i_user(i_user'high downto i_user'high - k_sl*(k_ns - i) downto 0); end if; end loop; end if; end if; end if; end process;