Volume 04 Beginner 6 sub-modules ~25 min read

Writing State Machines in Verilog and SystemVerilog

In Volume 03 you built a state machine from gates, by hand. Now you will write one the way engineers really do: in Verilog, with the synthesis tool doing the K-maps for you. This volume gives you one template that fits every machine, shows the three common styles and when each is right, and ends with a testbench that checks your machine for you.

You will learn
  • The two-block template that fits every state machine
  • The one-, two- and three-process styles, and their timing
  • How default assignments stop accidental latches
  • Synchronous and asynchronous reset, and how to choose
  • SystemVerilog: enum, always_ff, always_comb and unique case
  • How to write a testbench that checks itself
You need
  • Volume 03: the drink machine and its states
  • Running a simulation, from sub-module 0.5

4.1 The two-block template

Every state machine in Verilog has the same shape: one clocked block for the state register, and one combinational block that works out the next state and the outputs.

In Volume 03 you did the hard work by hand: codes, excitation tables, K-maps. In real projects, nobody does that. You describe the machine in Verilog, and the synthesis tool finds the gates. Your job is to describe it clearly - and one template does that for every state machine.

Two blocks, two jobs

Remember the three blocks inside every clocked state machine, from Figure 1.3 in Volume 01: the state register, the next-state logic and the output logic. In Verilog, they become two always blocks:

When an always block runs is set by its sensitivity list, the part after the @. Here is block 1 on its own:


always @(posedge clk) begin        // run at every rising edge of clk
  if (rst) state <= C0;            // reset: go to the start state
  else     state <= next_state;    // otherwise: take the step block 2 chose
end

That is all block 1 ever does. It never changes from one machine to the next, apart from the name of the reset state.

Naming the states

Block 2 talks about states by name, so we first give each code a name with a localparam - a named constant:


localparam [1:0] C0   = 2'b00,     // [1:0] means "2 bits wide"
                 C1   = 2'b01,     // 2'b01 means "2 bits, binary 01"
                 C2   = 2'b11,
                 VEND = 2'b10;
reg [1:0] state, next_state;       // the current and the next state

These are the codes chosen in Volume 03. You could pick others, and the machine would still work.

The whole machine

Here is the drink machine from Volume 03, complete. Read the comments first, then the code.


module drink_fsm (
  input  wire clk,
  input  wire rst,          // reset, active high
  input  wire coin,         // 1 in the cycle a coin arrives
  output reg  vend          // 1 for one cycle to release a drink
);
  localparam [1:0] C0 = 2'b00, C1 = 2'b01, C2 = 2'b11, VEND = 2'b10;
  reg [1:0] state, next_state;

  // Block 1: the state register (flip-flops)
  always @(posedge clk) begin
    if (rst) state <= C0;
    else     state <= next_state;
  end

  // Block 2: next-state and output logic (gates)
  always @(*) begin
    next_state = state;     // default: stay where you are
    vend       = 1'b0;      // default: no drink
    case (state)
      C0:   if (coin) next_state = C1;
      C1:   if (coin) next_state = C2;
      C2:   if (coin) next_state = VEND;
      VEND: begin
              vend       = 1'b1;               // Moore output of VEND
              next_state = coin ? C1 : C0;     // a coin now counts as the first
            end
    endcase
  end
endmodule

Compare it with the state diagram in Figure 3.5 of Volume 03. Each line of the case statement is one state, and each if is one arrow. The two lines at the top of block 2 are the self-loops and the "vend is 0" rule, written once instead of four times.

Two kinds of assignment

Look closely, and you will see two different assignment signs:

Remember

Use <= in always @(posedge clk) blocks. Use = in always @(*) blocks. Never mix them in one block.

The reason behind this rule is covered in depth in the Verilog course, sub-module 1.4. For state machines, following the rule is enough.

Common mistake

Older code often writes block 2 as always @(state or coin) - listing the inputs by hand. Forget one, and the simulator stops running the block when that input changes, while the real gates never stop. The simulation and the hardware then disagree. Always write always @(*), which means "every input of this block".

Quick check

In the two-block template, which block becomes flip-flops?

Show the answer

Answer: B. A block that runs only at a clock edge, and stores a value, describes flip-flops. The always @(*) block runs whenever an input changes and stores nothing, so it becomes gates.

4.2 One-, two- and three-process styles

The same machine can be written with one, two or three always blocks. They differ in where the outputs come from - and so in their timing.

Engineers call each always block a "process", so the three styles are known as the one-, two- and three-process styles. You have just seen the two-process style. Here are the other two.

One process: everything in one clocked block


always @(posedge clk) begin
  if (rst) begin
    state <= C0;
    vend  <= 1'b0;
  end else begin
    case (state)
      C0:   if (coin) state <= C1;
      C1:   if (coin) state <= C2;
      C2:   if (coin) state <= VEND;
      VEND: state <= coin ? C1 : C0;
    endcase
    vend <= (state == VEND);      // looks right - but it is one cycle late
  end
end

It is short, and every output comes from a flip-flop. But the marked line has a trap. At the edge where the machine enters VEND, state == VEND is still false, because state still holds its old value, C2. So vend rises one edge later - in the cycle after VEND. To fix it, set the output on the arrow that enters the state:


end else begin
  vend <= 1'b0;                                           // default: off
  case (state)
    C0:   if (coin) state <= C1;
    C1:   if (coin) state <= C2;
    C2:   if (coin) begin state <= VEND; vend <= 1'b1; end  // set it on the way in
    VEND: state <= coin ? C1 : C0;
  endcase
end

This works, but with many outputs it becomes hard to read: each output must be set on every arrow into every state where it is 1.

Three processes: registered outputs, on time

The three-process style keeps the two-process template, and adds a third clocked block for the outputs. The trick is that it looks at next_state, not at state:


// 1. state register
always @(posedge clk)
  if (rst) state <= C0;
  else     state <= next_state;

// 2. next-state logic only
always @(*) begin
  next_state = state;
  case (state)
    C0:   if (coin) next_state = C1;
    C1:   if (coin) next_state = C2;
    C2:   if (coin) next_state = VEND;
    VEND: next_state = coin ? C1 : C0;
  endcase
end

// 3. registered output, decoded from next_state so it is not late
always @(posedge clk)
  if (rst) vend <= 1'b0;
  else     vend <= (next_state == VEND);

At the edge where the state register takes the value VEND, the output register takes next_state == VEND, which is true. Both change at the same edge. The output now comes straight from a flip-flop - a registered output - so it cannot glitch, and it is not late.

Figure 4.1 shows all three on the same coins.

Timing of the vend output in the two-process, three-process and naive one-process styles 0 1 2 3 4 5 6 7 clk coin state C0 C1 C2 VEND C0 C1 vend_2proc vend_3proc vend_1proc
Figure 4.1 - Three coins, three styles. The two-process and three-process outputs rise in the VEND cycle. The naive one-process output rises one cycle late, after the machine has already left VEND.

Which style should you use?

One process Two processes Three processes
Always blocks 1 2 3
Where outputs come from flip-flops gates, from the state flip-flops
Can outputs glitch? no yes, briefly no
Easy to read? poor with many outputs yes yes
Best for tiny machines learning, and most control outputs that leave the block or the chip

This course uses the two-process style for teaching, and the three-process style where outputs must be clean. For a deeper comparison, including the synthesis results, see the three FSM coding styles in the Verilog course.

Common mistake

In the three-process style, do not decode the output register from state. That brings back the one-cycle delay of the naive one-process style. The output register must look at next_state - the state the machine is about to enter.

Quick check

In a one-process machine, an output is written as out <= (state == DONE);. What is wrong?

Show the answer

Answer: C. Inside a clocked block, state still holds the old value at the edge where the machine enters DONE. So the comparison is false at that edge, and the output only becomes 1 at the next one - one cycle late.

4.3 Default assignments and avoiding latches

In a combinational block, every output must get a value on every path through the block. A default assignment at the top makes sure it always does.

A drink machine that never stops giving

Here is block 2 of the drink machine, written slightly differently. Can you spot the problem?


always @(*) begin
  next_state = state;
  case (state)
    C0:   if (coin) next_state = C1;
    C1:   if (coin) next_state = C2;
    C2:   if (coin) next_state = VEND;
    VEND: begin vend = 1'b1; next_state = coin ? C1 : C0; end
  endcase
end

vend is given a value only in VEND. In C0, C1 and C2 the block says nothing about it. But Verilog has a firm rule: a variable that is not assigned keeps its old value. So after the first drink, vend becomes 1 - and stays 1 forever. The machine gives away every drink after the first.

What the hardware does

To "keep the old value" without a clock, the synthesis tool has to build a memory that is not a flip-flop. It builds a latch: a memory that copies its input while it is enabled, and holds it otherwise. A latch built by accident is almost always a bug:

Most synthesis tools print a warning that mentions a latch when this happens. Read your warnings.

The fix: defaults first

Give every output, and next_state, a value at the very top of the block. This is called a default assignment. Then each branch only has to say what is different:


always @(*) begin
  next_state = state;      // default: stay
  vend       = 1'b0;       // default: no drink
  case (state)
    C0:   if (coin) next_state = C1;
    C1:   if (coin) next_state = C2;
    C2:   if (coin) next_state = VEND;
    VEND: begin vend = 1'b1; next_state = coin ? C1 : C0; end
    default: next_state = C0;   // any other code: go back to the start
  endcase
end

Now every path assigns vend - even the paths that never mention it assign the default 0. There is no latch.

The default: branch catches any state value not listed above it. The drink machine uses all four codes, so it can never be reached - but in a machine with unused codes, it sends a lost machine home. Volume 11 builds on this idea.

Remember

Start every combinational block with a default for next_state (usually = state) and for every output (usually = 0). Then no path can leave anything unassigned.

Common mistake

"If I do not assign an output, it will be 0." It will not. In Verilog an unassigned variable keeps its old value, and in hardware that means a latch. Never rely on a missing assignment to mean 0.

Quick check

A combinational always block assigns busy in three of its four case branches. What will synthesis build for busy?

Show the answer

Answer: D. In the fourth branch nothing assigns busy, so it must keep its old value. Keeping a value without a clock needs a latch. A default assignment at the top of the block removes it.

4.4 Synchronous vs asynchronous reset

A synchronous reset acts only at a clock edge. An asynchronous reset acts at once, whatever the clock is doing.

Every state machine needs a reset, to start in a known state. There are two ways to connect it to the state register.

Synchronous reset


always @(posedge clk)              // only the clock is listed
  if (rst) state <= C0;            // reset is looked at only on a rising edge
  else     state <= next_state;

With a synchronous reset, rst is just another input that the flip-flops look at on each edge. Nothing happens until the next rising edge.

Asynchronous reset


always @(posedge clk or negedge rst_n)   // the reset is listed too
  if (!rst_n) state <= C0;               // acts at once when rst_n falls to 0
  else        state <= next_state;

With an asynchronous reset, the reset is in the sensitivity list, so the block runs the moment reset arrives - no clock edge needed. This version is also active low: it resets when rst_n is 0. The _n at the end of the name is a common way to say so. The ! means NOT.

Figure 4.2 shows both kinds meeting the same reset pulse, which arrives in the middle of a cycle.

Synchronous and asynchronous reset compared: the asynchronous reset clears the state at once, the synchronous reset waits for the next clock edge 0 1 2 3 4 5 clk coin rst state_sync C0 C1 C2 C0 C1 state_async C0 C1 C2 C0 C1
Figure 4.2 - The same reset pulse, arriving in the middle of cycle 2. The asynchronous machine goes to C0 at once. The synchronous machine keeps C2 until edge 3. Both start counting again after edge 5, once reset has ended.

Which one should you use?

Synchronous reset Asynchronous reset
Acts at the next clock edge at once
Needs a running clock? yes no
Seen by the timing tools as an ordinary input a special path
The tricky part reset must last at least one clock cycle the release must be lined up with the clock

Both are correct when used properly. Many FPGA design guides prefer synchronous resets. Many chip designs use asynchronous resets that are applied at once but released in step with the clock. Follow your project's rule, and use the same style everywhere in a design.

Going deeper: why the release of an asynchronous reset matters

If an asynchronous reset is released very close to a clock edge, some flip-flops may leave reset at that edge and others at the next one. For a moment, the state register holds a mix - possibly an unused code. The standard cure is a small reset synchroniser, so that reset is always released just after a clock edge. It is covered in reset domain crossing in the Verilog course.

Common mistake

With an asynchronous active-low reset, the sensitivity list and the if must agree: negedge rst_n goes with if (!rst_n). Writing posedge rst_n, or if (rst_n), gives a machine that resets at the wrong time - or never.

Quick check

The clock has stopped, and reset is applied. Which state machine goes to its reset state?

Show the answer

Answer: B. A synchronous reset is only looked at on a clock edge, so with no clock nothing happens. An asynchronous reset is in the sensitivity list, so it acts at once, clock or no clock.

4.5 enum, unique case, always_ff and always_comb

SystemVerilog adds keywords that say what you mean - and the tools check that you meant it.

SystemVerilog is the newer, larger version of Verilog. Four of its features make state machines clearer and safer:

Keyword Replaces What it adds
enum localparam codes state names that waveform viewers can display
always_ff always @(posedge clk) an error if the block is not really clocked logic
always_comb always @(*) a warning if the block would create a latch
unique case case a warning if no branch, or more than one, matches

SystemVerilog also has the type logic, which you can use almost everywhere Verilog needs wire or reg.

The drink machine in SystemVerilog


module drink_fsm_sv (
  input  logic clk, rst, coin,
  output logic vend
);
  typedef enum logic [1:0] {C0 = 2'b00, C1 = 2'b01, C2 = 2'b11, VEND = 2'b10} state_t;
  state_t state, next_state;

  always_ff @(posedge clk) begin
    if (rst) state <= C0;
    else     state <= next_state;
  end

  always_comb begin
    next_state = state;
    vend       = 1'b0;
    unique case (state)
      C0:   if (coin) next_state = C1;
      C1:   if (coin) next_state = C2;
      C2:   if (coin) next_state = VEND;
      VEND: begin
              vend       = 1'b1;
              next_state = coin ? C1 : C0;
            end
    endcase
  end
endmodule

It has the same shape as the Verilog version. The differences are all about intent:

Going deeper: when not to use unique case

unique case also lets the synthesis tool assume that no other value can ever appear. For most machines that is fine. But for a machine that must recover from an unused code - after a radiation hit, say - that assumption lets the tool remove the recovery logic. For those machines, a plain case with a default: branch is safer. Volume 11 explains this in full.

To run SystemVerilog, save the file as .sv. In EDA Playground, choose a simulator that supports SystemVerilog. With Icarus Verilog, add the flag -g2012.

Common mistake

Do not assign the same variable in two different always blocks. In plain Verilog the simulator lets you, and then does something confusing. With always_ff and always_comb, the tools report it as an error - one more reason to use them.

Quick check

What does always_comb add compared with always @(*)?

Show the answer

Answer: C. always_comb states your intent: this block is combinational logic. Because the tools know the intent, they can check it, and warn you if some path would need a latch.

Try it in FSM StudioFSM Studio writes this exact template for any state table you fill in, in Verilog or SystemVerilog, with binary, Gray or one-hot codes. It opens with the drink machine from this volume.
Open FSM Studio

4.6 Your first FSM testbench

A testbench drives the inputs cycle by cycle and checks the outputs by itself, so the computer - not your eyes - decides whether the machine works.

In Volume 00 you ran a testbench that printed values for you to read. That is fine for a flip-flop. For a state machine with many paths, reading printouts by eye is slow and easy to get wrong. A self-checking testbench knows the right answers, compares them with what the design does, and prints PASS or FAIL.

A testbench for the drink machine


`timescale 1ns/1ps
module drink_tb;
  reg  clk = 0, rst = 1, coin = 0;
  wire vend;
  integer errors = 0, drinks = 0;

  drink_fsm dut (.clk(clk), .rst(rst), .coin(coin), .vend(vend));

  always #5 clk = ~clk;                        // a 10 ns clock

  task insert_coin;                            // one coin, for one cycle
    begin
      @(negedge clk) coin = 1;
      @(negedge clk) coin = 0;
    end
  endtask

  always @(posedge clk) if (vend) drinks = drinks + 1;   // count drinks

  initial begin
    $dumpfile("dump.vcd"); $dumpvars(0, drink_tb);
    repeat (2) @(negedge clk); rst = 0;        // two cycles of reset
    insert_coin; insert_coin;                  // two coins: no drink yet
    repeat (3) @(negedge clk);
    if (drinks != 0) begin $display("ERROR: a drink after 2 coins"); errors = errors + 1; end
    insert_coin;                               // the third coin
    repeat (3) @(negedge clk);
    if (drinks != 1) begin $display("ERROR: expected 1 drink, saw %0d", drinks); errors = errors + 1; end
    if (errors == 0) $display("PASS"); else $display("FAIL: %0d error(s)", errors);
    $finish;
  end
endmodule

Here is what each part does:

  1. The clock. always #5 clk = ~clk; flips the clock every 5 ns, so one cycle takes 10 ns. The first line, starting with a backtick and the word timescale, says that the numbers are nanoseconds.
  2. The design under test. drink_fsm dut (...) places one copy of the machine, and connects its ports to the testbench's signals.
  3. A task. A task is a named group of steps that you can call many times. insert_coin makes coin 1 for exactly one clock cycle.
  4. A checker. The always @(posedge clk) line counts every cycle in which vend is 1.
  5. The script. The initial block runs once, from top to bottom: reset, two coins, a check, a third coin, another check, and a final PASS or FAIL.

When the machine is correct, the simulator prints PASS. Try breaking the design on purpose - remove the vend = 1'b0; default, for instance - and watch the testbench catch it.

Try it yourselfPut drink_fsm.v in the design box and drink_tb.v in the testbench box, choose Icarus Verilog, and run it. Then add a test of your own: put in six coins in a row, and check that exactly two drinks come out.
Open EDA Playground

Why the inputs change on the falling edge

The task changes coin at @(negedge clk) - the falling edge, halfway between two rising edges. If the testbench changed coin at the very same instant as a rising edge, the simulator would be free to update the flip-flops before or after the change. The result could differ from one simulator to another. This is called a race. Changing inputs half a cycle away from the rising edge removes it, just as real signals settle well before the edge.

Common mistake

A testbench that only prints values is not a test - it is a demonstration. If nobody reads the printout carefully, a bug passes unseen. Make the testbench decide: compare every result with the expected value, and end with one clear PASS or FAIL line.

Quick check

Why does the testbench change coin at the falling edge of the clock?

Show the answer

Answer: A. The machine's flip-flops act on the rising edge. Changing inputs half a cycle away means every input is steady when the rising edge comes. The result then cannot depend on the order in which the simulator handles two events at the same instant.

What you learned

Key words from this volume

Every word below has a plain-English entry in the glossary.

Practice

Practice 1

Write the turnstile

Write the turnstile from Volume 01 in the two-process style. It has inputs coin and push, an output locked, and a synchronous reset to LOCKED.

Show the solution

module turnstile_fsm (
  input  wire clk, rst, coin, push,
  output reg  locked
);
  localparam LOCKED = 1'b0, UNLOCKED = 1'b1;
  reg state, next_state;

  always @(posedge clk)
    if (rst) state <= LOCKED;
    else     state <= next_state;

  always @(*) begin
    next_state = state;
    locked     = 1'b0;
    case (state)
      LOCKED:   begin locked = 1'b1; if (coin) next_state = UNLOCKED; end
      UNLOCKED: if (push) next_state = LOCKED;
    endcase
  end
endmodule

Check it against the full state table in sub-module 1.4. In LOCKED with both inputs 1, coin wins and the machine unlocks. In UNLOCKED with both inputs 1, push wins and it locks - the same decisions made there.

Practice 2

Find the latch

This block is meant to drive two outputs, green and red. Which output gets a latch, and how do you fix it?


always @(*) begin
  next_state = state;
  green = 1'b0;
  case (state)
    GO:   begin green = 1'b1; if (stop) next_state = HALT; end
    HALT: begin red   = 1'b1; if (go)   next_state = GO;   end
  endcase
end
Show the solution

red gets a latch. It is assigned only in HALT, and it has no default, so in GO it must keep its old value. Once red becomes 1, it would stay 1 even after the machine returns to GO. The fix is one line at the top of the block: red = 1'b0;. green is fine, because it has a default.

Practice 3

Test the edge detector

Write a self-checking testbench for a rising-edge detector with inputs clk, rst and btn, and output pulse. It should press the button once for three cycles and check that exactly one pulse appears.

Show the solution

Use the same structure as the drink machine's testbench - a clock, a counter, a script:


module edge_tb;
  reg clk = 0, rst = 1, btn = 0;
  wire pulse;
  integer pulses = 0;

  edge_detector dut (.clk(clk), .rst(rst), .btn(btn), .pulse(pulse));

  always #5 clk = ~clk;
  always @(posedge clk) if (pulse) pulses = pulses + 1;

  initial begin
    repeat (2) @(negedge clk); rst = 0;
    @(negedge clk) btn = 1;                  // press ...
    repeat (3) @(negedge clk); btn = 0;      // ... hold for three cycles, release
    repeat (3) @(negedge clk);
    if (pulses == 1) $display("PASS"); else $display("FAIL: %0d pulses", pulses);
    $finish;
  end
endmodule

A correct detector gives exactly one pulse, however long the button is held. Try holding it for ten cycles as well.

Interview corner

Interview question 1

Blocking or non-blocking?

"In your state machine, which assignment do you use in the state register, and which in the next-state logic? Why?"

Show the solution

"Non-blocking, <=, in the clocked state register, so that all the flip-flops update together at the edge, as real flip-flops do. Blocking, =, in the combinational next-state logic, because it describes gates that compute one answer. A later line can then rely on an earlier one, such as the default assignments at the top. Mixing them is the classic cause of simulations that do not match the hardware."

Interview question 2

Two-process or three-process?

"What is the difference between the two-process and the three-process FSM coding styles, and when would you choose each?"

Show the solution

"In the two-process style, the outputs are decoded by gates from the current state, so they can glitch briefly, and they add a gate delay after the clock. In the three-process style, the outputs go through their own flip-flops, fed from the next-state logic. So they change exactly at the clock edge, glitch-free, with no extra cycle of delay. I use two processes for internal control, and three when the outputs drive another block, a pin or another clock domain."

Next, Volume 05 looks at a choice you have been making quietly all along: the bit patterns of the states. It also shows why the tools may change them behind your back.