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.