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)| Part | Meaning |
|---|---|
a > b | The selector; must be a hardware Bool |
a | The result when the selector is 1 |
b | The result when the selector is 0 |
| Result type | The common type of both branches, here UInt |
| Result width | The 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 x | True conditions | level |
|---|---|---|
| 5 | none | 0, the default |
| 50 | x > 10 | 1 |
| 150 | both | 2, 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)
))| Part | Meaning |
|---|---|
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 entry | op = 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 multiplexerval 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| Part | Meaning |
|---|---|
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?
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 ival 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 selectors | y |
|---|---|---|
0100 | 2 | 33 |
0110 | 1 and 2 | 22, the lowest index |
0000 | none | 44, 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 ival y = Mux1H(grant, values) // grant: UInt(4.W), one-hotgrant | True selectors | y |
|---|---|---|
0100 | 2 | 33 |
0000 | none | undefined by the contract; this UInt implementation returns 0 |
0110 | 1 and 2 | undefined 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
| Function | Selector | Several true | None true |
|---|---|---|---|
Mux(c, a, b) | one Bool | not applicable | b |
MuxCase(default, pairs) | independent conditions | first listed wins | default |
MuxLookup(key, default)(pairs) | one key, constant table | cannot happen with distinct keys | default |
PriorityMux(sels, values) | priority-ordered Bools | lowest index wins | last value |
Mux1H(sels, values) | one-hot Bools | undefined | undefined; 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?
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?
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 fourBools during elaboration;hit(i)isio.sel === i.U, so exactly one of them is true for anysel.- Five outputs make the same choice in five ways.
| Output | Construction |
|---|---|
nested | Three hand-nested Mux calls; input 3 is the final false branch |
muxCase | MuxCase with three conditions and input 3 as the default |
muxLookup | MuxLookup on sel with keys 0 to 2 and input 3 as the default |
oneHot | Mux1H over all four hit bits |
priority | PriorityMux over all four hit bits |
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.
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.
- Is there any
selfor which two of the five columns disagree? - For
sel=3, which outputs reach 44 through a default or final false branch, and which through a true selector?
Check the result.
| Output line | What it shows |
|---|---|
sel=0 to sel=2 | All five columns select 11, 22, 33 in turn |
sel=3 | All 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.
- Append a duplicate key
1.U -> 99.Uto theMuxLookuptable. Thesel=1line shows 99 in thelookupcolumn 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. - Replace the last input of the nested
Muxwith-1.S. Elaboration stops withcan't create Mux with heterogeneous types class chisel3.UInt and class chisel3.SInt. - Write the first
MuxCasecondition asio.sel === 0.U -> values(0). The compiler reportstype mismatch; found: (chisel3.UInt, chisel3.UInt), required: chisel3.UIntat 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:
| Output | Function | Rule |
|---|---|---|
lowFirst | PriorityMux | The lowest-numbered request wins |
highFirst | MuxCase listing requester 3 down to 0, default 0 | The highest-numbered request wins |
oneHot | Mux1H | Requires exactly one request |
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.
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.
req | Requesters | low | high | onehot |
|---|---|---|---|---|
0100 | 2 | ? | ? | ? |
0110 | 1 and 2 | ? | ? | ? |
1010 | 1 and 3 | ? | ? | ? |
0000 | none | ? | ? | ? |
Check the result.
| Output line | What it shows |
|---|---|
req=0100 | 33 in all three columns: with one request every form is correct |
req=0110 | low=22 and high=33 follow each list's order; onehot=55 is 22 OR 33, a value no requester owns |
req=1010 | low=22, high=44; onehot=62 is 22 OR 44 |
req=0000 | low=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.
- Reverse the
MuxCaselist so it starts at requester 0.highnow equalslowon every line with a request. Only0000still differs, 0 against 44, because the two functions handle "none" differently. - Feed
Mux1Ha vector that is one-hot by construction:Mux1H(UIntToOH(PriorityEncoder(io.req)), values).PriorityEncoderreturns the index of the lowest set bit andUIntToOHturns it back into a single bit. Theonehotcolumn then equalslowon every line, including 44 for0000, becausePriorityEncoderof 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?
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 bThe 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
| Form | Use it for | Rule to remember |
|---|---|---|
Mux(c, a, b) | Two inputs | Both branches the same Data kind; width is the wider branch |
MuxCase(d, pairs) | Conditions with a deliberate order | First true condition wins; d when none |
MuxLookup(k, d)(pairs) | A sparse table keyed by a UInt or enum | Second argument list; duplicate keys: the last one wins |
VecInit(...)(i) | A dense table indexed from 0 | A hardware index builds a multiplexer; a Scala Seq cannot take one |
PriorityMux(sels, values) | A priority-ordered request vector | Lowest index wins; no default, falls to the last value |
Mux1H(sels, values) | A selector that is one-hot by construction | AND-OR; final size and depth depend on synthesis; undefined if the promise breaks |
8. Check your understanding
-
Why do all five columns of the
mux-familyexample agree for everysel?Show answer
Each
hit(i)issel === i, so exactly onehitbit is true for anysel. Priority never comes into play, and the one-hot assumption ofMux1Halways holds. -
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. -
Where does 62 come from in the
req=1010line?Show answer
Requesters 1 and 3 are both active, so
Mux1HORs 22 (010110) and 44 (101100), giving111110, which is 62. -
What changes in the hardware, not just the output, when you replace
PriorityMuxwithMux1H?Show answer
PriorityMuxbuilds a chain of two-way multiplexers, N - 1 levels deep for N inputs before synthesis restructures it.Mux1Hbuilds AND gates feeding one OR, which can be shallower than a priority chain; its result is guaranteed only when exactly one selector is true. -
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 pair0.U -> afirst and then tries to compareselwith a pair. Write(sel === 0.U) -> a.