Skip to content
code0.HDL

Mux, when, Wire, and a default for every path

Choose between values with Mux and when, give a Wire a default, read the last-connection-wins rule off a trace, and see what happens to a path nothing drives.

In this chapter

  • Write a two-way choice as Mux or as a when chain and say which hardware each leaves in the circuit
  • Predict the value of a sink driven by a chain and a later override, using first-true-condition and last-connection-wins
  • Give every output and wire a driver on every path with a default, .otherwise, WireDefault or DontCare
  • Read a RefNotInitializedException and name the stage that reports it
Concepts in this chapter
  • Mux, when, default connection
Prerequisite concepts

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 bits

This 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.

WrittenDecidedLeaves in the circuit
if (scalaBoolean) ... else ...elaborationonly the branch that was chosen
Mux(bool, a, b)simulation, every momenta multiplexer
when (bool) { ... }simulation, every momenta 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 later

Wire(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
}
PartMeaning
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.

RuleMeaning
A chain uses its first true condition.elsewhen branches are skipped once one holds
Between separate statements, the last connection winsA 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 pathExample
Connect a default before the whenio.out := 0.U
Cover the rest with .otherwise.otherwise { io.out := 0.U }
Declare with a defaultWireDefault(0.U(8.W))
State that the value does not matterio.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.

Grade.scala
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
}
Tb.scala
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?

Your answer

Check the result. Press Run and trace the last row step by step.

StepConditionCandidate for grade
Chain, level > 200True3
.elsewhen branchesSkipped; a chain uses only its first true condition3
Separate when (io.force)True, and written later0

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?

Your answer

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?

Your answer

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?

Your answer

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)
  }
}
ItemDetail
Wrong codeNo connection to io.out on the path where io.en is 0
Failing stageThe initialization check before simulation. Elaboration itself succeeds, and the generated CHIRRTL holds a lone when io.en connection
Error messageRefNotInitializedException with Reference io is not fully initialized. The last line shows io.out driven by a mux of io.en, io.in, and VOID
FixAdd 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

TopicKey point
Mux(c, a, b)A multiplexer as an expression; the condition is a hardware Bool, the width is the wider value
Scala ifChooses 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 / .otherwiseConditional connections; a chain uses its first true condition
PriorityBetween separate statements the last true connection wins; an override comes last
CompletenessEvery sink needs a driver on every path: default, .otherwise, WireDefault, or DontCare
Undriven pathRejected before simulation as not fully initialized, with VOID marking the missing case

11. Check your understanding

  1. A wire is connected to 1 unconditionally and then to 2 inside when (c). What does it carry when c is false, and when c is true?

    Show answer

    1 when c is false, because only the unconditional connection applies. 2 when c is true, because the later connection wins.

  2. Deleting .otherwise in Grade makes the run fail, but alarm has no .otherwise at all. Why is alarm fine?

    Show answer

    alarm is declared with WireDefault(false.B), which drives it on every path that has no other connection. grade is a plain Wire, so without .otherwise one path leaves it undriven.

  3. io.x is an 8-bit UInt. How wide is Mux(io.sel, io.x, 1.U(4.W)), and what must io.sel be?

    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.sel must be a hardware Bool; a Scala Boolean would not compile.

  4. Why does the undriven output in Gate pass Scala compilation and elaboration, and which stage reports it?

    Show answer

    Each line is legal on its own: a when with 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 the VOID case; the error appears when test(...) 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.

Checkpoint

Submit the Practice problem for grading

Grading runs on the server against hidden tests; the result is recorded in this browser only.

Sources

Licences and notices for the sources this site follows