Skip to content
code0.HDL

Conditional update, hold, and reset

Update a register only under a when and let it hold otherwise, decide which registers a reset changes from how each was created, see that the implicit reset acts at a clock edge, and write priority as source order.

In this chapter

  • Connect a register only inside a when and explain why it needs no default
  • Tell from a register's constructor whether a reset changes it, and what its value is before the first reset
  • Predict what the synchronous implicit reset does before and after a clock edge, and name the three reset types
  • Write load, count, and clear priority as the order of a when chain
Concepts in this chapter
  • Conditional update and reset with when
Prerequisite concepts

1. A register updated under when holds in every other cycle

Outside reset, put a register's connection inside a when and it captures a new value only at edges where the condition holds. In every other cycle the register receives no connection and keeps its current value. That is the enable from the fundamentals, and it is the one place where a missing path is not an error: a wire with no driver on some path is rejected before simulation, but a register simply holds, so it never needs a default or an .otherwise. The unconnected-wire check that Mux, when, Wire, and a default for every path applied to wires and outputs is not triggered by a register connected only inside when.

Form

val r = RegInit(0.U(4.W))
when (io.en) {
  r := io.in
}
CycleenWhat the edge does to r
any1captures io.in
any0keeps its current value

2. Priority is written as source order

When two connections to a register could both apply in one cycle, the rules of the when chapter decide: a when/.elsewhen chain uses its first true branch, and between separate statements the later connection wins. A load that must beat counting is written first in a chain; a clear that must beat everything is written either first in the chain or as the last separate when.

Form

when (io.load) {
  count := io.cycles
} .elsewhen (count =/= 0.U) {
  count := count - 1.U
}

With load and a nonzero count both true, the register loads; the decrement branch is never reached. With both false, nothing connects and the register holds.

3. Reset changes only registers that declare a reset value

Reset is not a global clear. It belongs to each register's declaration, and it applies only to registers that declare a reset value.

Created withReset valueWhile reset is high, at the edge
RegInit(v)vcaptures v instead of its next value
RegNext(x, v)vthe same
Reg(t)nonehas no reset connection; keeps following its next-value logic
RegNext(x)nonethe same

A register without a reset value also has no defined starting value. The treadle simulator happens to start such registers at 0, but hardware promises nothing. Control state such as flags, valid bits, and counters needs a reset value because correctness depends on where it starts.

The implicit reset of a top-level module is synchronous: it takes effect at a clock edge, not the moment it goes high. chiseltest pulses it for one cycle before your code runs, so at the first peek every register with a reset value already holds it. A testbench can also drive it like an ordinary input with dut.reset.poke(true.B), and then nothing changes until a step.

4. Four registers through one reset

ResetDemo holds four registers created the four ways of section 3.

RegisterCreated withNext value
withInitRegInit(5.U(4.W))in while en is high
noInitReg(UInt(4.W))in while en is high
nextRegNext(io.in)in, every cycle
nextInitRegNext(io.in, 0.U(4.W))in, every cycle
ResetDemo.scala
import chisel3._

class ResetDemo extends Module {
  val io = IO(new Bundle {
    val en       = Input(Bool())
    val in       = Input(UInt(4.W))
    val withInit = Output(UInt(4.W))
    val noInit   = Output(UInt(4.W))
    val next     = Output(UInt(4.W))
    val nextInit = Output(UInt(4.W))
  })

  // Reset loads 5 into this register.
  val withInit = RegInit(5.U(4.W))
  // No reset value: reset does not touch this register.
  val noInit = Reg(UInt(4.W))
  when(io.en) {
    withInit := io.in
    noInit   := io.in
  }

  io.withInit := withInit
  io.noInit   := noInit
  io.next     := RegNext(io.in)
  io.nextInit := RegNext(io.in, 0.U(4.W))
}

The testbench first prints only withInit, so it never depends on the undefined start of the registers without a reset value. It then loads 9 into every register, sets en low and in to 6, pokes reset high, prints before the edge, steps once, prints again, and finally releases reset and steps once more.

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

object Main extends App {
  test(new ResetDemo) { dut =>
    def show(phase: String): Unit = {
      val v = Seq(dut.io.withInit, dut.io.noInit, dut.io.next, dut.io.nextInit).map(_.peek().litValue)
      println(s"$phase withInit=${v(0)} noInit=${v(1)} next=${v(2)} nextInit=${v(3)}")
    }
    println(s"start withInit=${dut.io.withInit.peek().litValue}")

    // Load 9 into every register.
    dut.io.en.poke(true.B)
    dut.io.in.poke(9.U)
    dut.clock.step()
    show("loaded")

    // Hold reset for one edge while in changes to 6 and en is low.
    dut.io.en.poke(false.B)
    dut.io.in.poke(6.U)
    dut.reset.poke(true.B)
    show("reset-before-edge")
    dut.clock.step()
    show("reset-after-edge")

    dut.reset.poke(false.B)
    dut.clock.step()
    show("released")
  }
}

Practice this concept

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

Predict before you run. What does the start line show for withInit? Then predict the reset-before-edge line, which is printed right after reset goes high and before any edge, and the four values on the reset-after-edge line.

Check

reset has just been poked high and no edge has happened. What does the reset-before-edge line show?

Your answer

Check

After the edge with reset high and en low, what does noInit show?

Check

On the same reset-after-edge line, what do next and nextInit show?

Your answer

Check the result. Press Run and compare the five lines.

Output lineWhat it shows
start5, because chiseltest pulsed reset before the test body
loadedAll four hold 9 after one edge with en high
reset-before-edgeStill 9 everywhere: poking reset alone moves nothing
reset-after-edgewithInit is its reset value 5; noInit holds 9 because en is low; next tracks in regardless of reset and shows 6; nextInit is its reset value 0
releasedwithInit and noInit hold because en is still low; nextInit resumes tracking in and shows 6

Nothing changed on the reset-before-edge line, which proves this module's reset is synchronous: it takes effect only at a clock edge.

5. Take away one reset value

In the lab, change withInit from RegInit(5.U(4.W)) to Reg(UInt(4.W)) and predict the reset-after-edge line before running again.

Check

With withInit created by Reg(UInt(4.W)), what does it show on the reset-after-edge line?

6. Reset types

6.1 Three reset types

Chisel has three reset types, all implementing the trait Reset.

TypeConstructed withProduces
BoolBool()Synchronous-reset registers
AsyncResetAsyncReset()Asynchronous-reset registers
ResetReset()Abstract reset; the concrete type is inferred during FIRRTL compilation

A Module's implicit reset is abstract, and inference resolves it from what drives it.

What drives the abstract resetInferred type
Only AsyncReset, abstract Reset, or DontCareAsyncReset
Both Bool and AsyncResetError
Anything elseBool

Nothing drives a top-level module's reset, so it defaults to Bool. That is why ResetDemo, used as the top, has a synchronous reset, and its CHIRRTL declares input reset : UInt<1>. Abstract reset lets one block be reused under either reset discipline without edits.

6.2 Forcing the reset type

Mixing a trait into a module fixes the type of its implicit reset.

Form

class ModuleName extends Module with RequireSyncReset   // reset is Bool
class ModuleName extends Module with RequireAsyncReset  // reset is AsyncReset

Change the header to class ResetDemo extends Module with RequireAsyncReset and emit CHIRRTL: the port becomes input reset : AsyncReset while the registers are declared exactly as before. The testbench then stops at dut.reset.poke(true.B) with chiseltest.LiteralTypeException: don't know how to poke an IO[AsyncReset] with a Bool, because the port's type really changed.

6.3 Changing the reset for a region with withReset

Registers created inside a withReset block use the given signal as their reset instead of the implicit one.

Form

withReset(resetSignal) {
  // registers created here reset on resetSignal
}
val r = withReset(io.clear) { RegInit(0.U(8.W)) }
r := r + 1.U

io.clear is a Bool, so r is a synchronous-reset register: raising clear does not zero r immediately, only after the next edge.

Caution. The casts reset.asBool and reset.asAsyncReset are not checked by the compiler. Use them only when you know the reset's actual discipline.

7. Common mistakes

7.1 Expecting reset to act the moment you poke it

// Wrong expectation: synchronous reset has not happened yet
dut.reset.poke(true.B)
dut.io.withInit.peek()   // still the old value

// Right: step one edge, then look
dut.reset.poke(true.B)
dut.clock.step()
dut.io.withInit.peek()   // the reset value

Synchronous reset acts only at a clock edge. To see its effect in a testbench, hold reset high through one clock.step().

8. Apply it: a flag that survives reset

The graded Practice problem below has an error flag seen next to an error counter errors. After a reset the counter is 0 and the flag is still on, and before the first error the flag has no defined value. Compare the two declarations against the table in section 3; only one of them has to change. Write the cycle-by-cycle table for the five printed phases before you press Run.

9. Summary

RuleConsequence
a register connected only inside whenholds in every cycle where the condition is false; no default needed
priorityfirst true branch of a chain; between separate statements the later one wins
RegInit(v), RegNext(x, v)reset to v at an edge while reset is high
Reg(t), RegNext(x)untouched by reset; undefined start
the implicit reset at the topsynchronous: poke it, then step, to see its effect
reset typethe implicit reset is abstract and resolves to Bool at the top; RequireSyncReset and RequireAsyncReset force it; withReset changes it for a region

10. Check your understanding

  1. If the reset were asynchronous, which registers should change on the reset-before-edge line, and why can the shipped testbench not show that directly?

    Show answer

    An asynchronous reset does not wait for an edge, so the registers with a reset value, withInit and nextInit, should jump to 5 and 0 at once. But forcing an asynchronous reset with RequireAsyncReset makes dut.reset.poke(true.B) throw LiteralTypeException, so the testbench never reaches that line.

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