Verilog · lesson 9

Finite State Machines

The three-process FSM coding style, why it keeps clocked and combinational logic separate, and a working 1011 detector verified against two overlapping matches.

11 min read · Free, no login

Three processes, three jobs

The Digital Electronics path's sixth lesson covered Moore and Mealy machines as a concept: a state diagram, a state table, next-state and output logic derived from them. This lesson is about writing that same machine in Verilog in a way that cannot accidentally produce a latch (lesson 6) or a blocking/non-blocking mismatch (lesson 8) โ€” by splitting it into exactly three always blocks, each with one job.

The three-process FSM template
// Process 1: state register -- the ONLY clocked part of the whole FSM.
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state <= S0;
    else        state <= next_state;
end

// Process 2: next-state logic -- pure combinational, reads state and inputs,
// decides next_state.
always @* begin
    case (state)
        // ...
    endcase
end

// Process 3: output logic -- pure combinational, reads state (Moore)
// or state and inputs (Mealy), decides the outputs.
always @* begin
    // ...
end

The split is not cosmetic. Process 1 is the only block in the whole machine assigned with <=, because it is the only clocked block โ€” exactly lesson 7's flip-flop template, with state as the register. Processes 2 and 3 are both always @* blocks assigned with =, following lesson 5 and lesson 6's rules for combinational logic exactly: every branch of every case needs to be covered (a default is cheap insurance against an unreachable-looking state value becoming reachable later), and nothing in either block is clocked. Separating next-state logic (process 2) from output logic (process 3) rather than folding them together is what makes a Mealy machine's output โ€” which depends on the current input as well as the current state โ€” straightforward to add later without touching the state transition logic at all: only process 3 would need to read bit_in as well as state.

A working sequence detector

Let us build the same 1011 overlap-detecting Moore machine the Digital Electronics path designed on paper, as real, simulated Verilog.

S0 S1 S2 S3 S4 match=1 1 0 1 1 0 1 0 0 0 1
Identical machine to the Digital Electronics path's worked example: S0 through S3 track the longest matched prefix of 1011; S4 is the accepting state and re-synchronizes rather than resetting to S0, which is what lets it catch overlapping matches.
seq_detect_1011.v
module seq_detect_1011 (
    input  wire clk,
    input  wire rst_n,
    input  wire bit_in,
    output reg  match
);
    localparam S0 = 3'd0, S1 = 3'd1, S2 = 3'd2, S3 = 3'd3, S4 = 3'd4;
    reg [2:0] state, next_state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) state <= S0;
        else        state <= next_state;
    end

    always @* begin
        case (state)
            S0: next_state = bit_in ? S1 : S0;
            S1: next_state = bit_in ? S1 : S2;
            S2: next_state = bit_in ? S3 : S0;
            S3: next_state = bit_in ? S4 : S2;
            S4: next_state = bit_in ? S1 : S2;
            default: next_state = S0;
        endcase
    end

    always @* begin
        match = (state == S4);
    end
endmodule

localparam names each state instead of using bare numbers โ€” this is purely for readability and carries no synthesis cost, and it means the next-state case reads exactly like the state table it was derived from. Driving the bit stream 1011011, which contains two deliberately overlapping matches of 1011 (bits 0–3, and bits 3–6, sharing bit 3), one bit per clock cycle:

bit=1  state=1  match=0
bit=0  state=2  match=0
bit=1  state=3  match=0
bit=1  state=4  match=1
bit=0  state=2  match=0
bit=1  state=3  match=0
bit=1  state=4  match=1

match goes high exactly twice, exactly where the two overlapping occurrences of 1011 actually end โ€” the fourth and the seventh bits of the stream. The machine reaching S4 after the first match and continuing on to a second match four bits later, rather than needing to restart from S0, is precisely the re-synchronizing behavior the Digital Electronics path's state table built in on purpose: from S4, an incoming 0 goes to S2 and an incoming 1 goes to S1, never back to S0, which is what makes overlapping matches detectable at all. Synthesizing this module produces only flip-flop cells for the 3-bit state register and ordinary combinational gates for both case statements โ€” no latch warning, because both case statements have a default branch and cover every state the register can hold.

Common mistakes

Coding the state register and the next-state logic in the same always block, mixing <= for state with = for some intermediate signal, is the first mistake โ€” lesson 8's rule against mixing styles in one block applies here exactly as it does anywhere else. The second is forgetting a default branch in the next-state case โ€” with a 3-bit state register encoding only 5 real states, three bit patterns (3'd5 through 3'd7) are never assigned by the design but are not actually unreachable from the hardware's point of view (power-on in an unknown state, or a single-event upset), and an unhandled case there infers a latch exactly as lesson 6 described. The third is writing a Moore machine's output logic so that it accidentally reads an input signal as well as state โ€” the moment process 3 reads anything besides state, the machine has quietly become Mealy, whether or not that was the intent, which is worth knowing precisely because of the timing difference the Digital Electronics path's lesson on this topic explains.

Interview questions

Why split an FSM into three always blocks instead of one? It cleanly separates the one genuinely clocked part (the state register, described with <=) from the two combinational parts (next-state logic and output logic, described with =), which keeps lesson 8's blocking/non-blocking rules unambiguous and makes a Moore-to-Mealy change a one-block edit rather than a restructuring.

What turns a three-process Moore machine into a Mealy machine? The output-logic process reading the current input, not just the current state. In the three-process template, this means process 3's sensitivity list and body change from depending on state alone to depending on state and the relevant input signals.

Why does a default branch matter in next-state logic even if every state value looks covered? A state register's bit width often has more encodings than the design uses on purpose โ€” an unhandled state value, even one that "shouldn't happen," still infers a latch for the next-state signal exactly as any other incomplete case would, per lesson 6.

Key takeaways

  • The three-process FSM template separates the clocked state register (<=) from the combinational next-state logic and output logic (=, each its own always @* block) โ€” applying lessons 5 through 8's rules directly rather than inventing new ones for FSMs specifically.
  • localparam names for states keep the next-state case reading like the state table it was derived from, at no synthesis cost.
  • A sequence detector built this way and driven with a real overlapping-match bit stream correctly flags both matches, confirmed here in simulation and confirmed latch-free in synthesis.
  • Whether a machine is Moore or Mealy is decided entirely by whether the output-logic process reads an input signal in addition to state โ€” splitting next-state and output logic into separate processes keeps that choice a one-block edit.