Simulation/Synthesis Mismatch and the Design of Multiplexers

by GateForge Consulting Ltd.

A pitfall in the Verilog language is the treatment of unknown X values in if statements, and it can cause a mismatch between the simulated and the synthesized behaviours. I have observed this exact mismatch in the field. This problem carries over into the design of multiplexers in general.

Here's a contrived example, where we select one of two possibilities:

reg                         selector;
reg     [WORD_WIDTH-1:0]    option_A;
reg     [WORD_WIDTH-1:0]    option_B;
reg     [WORD_WIDTH-1:0]    result;

always @(*) begin
    if (selector == 0) begin
        result <= option_A;
    end
    else begin
        result <= option_B;
    end
end

The problem here happens if selector is X or Z-valued. In real hardware, we would expect an X or Z selector to cause result to obtain an X value, but the if statement treats X as false, and so falls through to the else case. The simulation returns option_B while the synthesis returns X. This can also confuse verification efforts.


Instead, we can use the ternary ?: operator, which behaves as expected. Except in cases where both option_A/option_B evaluate to 1 or 0, an X or Z-valued selector will return an X value:

always @(*) begin
    result <= (selector == 0) ? option_A : option_B;
end

However, the ternary operator gets impractical for more that two options, and the case statement gets tedious and error-prone as the number of cases increases.


Instead, we can replace these with a single multiplexer module that simulates and synthesizes correctly, implemented using a vector part select. In the following code, we can think of selector as "addressing" one of the in options.

// Generic multiplexer
// Concatenate in with the zeroth element on the right.

module Addressed_Mux
#(
    parameter       WORD_WIDTH                          = 0,
    parameter       ADDR_WIDTH                          = 0,
    parameter       INPUT_COUNT                         = 0
)
(
    input   wire    [ADDR_WIDTH-1:0]                    addr,    
    input   wire    [(WORD_WIDTH * INPUT_COUNT)-1:0]    in,
    output  reg     [WORD_WIDTH-1:0]                    out
);
    always @(*) begin
        out <= in[(addr * WORD_WIDTH) +: WORD_WIDTH];
    end
endmodule

This module is also a useful "jellybean" to implement arbitrary small Boolean functions in a generic and maintainable manner. For example, assume we select one of two options, but forcibly return zero if an enable line is not set:

reg                         enable;
reg                         selector;
reg     [WORD_WIDTH-1:0]    option_A;
reg     [WORD_WIDTH-1:0]    option_B;
wire    [WORD_WIDTH-1:0]    result;

localparam ZERO = {WORD_WIDTH{1'b0}};

Addressed_Mux
#(
    .WORD_WIDTH     (WORD_WIDTH),
    .ADDR_WIDTH     (2),
    .INPUT_COUNT    (4)
)
Select_with_Enable
(
    .addr           ({enable,selector}),    
    .in             ({option_B,option_A,ZERO,ZERO}),
    .out            (result)
);

fpgacpu.ca