Multiplexers

Now, we will demonstrate how to use conditional expressions through a series of small, related examples.

2-Way Multiplexer

Mux2 is a module which takes two inputs and selects between them.

mux2.vir
pub mod Mux2 {
    incoming a : Word[8]
    incoming b : Word[8]

    incoming select : Bit

    outgoing out : Word[8] dependson a, b, select {
        it := mux(select, a, b)
    }
}

Mux2 is a 2-way multiplexer, which takes in two signals a and b and a control signal select. It outputs a if select is asserted and b otherwise.

This is accomplished through the use of the mux primitive. The first argument to mux must have type Bit. The second and third arguments must have the same type as each other. If the first argument to mux is true, it returns the second argument. Otherwise, it returns the third argument.

Note

Verilog uses the syntax select ? a : b. It is originally borrowed from C, and is sometimes called the ternary operator.

3-Way Multiplexer

Mux3 is a module which takes three inputs and selects between them.

mux3.vir
pub mod Mux3 {
    incoming a : Word[8]
    incoming b : Word[8]
    incoming c : Word[8]

    incoming select_a : Bit
    incoming select_b : Bit
    incoming select_c : Bit

    outgoing out : Word[8] dependson a, b, c, select_a, select_b, select_c {
        it := if {
            when select_a => a
            when select_b => b
            when select_c => c
            else => 0
        }
    }
}

Mux3 is a 3-way multiplexer. It takes three data inputs and three select lines. The output out takes on the value of the first of a b or c whose corresponding select line is asserted (or 0 if none are).

This is what we would call priority mux. Multiple values can be selected at once, but the final result is decided by which has priority. (Here, a has priority over b which has priority over c).

If Blocks

In Virdant, we use if blocks to represent priority muxes.

it := if {
    when select_a => a
    when select_b => b
    when select_c => c
    else => 0
}

Inside an if block is a number of when clauses.

The syntax when select_a => a means: “if select_a is true, then the result is a”

The condition for each when clause is tested in order until one is found. Otherwise, the value of the if block is given by the else clause.

4-Way Multiplexer

For our last example, we have Mux4, a 4-way multiplexer which takes four inputs and selects between them.

mux4.vir
pub mod Mux4 {
    incoming a : Word[8]
    incoming b : Word[8]
    incoming c : Word[8]
    incoming d : Word[8]

    incoming select : Word[2]

    outgoing out : Word[8] dependson a, b, c, d, select {
        it := match select {
            with 0 => a
            with 1 => b
            with 2 => c
            with 3 => d
        }
    }
}

In Mux3, each input had its own select line. But Mux4 encodes which value to select based on a 2-bit.

Match Blocks

In this example, we chose to make use of final conditional syntax in Virdant: the match block:

it := match select {
    with 0 => a
    with 1 => b
    with 2 => c
    with 3 => d
}

The syntax match select looks at select and then compares it for equality against each with arm. When it finds one that matches, the result of the match block becomes the value to the right of the =>.

One nice thing about match is that it prevents you from forgetting to cover every value. If you forget an arm, Virdant will help you out with an error.

match statements also produce efficient hardware, where the arms are tested in parallel.

Conditionals in Virdant

And so we have seen three different ways to express conditional logic in Virdant:

  • the mux primitive, for simple two-way selections

  • if blocks, for priority chains of conditions

  • match blocks, for comparing a value against all its possible values in parallel

These each give you a different way to express yourself in Virdant. Some situations call for one kind of expression, and some call for another.

Ultimately, it’s up to you to decide which you like best for a given situation. Which form will you reach for first?