Skip to content
code0.HDL

Selecting with MuxCase, MuxLookup, PriorityMux and Mux1H

Choose among many inputs with MuxCase, MuxLookup, PriorityMux and Mux1H, and decide which one a selection calls for by how its selectors behave.

In this chapter

  • Write the same selection as a nested Mux, a MuxCase and a MuxLookup
  • Choose between PriorityMux and Mux1H from how the selectors can overlap
  • Predict what Mux1H produces when more than one selector is high
  • Say what each form outputs when no selector is true, and pick the one whose default a specification needs
  • Index a VecInit table with a hardware index, and tell a static index from one that builds a multiplexer
Concepts in this chapter
  • Selecting with MuxCase, MuxLookup, PriorityMux and Mux1H
Prerequisite concepts

Almost every datapath ends in a choice: which register feeds the ALU, which requester gets the bus, which table entry an index names. Chisel has one two-way primitive, Mux, and a family of chisel3.util functions that build wider selectors from it. They differ less in what they compute than in what they assume about the selector: that one condition beats another, that the selector is an index, or that exactly one selector is true. This chapter follows the Muxes and Input Selection explanation of the official Chisel documentation and makes those assumptions visible in simulation.

Before you start. You need === and the comparison operators from Bool, UInt, and operators and Mux(cond, a, b) from Mux, when, and Wire. The chapter also uses Scala sequences of pairs, written Seq(key -> value, ...); a -> b is simply the pair (a, b). The one hardware table type it needs, Vec, is introduced in section 2.3 just far enough to index it.

Every function in this chapter runs during elaboration. A call executes once while the module is built and leaves behind a tree of ordinary two-way multiplexers or AND-OR gates; there is no "MuxCase hardware" in the circuit. The priority and one-hot rules are properties of that generated tree, and you observe them in simulation.

1. The two-way Mux

1.1 Form and meaning

Mux chooses between two values with one Bool. It is an expression: it returns a value that you assign or pass on.

Syntax

Mux(condition, valueIfTrue, valueIfFalse)
val max = Mux(a > b, a, b)
PartMeaning
a > bThe selector; must be a hardware Bool
aThe result when the selector is 1
bThe result when the selector is 0
Result typeThe common type of both branches, here UInt
Result widthThe wider of the two branches

For example, Mux(c, 3.U(2.W), 200.U(8.W)) is 8 bits wide and returns 3 or 200.

Caution. Both branches must be the same kind of Data. Mux(c, 1.U, -1.S) mixes UInt and SInt and stops elaboration with java.lang.AssertionError: can't create Mux with heterogeneous types class chisel3.UInt and class chisel3.SInt. Convert one side explicitly, for example with asSInt. The aggregates of Bundles and Vecs work as branches too, as long as both sides have the same type.

1.2 Nesting Mux for more inputs

Wider choices can be built by nesting: each inner Mux is the false branch of the outer one.

val y = Mux(s0, a, Mux(s1, b, Mux(s2, c, d)))

s0 is checked first, so a wins whenever s0 is 1, even if s1 is also 1. That "first check wins" order is priority, and the utilities below build exactly this kind of chain from a list. Hand-nested Mux calls become hard to read past three levels, which is where the utilities help.

2. Selecting from a list of conditions or keys

2.1 MuxCase: the first true condition wins

MuxCase takes a default and a list of condition -> value pairs. It returns the value of the first true condition in the list, or the default when none is true.

Syntax

MuxCase(default, Seq(
  condition1 -> value1,
  condition2 -> value2
))
val level = MuxCase(0.U, Seq(
  (x > 100.U) -> 2.U,
  (x > 10.U)  -> 1.U
))
Input xTrue conditionslevel
5none0, the default
50x > 101
150both2, because x > 100 is listed first

The list order is priority. If you swapped the two lines, x = 150 would return 1.

Caution. Write parentheses around comparisons inside a pair. In Scala, -> binds tighter than >, ===, and the other comparison operators, so x === 0.U -> a means x === (0.U -> a) and does not compile.

2.2 MuxLookup: compare one key with constants

MuxLookup compares one signal, the key, with a list of constant keys. Since the keys are distinct values of the same signal, at most one of them can match, so priority never matters for correct tables.

Syntax

MuxLookup(key, default)(Seq(
  key1 -> value1,
  key2 -> value2
))
val result = MuxLookup(op, 0.U)(Seq(
  0.U -> (a + b),
  1.U -> (a - b),
  2.U -> (a & b)
))
PartMeaning
MuxLookup(op, 0.U)First argument list: the key and the default
(Seq(...))Second argument list: the table of key -> value pairs
0.U -> (a + b)When op === 0.U, the result is a + b
Key with no entryop = 3 returns the default, 0

Once you have met enumerations in ChiselEnum and state machines, the key can also be an enum value, with enum values as table keys: MuxLookup(color, 0.U)(Seq(Color.red -> 10.U, Color.green -> 20.U)) returns 0 for Color.blue.

Caution. In Chisel 6 the table goes in a second argument list. The older three-argument form MuxLookup(op, 0.U, Seq(...)) no longer compiles.

2.3 Vec indexing as a lookup table

When the keys are exactly 0, 1, 2, ... and every index has an entry, indexing a table by the key says the same thing more directly. The table cannot be a Scala Seq, though. A Seq such as values = Seq(11.U, 22.U, 33.U, 44.U) exists only while the generator runs: Scala resolves values(2) by handing back the third element, and no hardware is built for the choice. Scala needs an Int for that, so values(io.sel) does not compile: type mismatch; found: chisel3.UInt, required: Int.

A Vec is the hardware counterpart: a fixed number of elements of one Chisel type, which the circuit itself can index. VecInit builds one from hardware values.

Syntax

VecInit(value0, value1, value2, value3)   // a Vec wire driven by the values
table(i)      // i is a Scala Int: one fixed element
table(sel)    // sel is a hardware UInt: a multiplexer
val table = VecInit(11.U, 22.U, 33.U, 44.U)
val y = table(io.sel)   // sel = 2 gives 33
val z = table(2)        // always 33
PartMeaning
VecInit(11.U, ...)A wire with four elements; the element type takes the widest value, here 6 bits
table(2)Static index: decided during elaboration, a plain connection to element 2
table(io.sel)Dynamic index: decided while the circuit runs, so a multiplexer is built

The emitted CHIRRTL shows the difference: table[2] names one element, while table[io.sel] is an access that later lowering turns into a multiplexer over all four elements.

Check

values = Seq(1.U, 2.U, 3.U) and table = VecInit(1.U, 2.U, 3.U). Which line builds a multiplexer in the circuit?

Your answer

Use MuxLookup when the table is sparse or needs a default, and Vec indexing when the index covers a dense range. Vec ports, Vec(n, type), and the full rules for Scala and hardware indices are in Bundles and Vecs.

3. Selecting with a vector of selectors

3.1 PriorityMux: the lowest index wins

PriorityMux takes a sequence of Bool selectors and a sequence of values. It returns the value of the lowest-numbered true selector.

Syntax

PriorityMux(selectors, values)          // Seq[Bool] and Seq of values
PriorityMux(Seq(sel0 -> v0, sel1 -> v1)) // the same, as pairs
PriorityMux(selUInt, values)            // bit i of a UInt is selector i
val values = Seq(11.U, 22.U, 33.U, 44.U)
val y = PriorityMux(req, values)   // req: UInt(4.W)
req (bit 3 to bit 0)True selectorsy
0100233
01101 and 222, the lowest index
0000none44, the last value

Caution. PriorityMux has no default argument. When no selector is true, the chain falls through to the last value, so "nobody asked" looks the same as "the last requester asked".

3.2 Mux1H: exactly one selector is true

Mux1H assumes the selectors are one-hot: exactly one of them is 1. With that promise it needs no chain at all. It ANDs every value with its selector and ORs the results together.

Syntax

Mux1H(selectors, values)           // Seq[Bool] and Seq of values
Mux1H(Seq(sel0 -> v0, sel1 -> v1)) // the same, as pairs
Mux1H(selUInt, values)             // bit i of a UInt is selector i
val y = Mux1H(grant, values)   // grant: UInt(4.W), one-hot
grantTrue selectorsy
0100233
0000noneundefined by the contract; this UInt implementation returns 0
01101 and 2undefined by the contract; this version returns 22 OR 33 = 55

The AND-OR structure can reduce selection depth; final area and timing depend on the payload and synthesis. The price is that a broken promise produces a value that belongs to no input, and neither Chisel nor the simulator warns you.

3.3 The family at a glance

FunctionSelectorSeveral trueNone true
Mux(c, a, b)one Boolnot applicableb
MuxCase(default, pairs)independent conditionsfirst listed winsdefault
MuxLookup(key, default)(pairs)one key, constant tablecannot happen with distinct keysdefault
PriorityMux(sels, values)priority-ordered Boolslowest index winslast value
Mux1H(sels, values)one-hot Boolsundefinedundefined; this version gives 0

Check

A state register is kept one-hot: exactly one of its four bits is 1 in every cycle, and each bit selects one output value. Which form fits best?

Your answer

Check

Four requesters share a bus. Requester 0 is the most urgent, and when nobody requests the output must be 0. Which form says that in one call?

Your answer

4. Example: five ways to say the same thing

Select.scala selects one of four constant inputs, 11, 22, 33, and 44, with a 2-bit sel. Read it in this order:

  • Seq.tabulate(4) builds four Bools during elaboration; hit(i) is io.sel === i.U, so exactly one of them is true for any sel.
  • Five outputs make the same choice in five ways.
OutputConstruction
nestedThree hand-nested Mux calls; input 3 is the final false branch
muxCaseMuxCase with three conditions and input 3 as the default
muxLookupMuxLookup on sel with keys 0 to 2 and input 3 as the default
oneHotMux1H over all four hit bits
priorityPriorityMux over all four hit bits
Select.scala
import chisel3._
import chisel3.util._

class Select extends Module {
  val io = IO(new Bundle {
    val sel       = Input(UInt(2.W))
    val nested    = Output(UInt(8.W))
    val muxCase   = Output(UInt(8.W))
    val muxLookup = Output(UInt(8.W))
    val oneHot    = Output(UInt(8.W))
    val priority  = Output(UInt(8.W))
  })
  val values = Seq(11.U, 22.U, 33.U, 44.U)
  // One Bool per input; exactly one is true for any sel.
  val hit = Seq.tabulate(4)(i => io.sel === i.U)

  io.nested    := Mux(hit(0), values(0), Mux(hit(1), values(1), Mux(hit(2), values(2), values(3))))
  io.muxCase   := MuxCase(values(3), Seq(hit(0) -> values(0), hit(1) -> values(1), hit(2) -> values(2)))
  // MuxLookup compares sel with each key; a sel with no key takes the default.
  io.muxLookup := MuxLookup(io.sel, values(3))(Seq(0.U -> values(0), 1.U -> values(1), 2.U -> values(2)))
  io.oneHot    := Mux1H(hit, values)
  io.priority  := PriorityMux(hit, values)
}

Tb.scala pokes every sel value and prints the five outputs side by side.

Tb.scala
import chisel3._
import chiseltest._
import chiseltest.RawTester.test

object Main extends App {
  test(new Select) { dut =>
    for (sel <- 0 until 4) {
      dut.io.sel.poke(sel.U)
      val outs = Seq("nested" -> dut.io.nested, "case" -> dut.io.muxCase, "lookup" -> dut.io.muxLookup,
        "onehot" -> dut.io.oneHot, "priority" -> dut.io.priority)
      println(s"sel=$sel " + outs.map { case (name, port) => s"$name=${port.peek().litValue}" }.mkString(" "))
    }
  }
}

Practice this concept

Use this concept in a short Chisel problem as your next step.

Predict before you run.

  1. Is there any sel for which two of the five columns disagree?
  2. For sel=3, which outputs reach 44 through a default or final false branch, and which through a true selector?

Check the result.

Output lineWhat it shows
sel=0 to sel=2All five columns select 11, 22, 33 in turn
sel=3All five show 44. nested, case, and lookup get there through their default; onehot and priority through hit(3)

The columns cannot disagree. The hit bits are exclusive by construction, so priority never matters and the one-hot promise always holds. The five forms still generate different hardware; the difference shows only when the assumptions break, which the next example does on purpose.

Try it yourself.

  1. Append a duplicate key 1.U -> 99.U to the MuxLookup table. The sel=1 line shows 99 in the lookup column and 22 everywhere else. The table is built as a chain in which the last entry ends up outermost, so a duplicate key silently overrides the earlier one.
  2. Replace the last input of the nested Mux with -1.S. Elaboration stops with can't create Mux with heterogeneous types class chisel3.UInt and class chisel3.SInt.
  3. Write the first MuxCase condition as io.sel === 0.U -> values(0). The compiler reports type mismatch; found: (chisel3.UInt, chisel3.UInt), required: chisel3.UInt at the ===: the pair was built first and then handed to ===.

5. Example: when the selectors overlap

Pick.scala splits a 4-bit request vector req into four Bools with asBools; bit 0 is requester 0. Requesters 0 to 3 own the values 11 to 44. Three outputs choose among them:

OutputFunctionRule
lowFirstPriorityMuxThe lowest-numbered request wins
highFirstMuxCase listing requester 3 down to 0, default 0The highest-numbered request wins
oneHotMux1HRequires exactly one request
Pick.scala
import chisel3._
import chisel3.util._

class Pick extends Module {
  val io = IO(new Bundle {
    val req       = Input(UInt(4.W))
    val lowFirst  = Output(UInt(8.W))
    val highFirst = Output(UInt(8.W))
    val oneHot    = Output(UInt(8.W))
  })
  val values = Seq(11.U, 22.U, 33.U, 44.U)
  val req    = io.req.asBools

  // PriorityMux: the lowest-numbered true selector wins.
  io.lowFirst  := PriorityMux(req, values)
  // MuxCase: the first listed true condition wins, so list order is priority.
  io.highFirst := MuxCase(0.U, Seq(req(3) -> values(3), req(2) -> values(2), req(1) -> values(1), req(0) -> values(0)))
  // Mux1H requires exactly one true selector; zero or multiple selections are undefined.
  io.oneHot    := Mux1H(req, values)
}

Tb.scala drives four request patterns and prints each most significant bit first: one request, two adjacent requests, two separated requests, and no request.

Tb.scala
import chisel3._
import chiseltest._
import chiseltest.RawTester.test

object Main extends App {
  test(new Pick) { dut =>
    for (req <- Seq(0x4, 0x6, 0xa, 0x0)) {
      dut.io.req.poke(req.U)
      val req4 = req.toBinaryString.reverse.padTo(4, '0').reverse
      println(s"req=$req4 low=${dut.io.lowFirst.peek().litValue} high=${dut.io.highFirst.peek().litValue} onehot=${dut.io.oneHot.peek().litValue}")
    }
  }
}

Practice this concept

Use this concept in a short Chisel problem as your next step.

Predict before you run. Fill in this table, then run. For oneHot with two requests, OR the two values in binary: 22 is 010110, 33 is 100001, 44 is 101100.

reqRequesterslowhighonehot
01002???
01101 and 2???
10101 and 3???
0000none???

Check the result.

Output lineWhat it shows
req=010033 in all three columns: with one request every form is correct
req=0110low=22 and high=33 follow each list's order; onehot=55 is 22 OR 33, a value no requester owns
req=1010low=22, high=44; onehot=62 is 22 OR 44
req=0000low=44 from the fall-through, high=0 from the explicit default, onehot=0 from ANDing everything with 0

The onehot values are exactly the AND-OR structure from section 3.2. Treat them as an illustration, not a behavior to rely on: the contract calls the result undefined, and another input type or Chisel version may generate a different structure.

Try it yourself.

  1. Reverse the MuxCase list so it starts at requester 0. high now equals low on every line with a request. Only 0000 still differs, 0 against 44, because the two functions handle "none" differently.
  2. Feed Mux1H a vector that is one-hot by construction: Mux1H(UIntToOH(PriorityEncoder(io.req)), values). PriorityEncoder returns the index of the lowest set bit and UIntToOH turns it back into a single bit. The onehot column then equals low on every line, including 44 for 0000, because PriorityEncoder of zero also falls through to the last index.

Check

Change values in Pick.scala to Seq(5.U, 6.U, 9.U, 12.U) and drive req = 0101. What do low and onehot show?

Your answer

6. Common mistakes

6.1 Branches of different types

// Wrong: UInt and SInt branches
val y = Mux(c, a, -1.S)

// Right: convert so both branches are SInt
val y = Mux(c, a.asSInt, -1.S)

Mux returns one value whose type must describe both branches. Elaboration stops with can't create Mux with heterogeneous types instead of guessing a conversion.

6.2 A comparison without parentheses inside a pair

// Wrong: parsed as sel === (0.U -> a)
MuxCase(d, Seq(sel === 0.U -> a))

// Right
MuxCase(d, Seq((sel === 0.U) -> a))

-> binds tighter than ===. The compiler reports type mismatch; found: (chisel3.UInt, chisel3.UInt), required: chisel3.UInt, pointing at the ===.

6.3 The old three-argument MuxLookup

// Wrong: older Chisel style
MuxLookup(op, 0.U, Seq(0.U -> a, 1.U -> b))

// Right: Chisel 6 takes the table in a second argument list
MuxLookup(op, 0.U)(Seq(0.U -> a, 1.U -> b))

The compiler lists the two apply overloads, one for UInt keys and one for EnumType keys, and reports that neither cannot be applied to three arguments.

6.4 Mux1H on selectors that can overlap

// Wrong: two requests at once produce an OR of two values
val data = Mux1H(io.req, values)

// Right: let a priority form choose, or make the vector one-hot first
val data = PriorityMux(io.req, values)

Use Mux1H only when exclusivity is guaranteed by construction, such as a decoded index, a one-hot state register, or an arbiter's grant vector.

6.5 A Scala Boolean as the selector

// Wrong: true is a Scala Boolean, decided before any hardware exists
val y = Mux(true, a, b)

// Right (hardware choice): select with a hardware Bool
val y = Mux(io.useA, a, b)

// Right (elaboration-time choice): use a Scala if
val y = if (useA) a else b

The wrong line fails with type mismatch; found: Boolean(true), required: chisel3.Bool. Mux builds a multiplexer that switches while the circuit runs. A choice that is already fixed while the generator runs is a Scala if, and it produces no multiplexer at all. You met that pattern in generator parameters, and Polymorphism and parameterization builds whole configurations on it.

7. Summary

FormUse it forRule to remember
Mux(c, a, b)Two inputsBoth branches the same Data kind; width is the wider branch
MuxCase(d, pairs)Conditions with a deliberate orderFirst true condition wins; d when none
MuxLookup(k, d)(pairs)A sparse table keyed by a UInt or enumSecond argument list; duplicate keys: the last one wins
VecInit(...)(i)A dense table indexed from 0A hardware index builds a multiplexer; a Scala Seq cannot take one
PriorityMux(sels, values)A priority-ordered request vectorLowest index wins; no default, falls to the last value
Mux1H(sels, values)A selector that is one-hot by constructionAND-OR; final size and depth depend on synthesis; undefined if the promise breaks

8. Check your understanding

  1. Why do all five columns of the mux-family example agree for every sel?

    Show answer

    Each hit(i) is sel === i, so exactly one hit bit is true for any sel. Priority never comes into play, and the one-hot assumption of Mux1H always holds.

  2. Which function returned 44 for an empty request, and why?

    Show answer

    PriorityMux. It has no default argument; when no selector is true, its chain falls through to the last value, 44.

  3. Where does 62 come from in the req=1010 line?

    Show answer

    Requesters 1 and 3 are both active, so Mux1H ORs 22 (010110) and 44 (101100), giving 111110, which is 62.

  4. What changes in the hardware, not just the output, when you replace PriorityMux with Mux1H?

    Show answer

    PriorityMux builds a chain of two-way multiplexers, N - 1 levels deep for N inputs before synthesis restructures it. Mux1H builds AND gates feeding one OR, which can be shallower than a priority chain; its result is guaranteed only when exactly one selector is true.

  5. MuxCase(0.U, Seq(sel === 0.U -> a)) does not compile. Why, and what is the fix?

    Show answer

    -> binds tighter than ===, so Scala builds the pair 0.U -> a first and then tries to compare sel with a pair. Write (sel === 0.U) -> a.

Checkpoint

Run the example and compare it with your prediction

Sources

Licences and notices for the sources this site follows