State Machines and Control/Datapath Separation
Write a readable FSM with an enumerated type and separate decisions from data calculations.
Three parts to recognize
A synchronous state machine usually contains:
- a state register;
- logic that selects the next state;
- output or control logic.
SystemVerilog makes these roles visible with an enumeration, always_ff, and always_comb.
module burst_controller (
input logic i_clk,
input logic i_rst,
input logic i_start,
input logic i_last,
output logic o_load,
output logic o_busy,
output logic o_done
);
typedef enum logic [1:0] {IDLE, LOAD, RUN, DONE} state_t;
state_t state_q, state_d;
always_ff @(posedge i_clk) begin
if (i_rst)
state_q <= IDLE;
else
state_q <= state_d;
Default values
The start of the combinational block assigns every output. Each state that may last for several cycles also assigns its hold value explicitly. This covers every path without a latch.
Here, state_d = IDLE defines recovery when the current encoding is illegal. Omitting a default item is deliberate: it lets the runtime check from unique case report that no branch recognized state_q. On the following edge, the machine returns to IDLE. The exact policy depends on safety requirements; it does not replace a proper reset or a dedicated assertion when an illegal state must fail simulation.
unique is not decoration
unique case promises that at most one branch should match and that one branch should be found. Without a default item, the simulator can warn when no branch or several branches match. A default item catches every remaining value and therefore suppresses the "no match" diagnostic. Synthesis may also use the unique promise for optimization.
Do not add unique only as a style choice. If several branches can match or an uncovered value is normal, the keyword does not describe reality.
Separate control and datapath
An FSM should generate simple commands such as load, clear, enable, or select. The datapath holds data registers, counters, multiplexers, and operators.
This separation helps verification: control transitions and calculations can be checked independently. It also avoids placing a long arithmetic expression inside already complex transition logic.
Key points
- An enumerated type gives states safe, readable names.
- The state register belongs in
always_ff. - Default assignments and explicit hold values prevent latches.
uniqueexpresses a real promise; adefaultitem changes the available diagnostic.- Control/datapath separation makes a design easier to review and test.
📝 Test your knowledge - Chapter quiz