FSM Coding Styles
Digital Design's FSM Fundamentals designed a Moore-machine sequence detector — asserting out=1 the cycle after seeing two consecutive 1s on serial input X — down to a state diagram and state table. This page codes that exact FSM in Verilog, in the three conventional styles every real FSM description falls into: 1-process, 2-process, and 3-process. All three describe identical hardware; they differ only in how the code is organized, and each has a real, practical tradeoff.
The FSM being coded
Structurally, any FSM is two pieces of hardware working together (a point every one of the three styles below organizes differently, but never eliminates): a state register (a flip-flop bank holding the current state) and combinational logic computing the next state and the outputs from the current state and inputs.
3-process style: one block per job
The clearest style separates the state register, the next-state logic, and the output logic into three distinct always blocks — each one following exactly one rule from Combinational vs. Sequential always:
localparam S0 = 2'd0, S1 = 2'd1, S2 = 2'd2;
reg [1:0] state, next_state;
reg out;
// Process 1: state register — sequential, nonblocking
always @(posedge clk or posedge rst) begin
if (rst) state <= S0;
else state <= next_state;
end
// Process 2: next-state logic — combinational, blocking
always @(*) begin
case (state)
S0: next_state = x ? S1 : S0;
S1: next_state = x ? S2 : S0;
S2: next_state = x ? S2 : S0;
default: next_state = S0;
endcase
end
// Process 3: output logic — combinational, blocking (Moore: depends only on state)
always @(*) begin
case (state)
S0: out = 0;
S1: out = 0;
S2: out = 1;
default: out = 0;
endcase
end
Each block is unambiguously either the sequential state register or purely combinational logic — there's no way to accidentally mix nonblocking and blocking assignment within a block, because each block only ever does one job. This makes 3-process FSMs the easiest style to review and debug, at the cost of the most typing.
2-process style: combine the logic, keep the register separate
A common compromise merges the next-state and output logic into one combinational block, keeping the state register separate:
always @(posedge clk or posedge rst) begin
if (rst) state <= S0;
else state <= next_state;
end
always @(*) begin
// defaults prevent inferred latches (see Conditional and Case Statements)
next_state = state;
out = 0;
case (state)
S0: if (x) next_state = S1;
S1: begin
if (x) next_state = S2;
end
S2: begin
out = 1;
if (x) next_state = S2; else next_state = S0;
end
endcase
end
This trims one block without weakening the combinational-vs-sequential separation — the register block is still purely sequential, and the merged block is still purely combinational (@(*), blocking assignment throughout). The default-assignment lines at the top of the combinational block are what keep every case branch fully covered without spelling out every signal in every branch — the same latch-avoidance principle from Conditional and Case Statements, just applied more compactly.
1-process style: everything in one clocked block
always @(posedge clk or posedge rst) begin
if (rst) begin
state <= S0;
out <= 0;
end else begin
case (state)
S0: begin state <= x ? S1 : S0; out <= 0; end
S1: begin state <= x ? S2 : S0; out <= 0; end
S2: begin state <= x ? S2 : S0; out <= 1; end
default: begin state <= S0; out <= 0; end
endcase
end
end
Everything — register update, next-state decision, and output — lives in one always @(posedge clk) block, entirely nonblocking. This is the most compact style, but it comes with a real cost: because out is itself now a registered (flip-flopped) signal rather than combinational logic reading the current state, it changes one clock cycle later than the 2-process and 3-process versions above, where the Moore output logic reads state combinationally and reflects it in the same cycle. That's not automatically wrong — a registered output is often exactly what you want, for a cleaner, glitch-free signal reaching another synchronous block — but it's a genuine behavioral difference between styles, not just a stylistic one, and choosing 1-process needs to be a deliberate choice about output timing, not a default.
State encoding: binary vs. one-hot
All three styles above use binary encoding — S0/S1/S2 packed into the fewest bits possible (2 bits cover up to 4 states). The alternative is one-hot encoding, where each state gets its own dedicated bit and exactly one bit is set at a time (S0 = 3'b001, S1 = 3'b010, S2 = 3'b100, for example) — trading more flip-flops (one per state, instead of log2(states)) for simpler, faster next-state and output decode logic, since each state's condition is a single bit test instead of a multi-bit comparison. Binary encoding is the more flip-flop-efficient default; one-hot is common on FPGAs specifically because FPGA fabric tends to have flip-flops to spare but benefits from the simpler combinational logic one-hot produces. Neither choice changes anything about which of the three process styles above is used — encoding and process style are independent decisions.
Which style to reach for
3-process is the safest default for learning and for review — the separation of concerns makes each block trivially checkable against the "one job per always block" rule. 2-process is extremely common in production RTL once that discipline is second nature, since it's less code with no loss of correctness. 1-process is worth using specifically when a registered (one-cycle-delayed) output is the actual design intent — not as a shortcut, since picking it by default silently changes your FSM's output timing.
What's next
FSMs are usually only one small piece of a larger design that also includes datapath elements — counters, shift registers, adders — often replicated multiple times. The next page covers parameters and generate, which is how Verilog scales a design like the ripple-carry adder from Data Types up to an arbitrary width without hand-writing one instance per bit.