1. Mux is a multiplexer written as an expression
Mux(condition, valueIfTrue, valueIfFalse) builds the two-way multiplexer from the fundamentals. The
condition must be a hardware Bool, and the result is as wide as the wider of the two values. It is an
expression, so it can sit on the right of := or inside a larger expression. Mux fits a single
choice that produces one value.
Syntax
Mux(condition, valueIfTrue, valueIfFalse)val larger = Mux(io.a > io.b, io.a, io.b)
val small = Mux(io.sel, io.x, 1.U(4.W)) // 8-bit x and 4-bit literal: 8 bitsThis is the place to repeat the rule from
the first chapter.
A Scala if needs a Scala Boolean that is known while the circuit is being built; it chooses which
hardware to build and leaves no gate behind. A Mux or a when takes a hardware Bool and builds a
circuit that chooses while the circuit runs. if (io.sel) does not compile, because io.sel has no
value during elaboration.
| Written | Decided | Leaves in the circuit |
|---|---|---|
if (scalaBoolean) ... else ... | elaboration | only the branch that was chosen |
Mux(bool, a, b) | simulation, every moment | a multiplexer |
when (bool) { ... } | simulation, every moment | a multiplexer in front of each sink connected inside |
Wider choices — a list of conditions, a lookup table, a vector of selectors — are built by the
chisel3.util functions in
Selecting with MuxCase, MuxLookup, PriorityMux and Mux1H.
2. Wire: a named node you drive with :=
A Wire is a named node that you drive with :=. It is useful when a value is built up by several
connections rather than one expression.
Syntax
val name = Wire(chiselType)
val name = WireDefault(initialValue)val level = Wire(UInt(2.W)) // no driver yet
val alarm = WireDefault(false.B) // driven with false unless connected laterWire(UInt(2.W)) only creates the node. Something must drive it on every path, as section 5
explains. WireDefault(v) creates the node and connects v to it in one step.
3. when, .elsewhen, and .otherwise
A when block attaches a condition to every connection inside it: every := in the block becomes
conditional. .elsewhen adds a branch tested only when the earlier conditions were false, and
.otherwise covers the rest.
Syntax
when (condition1) {
// connections used when condition1 is true
} .elsewhen (condition2) {
// used when condition1 is false and condition2 is true
} .otherwise {
// used when no condition above is true
}val speed = Wire(UInt(2.W))
when (io.temp > 80.U) {
speed := 3.U
} .elsewhen (io.temp > 50.U) {
speed := 1.U
} .otherwise {
speed := 0.U
}| Part | Meaning |
|---|---|
when (c) { ... } | Connections inside take effect only while c is true |
.elsewhen (c2) { ... } | Checked only when every earlier condition in the chain is false |
.otherwise { ... } | Covers every remaining case |
Wire(UInt(2.W)) creates the node with no driver; the when chain is what drives it. A chain picks
only its first true condition. For temp = 90, both > 80 and > 50 hold, but speed is 3.
4. The last connection wins
When two connections drive the same sink and both conditions hold, the one written later in the source takes effect. Two rules therefore decide what a sink carries when more than one connection could apply.
| Rule | Meaning |
|---|---|
| A chain uses its first true condition | .elsewhen branches are skipped once one holds |
| Between separate statements, the last connection wins | A connection written later overrides an earlier one whenever both hold |
An unconditional connection followed by a conditional one is how you write a default.
val bigger = WireDefault(0.U(8.W)) // default: 0
when (io.x > 100.U) {
bigger := io.x // later, so it wins while x > 100
}For x = 197, bigger is 197; for x = 3 or x = 40, the when does not hold and bigger stays 0.
Priority comes from position in the source, not from how important a condition looks.
5. Every path must be driven
Every output and wire needs a value for every combination of conditions. If some path leaves a sink
without a connection, Chisel does not invent a value; the check that runs before simulation stops the
run with RefNotInitializedException and reports the sink as not fully initialized. The message
shows the multiplexer chain with VOID where the connection is missing.
| Way to drive every path | Example |
|---|---|
Connect a default before the when | io.out := 0.U |
Cover the rest with .otherwise | .otherwise { io.out := 0.U } |
| Declare with a default | WireDefault(0.U(8.W)) |
| State that the value does not matter | io.out := DontCare |
Caution. DontCare leaves the value unspecified. Use it only when nothing reads the signal in that
state.
6. Example: Wire, when, and last connection wins
Grade.scala sorts an 8-bit level into four grades. A four-way when chain drives the grade wire,
and then a separate when (io.force) drives it again. alarm starts as WireDefault(false.B), and a
single when raises it for grade 3.
import chisel3._
class Grade extends Module {
val io = IO(new Bundle {
val level = Input(UInt(8.W))
val force = Input(Bool())
val grade = Output(UInt(2.W))
val alarm = Output(Bool())
})
val grade = Wire(UInt(2.W))
// The first true condition in the chain chooses the connection.
when (io.level > 200.U) {
grade := 3.U
} .elsewhen (io.level > 100.U) {
grade := 2.U
} .elsewhen (io.level > 0.U) {
grade := 1.U
} .otherwise {
grade := 0.U
}
// A later connection wins over the chain whenever force is high.
when (io.force) {
grade := 0.U
}
io.grade := grade
// WireDefault drives the wire on every path that has no other connection.
val alarm = WireDefault(false.B)
when (grade === 3.U) {
alarm := true.B
}
io.alarm := alarm
}import chisel3._
import chiseltest._
import chiseltest.RawTester.test
object Main extends App {
test(new Grade) { dut =>
for ((level, force) <- Seq((0, false), (50, false), (150, false), (250, false), (250, true))) {
dut.io.level.poke(level.U)
dut.io.force.poke(force.B)
val grade = dut.io.grade.peek().litValue
val alarm = dut.io.alarm.peek().litValue
println(s"SIMULATE level=$level force=$force grade=$grade alarm=$alarm")
}
}
}Practice this concept
Use this concept in a short Chisel problem as your next step.
Predict before you run. What grade does each of level = 0, 50, 150, and 250 get with force
low? In the last row, level = 250 and force is high: two blocks connect to grade. Which one wins,
and does alarm follow the chain's choice or the final grade?
Check
For level = 250 and force = true, what are grade and alarm?
Check the result. Press Run and trace the last row step by step.
| Step | Condition | Candidate for grade |
|---|---|---|
Chain, level > 200 | True | 3 |
.elsewhen branches | Skipped; a chain uses only its first true condition | 3 |
Separate when (io.force) | True, and written later | 0 |
The later connection wins, so grade is 0. alarm compares the final grade, not the chain's
intermediate choice, so it stays at its default, 0. The first four rows show grades 0, 1, 2, and 3 with
the alarm raised only on the fourth. The fourth and fifth rows have the same level: first grade 3 with
the alarm raised, then grade 0 with the alarm low.
Try it yourself. First move the whole when (io.force) { ... } block above the chain and run
again.
Check
With the force block written before the chain, what does the last row show?
The chain always connects something, because .otherwise covers every remaining case, so a connection
written after the force block always overrides it. Then try the opposite edit: restore the order and
delete the .otherwise branch.
Check
Without .otherwise, what happens on the first row, where level = 0 and force is low?
The run fails before simulation with RefNotInitializedException and Reference grade is not fully
initialized, and the message prints the multiplexer chain with VOID for the missing case. Section 7
studies the same error on a port.
7. Failure: an output not driven on every path
This example fails on purpose. Gate drives io.out only inside when (io.en), so nothing drives it
while en is low. Before you press Run, decide which stage reports the error.
Check
Which stage reports the error in Gate.scala?
import chisel3._
import chiseltest._
import chiseltest.RawTester.test
class Gate extends Module {
val io = IO(new Bundle {
val en = Input(Bool())
val in = Input(UInt(8.W))
val out = Output(UInt(8.W))
})
// Nothing drives out while en is low, so the circuit is incomplete.
when (io.en) {
io.out := io.in
}
}
object Main extends App {
test(new Gate) { dut =>
dut.io.en.poke(true.B)
}
}| Item | Detail |
|---|---|
| Wrong code | No connection to io.out on the path where io.en is 0 |
| Failing stage | The initialization check before simulation. Elaboration itself succeeds, and the generated CHIRRTL holds a lone when io.en connection |
| Error message | RefNotInitializedException with Reference io is not fully initialized. The last line shows io.out driven by a mux of io.en, io.in, and VOID |
| Fix | Add io.out := 0.U before the when as a default, or io.out := DontCare if no one reads the output while en is low |
Read the message from the bottom up. VOID marks the path with no connection, and the first line names
the module and the aggregate io that holds the bad field. The error appears when test(...) starts,
not when new Gate is constructed.
8. Apply it: an override that fails to override
The graded Practice problem below is a fan controller: a temperature chain picks a speed, and a fault
input must force speed 3 whatever the temperature. The starter's fault block has no effect. The first
edit in section 6 is the whole diagnosis; keep in mind that the fix must also hold for a hot reading
with the fault high, a row the public run does not print.
9. Common mistakes
9.1 A when with no default
// Wrong: nothing drives out while en is low
when (io.en) { io.out := io.in }
// Right: connect a default first
io.out := 0.U
when (io.en) { io.out := io.in }The check before simulation reports not fully initialized with VOID in the mux. Every sink needs
a driver on every path.
9.2 Writing an override before the chain it should override
// Wrong: the chain is written later and always wins
when (io.force) { grade := 0.U }
when (io.level > 200.U) { grade := 3.U } .otherwise { grade := 1.U }
// Right: write the override last
when (io.level > 200.U) { grade := 3.U } .otherwise { grade := 1.U }
when (io.force) { grade := 0.U }The last connection wins. An override must come after every connection it is meant to beat.
10. Summary
| Topic | Key point |
|---|---|
Mux(c, a, b) | A multiplexer as an expression; the condition is a hardware Bool, the width is the wider value |
Scala if | Chooses what to build during elaboration and leaves no gate; it cannot read a Bool port |
Wire(t), WireDefault(v) | A named node driven by :=; with a default on every path |
when / .elsewhen / .otherwise | Conditional connections; a chain uses its first true condition |
| Priority | Between separate statements the last true connection wins; an override comes last |
| Completeness | Every sink needs a driver on every path: default, .otherwise, WireDefault, or DontCare |
| Undriven path | Rejected before simulation as not fully initialized, with VOID marking the missing case |
11. Check your understanding
-
A wire is connected to 1 unconditionally and then to 2 inside
when (c). What does it carry whencis false, and whencis true?Show answer
1 when
cis false, because only the unconditional connection applies. 2 whencis true, because the later connection wins. -
Deleting
.otherwiseinGrademakes the run fail, butalarmhas no.otherwiseat all. Why isalarmfine?Show answer
alarmis declared withWireDefault(false.B), which drives it on every path that has no other connection.gradeis a plainWire, so without.otherwiseone path leaves it undriven. -
io.xis an 8-bitUInt. How wide isMux(io.sel, io.x, 1.U(4.W)), and what mustio.selbe?Show answer
8 bits: the result takes the wider of the two values, and the 4-bit literal is zero-extended when it is selected.
io.selmust be a hardwareBool; a ScalaBooleanwould not compile. -
Why does the undriven output in
Gatepass Scala compilation and elaboration, and which stage reports it?Show answer
Each line is legal on its own: a
whenwith one connection inside is valid Chisel, so compilation and elaboration succeed and the circuit graph is built. Only the initialization check that chiseltest runs before simulation walks every path and finds theVOIDcase; the error appears whentest(...)starts.
The Practice problem below controls a fan from a temperature; a sensor fault should force the highest speed whatever the temperature says, but the faulty row prints the speed for a cool reading instead.