Multiplexers

Multiplexers can be built in a large number of ways in both Verilog and VHDL; some of these are described below. In practice, many Verilog multiplexers are built using the conditional operator (Mux #1), and most other multiplexers in both languages are built using sequential case statements (Mux #2).

VHDL multiplexers are also frequently inplemented as a selected signal assignment (Mux #3). This is the concurrent version of VHDL's sequential case statement, and is more compact, since it does not appear in a process.

Multiplexers are occasionally implemented simply by coding the required logic (Mux #4), or by direct selection (Mux #5) of an array element (in VHDL) or a vector bit-select (in Verilog).

There are a number of examples of an 8-to-1 multiplexer below, for both Verilog and VHDL, together with a single testbench which can be used for all the code examples. All the multiplexers have a 3-bit SEL input, an 8-bit I input, and a 1-bit O output:

A sequential if-else statement is also occasionally used to code a multiplexer, in both Verilog and VHDL. This will be implemented as a priority encoder when the width of the control expression (SEL) exceeds a small value. This is occasionally useful if there is a late-arriving data input; the priority can be adjusted so that this input has a minimal delay through to the output. The if-else implementation otherwise has no advantage over any other implementation here.

Verilog conditional (ternary) operator

The simplest way to implement "narrow" multiplexers in Verilog is to use the conditional (?:) operator:

// 2-to-1 mux; the A control selects either B or C
assign Q = A? B:C;

B and C are m-bit expressions (for example, 10-bit buses); this statement implements an (m x (2-to-1)) multiplexer.

This operator is particularly convenient, because it can be used in an expression, and so can form the right-hand-side of a continuous assignment. This is therefore a compact way of driving a net ("wire") without the overhead of the procedural if statement.

A Verilog mux described in this way is essentially identical to one described using an if-else statement, although the two will simulate differently when the control expression (A) contains metavalues. The synthesised hardware, however, will probably be identical; in particular, both are likely to be implemented as priority encoders once the control width exceeds a small value.

In practice, the conditional operator tends to be used for 2:1 multiplexers only, because:

  1. wider multiplexers are likely to be implemented as priority encoders. However, XST implements the 8:1 code below as efficiently as any other style here, and does not use a priority encoder.
  2. the code for cascaded conditional operators is generally less readable than the alternatives.

There is a widespread opinion that a multiplexer implemented using the conditional operator is better than other implementations because it propagates X values, so making it easier to find errors in a simulation. The reasoning is that the control expression is treated differently from the control expression in, for example, an if statement. If A contains metavalues, the corresponding bits of B and C are combined in a way that simulates X propagation from the control input through the multiplexer. However, in practice, this is of little use, for these reasons (1):

  1. Procedural if and case statements don't have this functionality, and it's not practical to build a complete device using only the conditional operator
  2. The X propagation is "optimistic". In some circumstances, a gate-level description will propagate X's while the conditional operator will not (see Mux #4 below). The behavioural code in Mux #5 below is even more pessimistic than the gate-level version, and is arguably a much better way to carry out X propagation.

mux8to1_a1.v

module MUX8TO1(input [2:0] SEL, input [7:0] I, output O);
   assign O =
          (SEL == 0)? I[0] : 
          (SEL == 1)? I[1] : 
          (SEL == 2)? I[2] : 
          (SEL == 3)? I[3] : 
          (SEL == 4)? I[4] : 
          (SEL == 5)? I[5] : 
          (SEL == 6)? I[6] : I[7];
endmodule
Sequential case statement

Some care is needed with the Verilog version of this code. Verilog case statements are essentially identical to if-else statements; each case item is "executed" in turn, and the first match wins. There is no requirement that case items do not overlap. This implies a priority-encoder structure, rather than a wide-input multiplexer. There is, additionally, no requirement for a default clause.

The interpretation of a Verilog case statement as a multiplexer is a synthesiser convention, rather than a language feature (contrast with VHDL, in which the simulation semantics of the case statement - in other words, the language itself - actually does have "multiplexer" behaviour). In order for a Verilog synthesiser to recognise and extract a multiplexer, the programmer has to ensure that two conditions are met:

  1. no case items overlap (the case statement is parallel). In this example, the case items are simply the integers 0 through 6, and the items are clearly all different, with no overlap.
  2. All possible values of the case expression (SEL) are covered by the case items (the case statement is full). If the case statement is not full, then a latch will be inferred, for simulation, synthesis, or both.

There are two ways to ensure that a case statement is full:

  1. explicitly list all possible values of the case expression (SEL). For a simulator, this means all 4-state values. For a synthesiser, this means all 2-state values (SEL has 64 possible 4-state values, and 8 possible 2-state values).
  2. use a default clause; this covers all values which are not explicitly listed.

Historically, it was common to add a synthesis directive (/* synopsys full_case parallel_case */) to tell the synthesiser that the case statement met these two conditions. This is not, however, a good idea; it gives the synthesiser information which is not available to the simulator, and potentially leads to simulation mismatches. It is safer, and simpler, to manually ensure that the case items do not overlap, and that you have a default clause.

There are two possibilities for the coding of the default clause, depending on whether or not you want to propagate metavalues. This applies to both the Verilog and VHDL models. If you're not interested in propagating metavalues, you can simply replace the last "real" (2-state) selector value with a default; both the examples below do this. If, on the other hand, you wish to propagate metavalues, all the "real" selector values should be explicitly listed, with an additional default. This default branch normally includes error-handling code (reporting an error, for example), but should be protected from synthesis:

    case (SEL)
      ...
      7:       O = I[7];
/* synthesis off */
      default: $display("no case matched");
/* synthesis on */
    endcase

Note also that blocking assignments (=) may be used in the Verilog code, since this is combinatorial code.

mux8to1_a2.v

module MUX8TO1(
    input [2:0] SEL, 
    input [7:0] I, 
    output reg  O);

  always @(SEL, I) begin
    case(SEL)
      0:        O = I[0];
      1:        O = I[1];
      2:        O = I[2];
      3:        O = I[3];
      4:        O = I[4];
      5:        O = I[5];
      6:        O = I[6];
      default:  O = I[7];
    endcase
  end
endmodule

mux8to1_a2.vhd

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;               -- unsigned and to_integer

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

architecture A of MUX8TO1 is
begin
  process(SEL, I) is
  begin
    -- could instead use to_integer(SEL) here; see mux8to1_a3.vhd  
    case SEL is 
      when "000"  => O <= I(0);
      when "001"  => O <= I(1);
      when "010"  => O <= I(2);
      when "011"  => O <= I(3);
      when "100"  => O <= I(4);
      when "101"  => O <= I(5);
      when "110"  => O <= I(6);
      when others => O <= I(7);
    end case;
  end process;
end architecture A;
VHDL selected signal assignment

This code uses a concurrent signal assignment, and is the concurrent equivalent of mux8to1_a2.vhd. It should produce identical results in synthesis. This version, however, is more concise, and doesn't require a correct sensitivity list. Note the use of the to_integer function to allow the case items to be specified as an integer (4 rather than "100", for example).

mux8to1_a3.vhd

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;               -- unsigned and to_integer

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

architecture A of MUX8TO1 is
begin
  with to_integer(SEL) select
    O <= 
      I(0) when 0,
      I(1) when 1,
      I(2) when 2,
      I(3) when 3,
      I(4) when 4,
      I(5) when 5,
      I(6) when 6,
      I(7) when others;
end architecture A;
Logic implementation

Multiplexers are trivially implemented as a logic operation. This form may be compact if the select input is already one-hot encoded, but otherwise has no particular advantage over any other form.

The Verilog version, however, does propagate X's in the select input if the logic equality operator (==) is used, although the resulting X propagation differs from that produced by the conditional operator. The table below shows the output of a 2-to-1 mux, with a select input of X:

Input?: outputAND-OR output
11 1 X
10 X X
01 X X
00 0 0

The AND-OR output is arguably preferable to the ?: output, since this output should also be produced by a gate-level model, and so would allow the RTL and gate-level models to simulate identically (2).

mux8to1_a4.v

module MUX8TO1(input [2:0] SEL, input [7:0] I, output O);
   assign O = 
          ((SEL == 0) & I[0]) |  // normally use brackets for clarity,
            SEL == 1  & I[1]  |  // but not required here (precedence
            SEL == 2  & I[2]  |  // is: == -> & -> |)
            SEL == 3  & I[3]  |
            SEL == 4  & I[4]  |
            SEL == 5  & I[5]  |
            SEL == 6  & I[6]  |
            SEL == 7  & I[7];
endmodule

VHDL is strongly-typed (3), so it is not possible to directly compare the select input against an integer. The to_integer function is therefore used to convert SEL to an integer (S) before the comparison. It is, similarly, not possible to directly combine boolean and std_logic quantities in an expression without overloading the operators. Rather than overloading the AND operator, this code defines function to_sl to convert the boolean result of the comparison to a std_logic. The std_logic result can then be directly combined with the data inputs. In practice, the subtype definition and the to_sl function would normally be in a project-wide package.

mux8to1_a4.vhd

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;               -- unsigned and to_integer

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

architecture A of MUX8TO1 is
  subtype int3 is integer range 0 to 7;
  signal S : int3;
  function to_sl(inp : boolean) return std_logic is
  begin
    if inp then
      return '1';
    else
      return '0';
    end if;
  end function to_sl;
begin
  S <= to_integer(SEL);
  O <= (to_sl(S = 0) and I(0)) or      -- brackets required when...
       (to_sl(S = 1) and I(1)) or      -- mixing 'and' and 'or'
       (to_sl(S = 2) and I(2)) or
       (to_sl(S = 3) and I(3)) or
       (to_sl(S = 4) and I(4)) or
       (to_sl(S = 5) and I(5)) or
       (to_sl(S = 6) and I(6)) or
       (to_sl(S = 7) and I(7));
end architecture A;
Array element selection

These two behavioural models are far simpler than the other mux8to1 models. However, perhaps surprisingly, XST produces exactly the same synthesis results as for any other model here. This style has perhaps not been used historically because of uncertainty over whether or not it is synthesisable; it would make sense to confirm this result with your own synthesiser before using this style.

This style also has an advantage in that metavalues in SEL are automatically propagated (for the Verilog code) or reported (for the VHDL code). The Verilog LRM requires I[SEL] to evaluate to X if SEL contains a metavalue. This is more pessimistic than either the conditional operator (Mux #1) or the logic-level examples (Mux #4), but is preferable in that it makes it more likely that the error will be detected. For the VHDL code, to_integer (from numeric_std) will report an error if SEL contains a metavalue, and will return 0.

mux8to1_a5.v

module MUX8TO1(
    input [2:0] SEL, 
    input [7:0] I, 
    output      O);

   assign O = I[SEL];
endmodule

mux8to1_a5.vhd

library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;               -- unsigned and to_integer

entity MUX8TO1 is
  port (
    SEL : in  unsigned(2 downto 0);
    I   : in  std_logic_vector(7 downto 0);
    O   : out std_logic);
end entity MUX8TO1;

architecture A of MUX8TO1 is
begin
  O <= I(to_integer(SEL));
end architecture A;
Testbench

mux8to1.tv

DUT {
   module MUX8TO1(input [2:0] SEL, input [7:0] I, output O);
   [SEL, I] -> [O];
}

main() {
   bit3 sel;
   bit8 data;
   for all data                     // cycles data from 0 to 255
      for all sel {                 // cycles sel from 0 to 7
         bit expected = data.(sel);
         [sel, data] -> [expected];
      }
}

This testbench can be used for all the mux8to1 code examples above. The expected output from the testbench is:

  (Log)   (20480 ns) 2048 vectors executed (2048 passes, 0 fails)
Synthesis results

The 4 Verilog and 4 VHDL models were synthesised with Xilinx XST L.57, on ISE 11.3, targetting a Virtex-5 device (the XC5VLX30).

XST inferrred a one-bit 8-to-1 multiplexer for all the models apart from the logic-level ones (mux8to1_a4.v and mux8to1_a4.vhd). The RTL schematics for the logic-level models showed a gate-level description, while the RTL schematics for the other 6 models showed an 8:1 multiplexer.

The technology schematics for all 8 models were, however, identical. The schematics showed two LUT6 blocks, each of which handled 4 data inputs and 2 select inputs; the outputs of these two blocks were then combined in a MUXF7 (a 2:1 multiplexer), to produce the single-bit output.