Sequence detector for 1011
A Moore state machine that finds 1011 in a serial bit stream, overlaps included. Five states, ten arrows, and the one state that reports a match.
- Level
- rtl
- Code
- sequence-detector-1011
- Checked
- Verilator 5.052, Icarus Verilog 13.0, Yosys 0.66
- Published
- Authorship
- Drafted with AI assistance
- Status
- In progress — published so it can be reviewed, not finished.
A sequence detector watches one bit per clock on sig_in and says when the last few bits
spelled a pattern — here 1011. Each state is how much of the pattern the machine has seen so
far: nothing (IDLE), then 1, 10, 101 and 1011. It is a Moore machine: found is a
property of the state S1011 and of nothing else, so it is high for exactly the clock period
the machine spends there.
The arrows are labelled with the value of sig_in that takes them, 1 or 0, where the code
tests sig_in and !sig_in.
Run plays this scenario from the start; driving an input continues from here.
A state machine that runs: a state diagram and its waveform on one time axis. Inputs: rst_n, sig_in. Outputs: found. clk runs at 8 ticks a period. It has 5 states, IDLE, S1, S10, S101 and S1011, and starts in IDLE on reset; 10 transitions. The waveform shows clk, rst_n, sig_in, state, found.
Figure 1. Press Run. sig_in carries 1, 0, 1 and 1 into four rising edges, and the lit state walks the pattern to S1011.
How it works, edge by edge
At the start rst_n is low, and the reset is asynchronous: the register is IDLE at once,
with no clock edge needed. The reset is released at tick 2, between edges, so nothing moves
until the first rising edge.
At edge 0 sig_in is 1, so the machine takes the arrow from IDLE to S1. That arrow stays
heavy until the next edge: it is the transition that fired. The state row of the waveform
prints the state's name, and the digits it stands for are the encoding, three bits counting
states in the order the code declares them.
At edges 1 and 2 the bits are 0 and 1, and the machine reaches S10 and then S101. At edge 3
the fourth bit, a 1, takes it to S1011, and found rises with it. Look at when: not while
the fourth bit is on sig_in, but at the edge that samples it. A Moore output waits for the
state.
Overlap plays 1011011. After the first match the next bit is 0, and the machine goes to
S10, not to IDLE: the 1 at the end of 1011 and the 0 after it are the start of the
next 1011. Three edges later it is found again, starting on the last bit of the first.
No reset never asserts rst_n. The register holds no known value, so every state is drawn
dashed — the register could hold any of their codes, or one of the three that name none — and
the state row is hatched. found is X too, because one of those states is S1011. While
sig_in stays 1 nothing changes: a 1 leads to S1, S101 or S1011 depending on where the
machine was, and their codes, 001, 011 and 100, differ in every bit, so the register stays
X. Press sig_in once, to 0, and run two edges: every state goes to IDLE on two 0s, and the
machine finds its way out of X without a reset. 00 is the shortest synchronizing sequence for
this machine, and its only two-bit one: whatever the register starts in, S1011 or a code that
names no state, those two bits leave it in IDLE. That says something about this machine, not
about resets.
The code
// Detects the bit sequence 1011 on sig_in, with overlap: 1011011 is found twice.
//
// Reset style for this module: asynchronous, active low. One style per page,
// stated up front, so nothing on the page has to be read twice.
module sequence_detector (
input logic clk,
input logic rst_n,
input logic sig_in,
output logic found
);
typedef enum logic [2:0] {
IDLE = 3'd0,
S1 = 3'd1,
S10 = 3'd2,
S101 = 3'd3,
S1011 = 3'd4
} state_t;
state_t state, next;
// The state register: the only flip-flops in the module.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
end else begin
state <= next;
end
end
// Next state. The default assignment holds the state when no branch fires,
// so no arm can leave a latch behind.
always_comb begin
next = state;
unique case (state)
IDLE: begin
if (sig_in) next = S1;
else if (!sig_in) next = IDLE;
end
S1: begin
if (!sig_in) next = S10;
else if (sig_in) next = S1;
end
S10: begin
if (sig_in) next = S101;
else if (!sig_in) next = IDLE;
end
S101: begin
if (sig_in) next = S1011;
else if (!sig_in) next = S10;
end
S1011: begin
if (!sig_in) next = S10;
else if (sig_in) next = S1;
end
default: next = IDLE;
endcase
end
// Moore output: a function of the state alone.
always_comb begin
found = 1'b0;
unique case (state)
S1011: found = 1'b1;
default: found = 1'b0;
endcase
end
endmoduleThe code and the drawing are one thing. The typedef enum lists the states, the always_ff
block is the only register the machine has, each arm of the first case is a state and each
if or else if line is one arrow. Point at a state and its arm lights; point at an arrow and
its line does; the reset marker lights the register. The FSM viewer draws a
state machine of your own from its code, linked the same way.
The next-state block opens with next = state;, so a state whose conditions are all false
keeps its state rather than leaving a latch behind. Here every state draws both of its arrows,
so that default never decides anything; it is there so that no arm can forget. The output block
is separate and reads the state alone, which is what makes found a Moore output.
Synthesis result
Yosys's generic synthesis turns sequence_detector.sv into 17 cells, 3 of them flip-flops. The cells are Yosys's own gate library, not an FPGA's or a standard-cell library's: the count measures the logic, not the area of any chip.
The code gives state a 3-bit encoding, and these numbers keep it. Left to its fsm pass, Yosys re-encodes it one-hot: 5 flip-flops, 14 cells in all.
| Cell | What it is | Count |
|---|---|---|
$_DFF_PN0_ | D flip-flop on the rising edge, active-low asynchronous reset to 0 | 3 |
$_AND_ | 2-input AND | 2 |
$_ANDNOT_ | AND with its second input inverted, A & ~B | 4 |
$_NAND_ | 2-input NAND | 5 |
$_NOR_ | 2-input NOR | 2 |
$_OR_ | 2-input OR | 1 |
| Total | 17 | |
Lint by Verilator 5.052, the testbench by Icarus Verilog 13.0, synthesis by Yosys 0.66.
Icarus replayed two of this page's three scenarios on this code, and all 470 values the waveform draws for them match. No reset is left out: it starts from an unknown register, which an RTL simulation reads more optimistically than this engine.
The netlist before synthesis
The netlist of sequence_detector.sv, drawn by netlistsvg 1.0.2, is 760 × 765 px, too large to read inline: open it on its own, where the browser can zoom.
Pitfalls
Forgetting the overlap. An arrow from S1011 back to IDLE on 0 looks tidier and finds
1011011 only once. Every arrow out of a partial match has to go to the longest suffix of what
has been seen that is still a prefix of the pattern. For another pattern, the
sequence detector generator builds the machine by this rule,
overlapping or not, Moore or Mealy, with its code.
Expecting found a cycle early. A Mealy version, with found on the arrow from S101 on 1,
would raise it while the fourth bit is still arriving, a clock period sooner — and
combinationally from sig_in, so any glitch on the input reaches the output.
No reset. The X is this figure's answer, and an RTL simulation gives another. Run the same
code with rst_n never asserted and the X state matches no case item, so the default: arm
is taken: next is IDLE and found is 0 from time 0, and the register is X until the first
edge and IDLE after it. The simulation looks healthy. On an ASIC the register powers up in any
code, the three unused ones included, and in S1011 it reports found for a cycle before a
single bit has been seen. On most FPGAs, configuration loads each flip-flop's initial value, 0
unless the code sets one, so this machine starts in IDLE; but that value is loaded once, at
configuration, and it is not a reset the design can assert. This X-optimism is why a missing reset survives RTL
simulation.
Check yourself
- The machine is in S1011 and the next bit is 0. Why does it go to S10 and not back to IDLE?
- Because the last two bits it has seen, 1 and then 0, are the first two bits of 1011 again. Going to IDLE would throw them away, and 1011011 would be found once instead of twice. That is what overlap means.
- The fourth bit of the pattern is on sig_in for a whole clock period. Why does found not rise until the edge at the end of that period?
- Because found is a Moore output, a function of the state alone. The machine enters S1011 at the edge that samples the fourth bit, and found follows the state, so it rises at that edge and not while the bit is arriving.
- In the No reset scenario found is X, not 0. Why?
- Nothing has put the register in a known state, so the machine could be in any of its five states, or one of the three codes that name none, and S1011 is among them, where found is 1. An output that is 0 in some of those and 1 in another is unknown.
Challenge: S101 twice
You drive sig_in, nothing else. The machine must be in S101 at the edges numbered 3 and 5, and found must be 1 at the edge numbered 6.
- Your input
-
- sig_in, starting at 0
- The script
-
- rst_n starts at 0.
- rst_n becomes 1 before the edge numbered 0.
- The goal
-
- state is S101 at the edge numbered 3.
- state is S101 at the edge numbered 5.
- found is 1 at the edge numbered 6.
With JavaScript on, Start plays this challenge in the figure.