localparam for state encoding, one clocked block for the state register,
and combinational blocks (with the latch rule enforced) for next-state and output logic.
You already know what an FSM is: a set of states, transitions between them triggered by inputs, and outputs that depend on the state (Moore) or on the state and current input together (Mealy). Nothing about that theory changes. What's new is purely mechanical — how those three pieces map onto Verilog constructs you already have.
| FSM concept | Verilog construct |
|---|---|
| The current state, stored as memory | A reg updated in an always @(posedge clk) block, non-blocking
(Lesson 4) — this is the only piece of real memory in the whole machine. |
| State names | localparam constants, so you write S_RED instead of a bare number. |
| Transition logic (what the next state should be) | An always @(*) block with a case on the current state — combinational,
blocking (Lesson 3). |
| Output logic | Another combinational block (or folded into the same one) — a case keyed on state
(Moore) or on state and input (Mealy). |
Three states, one input (tick, pulses once per timing interval), Moore outputs (each light
depends only on the current state):
module traffic_light_fsm (
input wire clk,
input wire rst,
input wire tick,
output reg red,
output reg yellow,
output reg green
);
localparam [1:0] S_RED = 2'd0, S_GREEN = 2'd1, S_YELLOW = 2'd2;
reg [1:0] state, next_state;
// Block 1: the state register — the only sequential element here.
always @(posedge clk) begin
if (rst) state <= S_RED;
else state <= next_state;
end
// Block 2: next-state logic — purely combinational.
always @(*) begin
case (state)
S_RED: next_state = tick ? S_GREEN : S_RED;
S_GREEN: next_state = tick ? S_YELLOW : S_GREEN;
S_YELLOW: next_state = tick ? S_RED : S_YELLOW;
default: next_state = S_RED;
endcase
end
// Block 3: output logic (Moore — depends only on state).
always @(*) begin
{red, yellow, green} = 3'b000; // defensive default — see callout below
case (state)
S_RED: red = 1'b1;
S_GREEN: green = 1'b1;
S_YELLOW: yellow = 1'b1;
default: red = 1'b1;
endcase
end
endmodule
Full source: code/traffic_light_fsm.v.
This is the three-block style: one sequential block, two combinational blocks. You'll
also see a two-block style that merges next-state and output logic into one
always @(*) — fine for small Moore machines, but the three-block split scales better once
output logic gets complicated or you move to Mealy outputs, because you can reason about "what
determines the next state" and "what determines the outputs" completely separately.
{red, yellow, green} = 3'b000; at the top of the output block, before the
case, assigns every output on every path unconditionally — then the case only
needs to override what's different per state. This is the single most reliable way to avoid the
latch-inference bug from Lesson 3 in FSM output logic, where it's easy to forget a light in one branch.
The default: branch in the next-state case serves the same purpose: it
guarantees a defined next state even for an unreachable/corrupted encoding of state
(relevant if the FSM ever ends up in an unexpected state, e.g. from a glitch).
The highlighted circle is state right now; the lit lamp is this module's Moore output for
that state. Each click of Pulse tick simulates one posedge clk with
tick = 1 — watch state advance exactly one step around the cycle, matching
the case in Block 2.
state declared as a reg updated in a clocked block, while next_state is only ever assigned in a combinational block?traffic_light_fsm with a fourth state, S_YELLOW_BLINK, entered from
S_RED when a new input fault is high (checked instead of the normal
tick transition), and add it to both the next-state case and the output case. This
forces you to touch every place a new state has to be registered — a good check for whether you've
internalized the three-block structure.
New terms — state register, next-state logic, Moore/Mealy machine, localparam — are in the glossary.