How Do You Design a Finite State Machine in Verilog, With a Testbench?

Draw the state diagram first, then write three pieces of Verilog: a clocked state register that uses nonblocking assignments and handles reset, a combinational next-state block with a default, and the output logic. Test it with a self-checking testbench that feeds directed and random inputs and compares the output against a simple reference model on every clock. The example here, a 1011 sequence detector, passes 1000 random cycles with zero mismatches in both Icarus Verilog and Verilator.

What you'll learn

  • How to turn a problem statement into states, including the overlap case
  • The three-block FSM style and why each block uses the assignment it does
  • Moore vs Mealy outputs, and which one this design uses
  • How to write a self-checking testbench with a reference model and random stimulus

Conventions and assumptions

Synchronous, active-high reset. One clock, rising edge. Verilog-2001 syntax, valid in SystemVerilog. Simulated in Icarus Verilog 12 and Verilator 5.036 (with --timing), and linted with Verilator -Wall, which reports no warnings for the design.

The problem

A serial input din arrives one bit per clock. Raise detect for one clock whenever the last four bits were 1, 0, 1, 1. Overlaps count: in 1011011 the final 1 of the first match and the next 011 form a second match, so detect goes high twice.

Step 1: the states

Each state remembers how much of the pattern has been seen so far. That gives five states for a Moore machine, where the output depends only on the state:

StateMeaningdin = 0din = 1detect
IDLEnothing usefulIDLES10
S1seen 1S10S10
S10seen 10IDLES1010
S101seen 101S10S10110
S1011seen 1011S10S11

The overlap is in the S1011 row. After 1011, a 0 means the last two bits are 10, so go to S10, not IDLE. A 1 means the latest bits end in 11, of which only the final 1 can start a new match, so go to S1. The S101 row is the other subtle one: after 101, a 0 leaves 1010, whose last two bits are 10, so S10.

Step 2: the Verilog

Three blocks. The state register is the only clocked block and uses <=. The next-state logic is combinational, uses =, and starts with a default so that no path leaves next unassigned (which would infer a latch). The output is a plain assign because this is a Moore machine.

seq_detect_1011.v
// Moore FSM: detect = 1 for one clock after the input has shown 1, 0, 1, 1
// (overlapping, so 1011011 detects twice). Synchronous, active-high reset.
module seq_detect_1011 (
  input  wire clk,
  input  wire rst,
  input  wire din,
  output wire detect
);
  localparam [2:0] IDLE  = 3'd0,  // nothing useful seen
                   S1    = 3'd1,  // seen 1
                   S10   = 3'd2,  // seen 10
                   S101  = 3'd3,  // seen 101
                   S1011 = 3'd4;  // seen 1011: output high

  reg [2:0] state, next;

  // 1. State register: the only clocked block, non-blocking.
  always @(posedge clk) begin
    if (rst) state <= IDLE;
    else     state <= next;
  end

  // 2. Next-state logic: combinational, blocking, with a default.
  always @(*) begin
    next = state;
    case (state)
      IDLE:    next = din ? S1    : IDLE;
      S1:      next = din ? S1    : S10;
      S10:     next = din ? S101  : IDLE;
      S101:    next = din ? S1011 : S10;
      S1011:   next = din ? S1    : S10;
      default: next = IDLE;
    endcase
  end

  // 3. Output logic: Moore, so it depends on the state alone.
  assign detect = (state == S1011);
endmodule

Moore or Mealy?

In a Mealy machine the output depends on the state and the current input, so detect could go high in the same clock that the final 1 arrives, with one fewer state (S101 plus din = 1). The cost is that the output can glitch with the input and passes combinational logic straight from din to detect. The Moore version here is one clock later but its output comes straight from the state register's decode, which is easier to time and to reason about.

Step 3: a self-checking testbench

Rather than reading waveforms, the testbench keeps the last four input bits in a shift register and works out what detect should be. After every clock it compares the design against that model and counts mismatches. It runs a directed sequence first (1011011, expecting two detections) and then 1000 random bits.

tb_seq_detect_1011.v
`timescale 1ns/1ps
module tb_seq_detect_1011;
  reg clk = 0, rst = 1, din = 0;
  wire detect;
  reg [3:0] history = 4'b0000;   // the last four inputs, newest in bit 0
  reg expected = 0;
  integer i, errors = 0, hits = 0;

  seq_detect_1011 dut (.clk(clk), .rst(rst), .din(din), .detect(detect));

  always #5 clk = ~clk;

  // Reference model: after each edge, detect should be 1 exactly when the
  // last four inputs sampled were 1, 0, 1, 1.
  always @(posedge clk) begin
    if (rst) begin
      history  <= 4'b0000;
      expected <= 1'b0;
    end else begin
      history  <= {history[2:0], din};
      expected <= ({history[2:0], din} == 4'b1011);
    end
  end

  task check;
    begin
      if (detect !== expected) begin
        errors = errors + 1;
        $display("MISMATCH at %0t: din history %b, detect %b, expected %b", $time, history, detect, expected);
      end
      if (detect) hits = hits + 1;
    end
  endtask

  initial begin
    // Hold reset for two edges, then a directed sequence with an overlap.
    @(negedge clk); @(negedge clk); rst = 0;
    // 1011011 should detect twice (overlapping).
    for (i = 6; i >= 0; i = i - 1) begin
      din = (7'b1011011 >> i) & 1'b1;
      @(negedge clk); check;
    end
    $display("directed: %0d detections (expect 2)", hits);
    // Then 1000 random bits, checked against the model on every clock.
    for (i = 0; i < 1000; i = i + 1) begin
      din = $random;
      @(negedge clk); check;
    end
    $display("random: %0d errors in 1000 cycles, %0d detections in total", errors, hits);
    if (errors == 0) $display("PASS"); else $display("FAIL");
    $finish;
  end
endmodule

What the simulation shows

Both simulators print "directed: 2 detections (expect 2)", then "random: 0 errors in 1000 cycles" and PASS. The number of random detections depends on the simulator's random sequence; it should be close to 1000/16 = 62.5, since any four random bits are 1011 with probability 1/16. Icarus Verilog gave 62 in total including the two directed ones.

Why each block uses what it does

  • State register: <=, because it is clocked (see blocking vs nonblocking).
  • Next-state logic: =, with next = state as the first line, so every path assigns next and no latch is inferred.
  • A default branch in the case: if the state register ever holds an unused code (5, 6 or 7 here), the machine goes back to IDLE rather than getting stuck.
  • Named states with localparam: the code reads like the table, and you can change the encoding in one place.

Common mistakes

  • Going to IDLE on every mismatch, which misses overlapping matches such as 1011011.
  • Leaving next unassigned on some path in the combinational block, which infers a latch.
  • Putting the output in the clocked block by accident, which delays it by one more clock.
  • A testbench that only prints values, so nobody notices when they are wrong. Compare against a model and count errors.

Check your understanding

  • Q1. The input is 10111011. How many times does detect go high? Answer: twice, after the fourth bit and after the eighth.
  • Q2. Which state does the machine go to from S10 on din = 0, and why? Answer: IDLE. The last two bits are 00, and no suffix of 100 starts the pattern.
  • Q3. How many states does a Mealy version need? Answer: four: IDLE, S1, S10 and S101. The output is high while in S101 with din = 1, so S1011 is not needed.

Related

Sources

Last verified

Design and testbench simulated in Icarus Verilog 12 and Verilator 5.036 on 14 November 2026, both passing with zero mismatches; the design lints cleanly with Verilator -Wall.

Keep going