The Dual always-Block FSM Pattern

Why every FSM in this course is written as exactly two always blocks — a clocked state register and a combinational next-state block — and why single-block FSMs are off the menu.

Updated 2026-07-20

Every finite state machine you build in this course follows the same shape: exactly two always blocks, each with one job. This guide explains why that split exists, what each block is responsible for, and what goes wrong if you collapse them into one. The example below is a simple traffic-light phase sequencer — a different FSM from any lesson exercise, used here only to illustrate the pattern; RED/GREEN/YELLOW stand in for whatever state codes a real design would use.

The two blocks, and the one job each has

The state register is clocked. It holds the FSM's current phase and updates it on the rising clock edge, loading a known startup phase when clear is high:

always @(posedge clk) begin
  if (clear) phase <= RED;
  else       phase <= phase_next;
end

This block never decides anything. It has no case statement, no branching logic about what the next phase should be — it simply samples whatever the next-phase logic computed, on the edge, and holds that value until the next edge.

The next-phase block is combinational. It looks at the current phase (and usually an input) and computes what the next phase should be, using case:

always @(*) begin
  phase_next = phase;                    // safe default
  case (phase)
    RED:     if (go) phase_next = GREEN;
    GREEN:   if (go) phase_next = YELLOW;
    YELLOW:  if (go) phase_next = RED;
    default: phase_next = RED;
  endcase
end

This block never stores anything. It has no clock in its sensitivity list, no <= non-blocking assignment — it reacts instantly and continuously to its inputs, the same as any other combinational logic you built in Module 2.

Why not just write one block?

A single always @(posedge clk) block that both decides the next phase and stores it is technically possible — plenty of textbooks show it, and it is not inherently broken. This course standardizes on the two-block split instead because it keeps two habits you already learned completely separate, rather than because the single-block form is defective.

Combinational next-phase logic still needs a safe default, exactly like every always @(*) block since Module 2 — a default assignment at the top, before the case, so that any phase/input combination the case items don't explicitly handle still assigns a value (in this instance, "stay put"). When that decision logic is folded into the same block as the clocked phase update, the safe-default habit gets mixed together with clocked-assignment habits (<=, sensitivity on posedge clk), and it becomes easier to accidentally write a phase register that only updates under some conditions — a register that quietly holds stale state on the paths you forgot, not a level-sensitive latch (that specific failure mode belongs to combinational logic), but a bug with the same root cause: a path where an assignment was skipped.

Keeping the two blocks separate keeps each habit clean. The clocked block only ever needs the reset-then-sample pattern you already know from Module 3's flip-flops. The combinational block only ever needs the default-at-top-then-case pattern you already know from Module 2's case statements. Neither block has to think about the other's concerns — which is what makes this course's chosen pattern easier to reason about and easier to test deterministically, even though a well-written single-block FSM can be just as correct.

Two kinds of default, one block each

The combinational next-phase block actually carries two separate safety nets, and it is worth being precise about which protects what:

  • The default assignment at the top covers any input condition a matched case item's if does not explicitly handle — this is what makes "stay in the current phase when the input doesn't ask for a move" work without writing an else on every branch.
  • A default: case item covers a state code that no case item lists at all. With an n-bit state variable and fewer than 2ⁿ real phases, at least one code is unused. Without an explicit default: item, reaching that unused code (through a fault, a reset glitch, or simply a bug elsewhere) leaves the design with no matching case — the top-level default just repeats the same unused code back, forever. An explicit default: item recovers to a known phase instead.

Both matter, and they are not the same fix. Skipping the first reintroduces the ordinary combinational latch-inference trap inside a case item. Skipping the second means an FSM that reaches an unreachable-in-theory state has no way back to correct behavior — a much harder bug to spot, because it only shows up if that code is ever reached at all.

How to read this pattern going forward

Every FSM lesson from here forward — the sequence detector, the traffic light, the capstone's controller — reuses this exact two-block shape without re-explaining it. When you see an FSM's code, look for the state register first (clocked, no decisions) and the next-state block second (combinational, no storage). This course's exercises expect exactly this split; if your own design ever blends the two together, check it against the lesson's testbench before assuming it is equivalent.