Module 5: Behavioral Verilog for Synthesis ยท 5.6 Modeling Finite State Machines in Verilog

The Two-Process Style

Ask ten RTL engineers for their default state-machine style and most will say two processes: one for the clock, one for everything else. It is in the Xilinx, Intel and Synopsys guides, and it is the one to use unless you have a reason not to.

The split

// the state register
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) state <= IDLE;
    else        state <= next_state;
end

// next state and outputs
always @(*) begin
    next_state = state;
    out        = 1'b0;
    case (state)
        IDLE:   if (start) next_state = ACTIVE;
        ACTIVE: begin
                    out = 1'b1;
                    if (done) next_state = IDLE;
                end
    endcase
end

The first block does one thing: at each edge, load state from next_state. It is the only flip-flop in the machine. The second is purely combinational, @(*), blocking assignments, computing the next state and every output afresh whenever anything changes.

Why it is the default

The combinational block is exactly the discipline of sections 5.2 and 5.3: @(*), defaults at the top, full coverage. There is no mixing of clocked and combinational reasoning in one block. The transitions and the outputs sit together, away from the clocking, where they can be read and tested on their own, and a $display in the second block shows every decision the machine makes.

Moore and Mealy

The template does not change; only what the output reads does.

// Moore: the output reads the state
always @(*) begin
    next_state = state;
    grant      = 1'b0;
    case (state)
        ARBITRATE: if (winner) next_state = GRANTED;
        GRANTED:   grant = 1'b1;
    endcase
end
// Mealy: the output reads the state and an input
always @(*) begin
    next_state = state;
    grant      = 1'b0;
    case (state)
        ARBITRATE: begin
            grant = req;
            if (req) next_state = GRANTED;
        end
    endcase
end

That is the whole of the Moore and Mealy distinction from section 3.3, visible in the RTL.

Defaults

Both blocks open with next_state = state; and a default for every output before the case. Whatever arm runs, and whatever state or input combination is not listed, every output has a value, and the block is latch-free by construction. Two lines at the top of the block buy that.

The detector again

`timescale 1ns / 1ps
`default_nettype none

module packet_detector_2proc (
    input  wire clk, rst_n, bit_in,
    output reg  found
);
    localparam S0 = 1'b0, S1 = 1'b1;
    reg state, next_state;

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

    always @(*) begin
        next_state = state;
        found      = 1'b0;
        case (state)
            S0: if (bit_in) next_state = S1;
            S1: if (!bit_in) begin
                    found      = 1'b1;      // combinational: in the same cycle as the 0
                    next_state = S0;
                end else begin
                    next_state = S1;
                end
        endcase
    end
endmodule

Run this beside the one-process version on the same stream and the difference is in the waveform: found is now a combinational function of the state and the input, so it rises in the cycle the pattern completes, a cycle earlier than the registered version. Same states, same logic, different output timing, because of which block the output lives in.

If you learn one state-machine style well, learn this one. The next article splits the combinational block once more, into transitions and outputs.

โ† The One-Process Style The Three-Process Style โ†’