RTL Design Patterns · Chapter 4 · FSM Design
Moore vs Mealy
A finite state machine has a state register, next-state logic, and output logic, and the one question that separates Moore from Mealy is where the output logic gets its inputs. A Moore output depends on the state only, so it is registered and glitch-free but lands one cycle after the input that decided it. A Mealy output depends on the state and the current input, so it responds the same cycle and needs fewer states, but it is combinational, so it can glitch and it puts the input on a straight path to the outputs. Choosing wrong costs you a late response, a glitchy strobe, or a path that fails timing. You build the same small machine both ways, study the Mealy glitch, and learn the registered-Mealy compromise, in SystemVerilog, Verilog, and VHDL.
Intermediate16 min readRTL Design PatternsMoore FSMMealy FSMOutput TimingCombinational GlitchRegistered Mealy
Chapter 4 · Section 4.2 · FSM Design
1. The Engineering Problem
You are building a serial-line watcher: a single-bit input in arrives one bit per clock, and the moment two consecutive 1s go by, you must raise a one-cycle out strobe that tells a downstream block "the pattern happened." The state machine is trivial — you learned to build exactly this shape in 4.1: a state register, next-state logic that walks a couple of states, and output logic that raises out. You write it, it simulates, out pulses, and you move on.
Then integration finds a problem that has nothing to do with whether you detect the pattern and everything to do with when and how the strobe appears. In one reviewer's build, out fires the cycle after the second 1 — a cycle too late for the downstream block, which needed to act on the same cycle. In another engineer's build, out fires the same cycle — but static timing flags a long path from the in pin straight through to out, because the output now depends combinationally on the live input, and that build fails timing at speed. In a third, out fires the same cycle and meets timing, but a downstream edge-triggered counter occasionally records two events for one real one, because out briefly glitched high while in was still settling and an adjacent flop caught the glitch. Three engineers, one specification, three different failures — and the only thing that differed between them was the answer to a single question: does the output depend on the state alone, or on the state and the current input?
That question is the Moore-versus-Mealy choice, and the point of this page is that it is not a definition to memorize — it is a hardware trade-off you make deliberately. Two FSMs can implement the same behaviour yet differ in when their outputs appear (a Moore output is a cycle later than the equivalent Mealy output), in how clean they are (a Moore output is registered and glitch-free; a Mealy output is combinational and can glitch), and in what path they create (a Mealy output puts the input on a combinational path straight to the outputs). Pick the wrong one for the situation and you get a late response, a glitchy strobe, or a failing path. The rest of this page is how to choose on purpose.
// Detect two consecutive 1s on serial `in`, raise a one-cycle `out`.
// The state machine is the same in both styles; only the OUTPUT tap differs:
// MOORE: out depends on STATE only. out is registered behind the state flops.
// -> glitch-free, times like datapath, but fires the cycle AFTER the 2nd 1.
// assign out = (state == GOT_TWO);
// MEALY: out depends on STATE and the CURRENT input `in`.
// -> fires the SAME cycle as the 2nd 1, fewer states, but out is COMBINATIONAL:
// it can glitch as `in` settles, and `in` now drives a path straight to `out`.
// assign out = (state == GOT_ONE) & in;
// Same function. Different WHEN (latency), different HOW (glitch, input->out path).
// The choice is a trade-off, not a label — this page makes it deliberate.2. Mental Model
3. Pattern Anatomy
The three FSM pieces from 4.1 are unchanged. Moore vs Mealy is entirely about the output logic — the third piece — and specifically about what it reads and whether its result is registered.
Where the output logic taps. In a Moore machine the output logic reads the state register only: out = f(state). In a Mealy machine it reads the state register and the current input: out = f(state, in). That is the entire structural difference — one extra wire (the live input) feeding the output block. Everything below is a consequence of that one wire.
The latency difference — a cycle, and where it comes from. Consider the deciding input arriving on cycle N. In a Mealy machine the output logic sees that input on cycle N and can raise out on cycle N — the same cycle. In a Moore machine the input on cycle N can only change the next state, which the state register loads on the edge into cycle N+1; the output, reading only the state, therefore cannot reflect the input until cycle N+1. A Moore output is valid the cycle after the deciding input; a Mealy output is valid the same cycle. That single cycle is not a bug in either — it is the defining timing property of each, and the reason a Moore detector often needs one more state (a dedicated "I just saw it" state to hang the output on) than the equivalent Mealy detector.
Glitches on the Mealy combinational output. Because the Mealy output is a combinational function of the live input, it inherits the hazards of combinational logic. As in transitions and the logic settles, out can momentarily glitch — go high and back, or high a beat early — before it reaches its steady value for the cycle. In a purely synchronous world where out feeds another flop's D and is sampled only at the clock edge, that glitch is harmless (it has settled by the edge). But the instant that Mealy output feeds something edge-sensitive — a downstream flop's clock or async set/reset, an asynchronous enable, a counter that increments on a rising edge of out — the glitch is a real, spurious event. The Moore output, being registered behind the state flops, is glitch-free by construction.
The input-to-output combinational path. The Mealy output puts the input on a straight combinational path: in -> output logic -> out, and if out then feeds combinational logic in the next block, the path extends further. Static timing sees this as a real path from an input pin (or an upstream register) all the way to out (or the next register), and on a fast clock it can be the critical path — the classic "my Mealy FSM fails timing on the input-to-output arc" problem. A Moore output has no such path: the input reaches only the next-state logic, and the output is fed by the (already registered) state, so the input-to-output arc is broken by the state register.
The registered-Mealy compromise. You do not have to choose between Mealy's low latency and Moore's clean output. Register the Mealy output: compute the Mealy decision combinationally (out_comb = f(state, in)) and flop it, so the design sees a registered out_reg. The registered output is glitch-free and breaks the input-to-output combinational path (the flop is the endpoint), recovering Moore's cleanliness — but it re-adds one cycle of latency on that output, landing it a cycle after the pure-Mealy output. It is the pragmatic default when you want fast internal reactivity but a clean, timing-safe output leaving the block.
Moore vs Mealy — same machine, the output taps in two places
data flowThe second visual makes the timing concrete: drive the same input sequence into both machines and watch when each raises its output, and what the Mealy output does mid-cycle.
Output timing on the same input sequence — Mealy same cycle, Moore next cycle
data flow4. Real RTL Implementation
This is the core of the page. We build the same small machine — a detector that raises a one-cycle out when it sees two consecutive 1s on serial in — three ways: (a) as a Moore FSM (output from state only), (b) as a Mealy FSM (output from state and input), and (c) the Mealy-glitch bug beside its fix (raw Mealy combinational output feeding edge-sensitive logic vs a Moore / registered-Mealy output). Each is shown in SystemVerilog, Verilog, and VHDL with an RTL block and a self-checking clocked testbench. Building the same function both ways is what makes the timing contrast concrete rather than definitional.
Two conventions run through every block. The reset is a synchronous, active-high rst (as in 2.3) that returns the machine to its idle state; the state is a small enumerated type (typedef enum / localparam / a VHDL enumerated type) named for what it remembers — S_IDLE (seen nothing useful), S_GOT1 (just saw a single 1). The Moore machine needs a third state, S_GOT2, to hang its registered output on; the Mealy machine does not — it raises out combinationally on the transition into the "two-in-a-row" condition, which is exactly the one-state saving Mealy buys.
4a. The Moore FSM — output from state only
The Moore detector uses three states and taps out from the state register alone: out = (state == S_GOT2). Because the output reads only the state, it is glitch-free and lands the cycle after the second 1 — the cycle in which the machine has entered S_GOT2.
module seq_moore (
input logic clk,
input logic rst, // synchronous, active-high
input logic in, // serial input, one bit per clock
output logic out // 1-cycle strobe: seen two 1s in a row
);
typedef enum logic [1:0] {S_IDLE, S_GOT1, S_GOT2} state_t;
state_t state, next_state;
// NEXT-STATE logic: f(state, in) — identical intent to the Mealy version below.
always_comb begin
next_state = state; // default: hold (latch-free combinational block)
unique case (state)
S_IDLE: next_state = in ? S_GOT1 : S_IDLE;
S_GOT1: next_state = in ? S_GOT2 : S_IDLE; // second 1 -> enter GOT2
S_GOT2: next_state = in ? S_GOT2 : S_IDLE; // stay while 1s keep coming
endcase
end
// STATE register: synchronous reset to idle.
always_ff @(posedge clk)
if (rst) state <= S_IDLE;
else state <= next_state;
// OUTPUT logic — MOORE: reads the STATE ONLY. `out` is effectively registered
// behind the state flops => glitch-free, but it is high the cycle AFTER the
// second 1 (the cycle the machine has ENTERED S_GOT2).
assign out = (state == S_GOT2);
endmoduleThe clocked, self-checking testbench drives a fixed input sequence and checks out cycle by cycle against a reference of when the Moore output should fire — one cycle after each completed pair.
module seq_moore_tb;
logic clk = 1'b0, rst, in, out;
int errors = 0;
seq_moore dut (.clk(clk), .rst(rst), .in(in), .out(out));
always #5 clk = ~clk;
// Drive in = 0 1 1 0 1 1 1 0. Moore `out` is high the cycle AFTER each 2nd 1.
// Expected out, aligned one cycle later than the deciding input, is checked
// just before each rising edge (after `out` for the previous input has settled).
localparam int LEN = 8;
logic [LEN-1:0] seq_in = 8'b0_1_1_0_1_1_1_0; // read MSB-first below
logic [LEN-1:0] seq_out = 8'b0_0_1_0_0_1_1_0; // Moore: one cycle after each pair
initial begin
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
for (int i = LEN-1; i >= 0; i--) begin
in = seq_in[i];
@(posedge clk); #1; // sample AFTER the edge that loaded the state
assert (out === seq_out[i])
else begin $error("cycle %0d: in=%b out=%b exp=%b", LEN-1-i, in, out, seq_out[i]); errors++; end
end
if (errors == 0) $display("PASS: Moore out asserts one cycle after each detected pair");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog form is the same three-state machine with localparam state codes and reg/wire typing; out is a continuous assign off the state, so it is equally registered-clean.
module seq_moore (
input wire clk,
input wire rst,
input wire in,
output wire out
);
localparam S_IDLE = 2'd0, S_GOT1 = 2'd1, S_GOT2 = 2'd2;
reg [1:0] state, next_state;
always @(*) begin
next_state = state; // default hold => no latch
case (state)
S_IDLE: next_state = in ? S_GOT1 : S_IDLE;
S_GOT1: next_state = in ? S_GOT2 : S_IDLE;
S_GOT2: next_state = in ? S_GOT2 : S_IDLE;
default: next_state = S_IDLE;
endcase
end
always @(posedge clk)
if (rst) state <= S_IDLE;
else state <= next_state;
// MOORE output: state only => registered-clean, high the cycle AFTER the 2nd 1.
assign out = (state == S_GOT2);
endmodule module seq_moore_tb;
reg clk, rst, in;
wire out;
integer i, errors;
reg [7:0] seq_in, seq_out;
seq_moore dut (.clk(clk), .rst(rst), .in(in), .out(out));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
seq_in = 8'b0_1_1_0_1_1_1_0;
seq_out = 8'b0_0_1_0_0_1_1_0; // Moore: one cycle after each pair
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
for (i = 7; i >= 0; i = i - 1) begin
in = seq_in[i];
@(posedge clk); #1;
if (out !== seq_out[i]) begin
$display("FAIL: cycle %0d in=%b out=%b exp=%b", 7-i, in, out, seq_out[i]);
errors = errors + 1;
end
end
if (errors == 0) $display("PASS: Moore out one cycle after each pair");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the states are an enumerated type, the state register uses rising_edge(clk) with a synchronous reset, and the Moore output is a concurrent assignment off the state signal.
library ieee;
use ieee.std_logic_1164.all;
entity seq_moore is
port (
clk : in std_logic;
rst : in std_logic; -- synchronous, active-high
d : in std_logic; -- serial input ('in' is reserved)
q : out std_logic -- 1-cycle strobe
);
end entity;
architecture rtl of seq_moore is
type state_t is (S_IDLE, S_GOT1, S_GOT2);
signal state, next_state : state_t;
begin
-- NEXT-STATE logic: f(state, d).
process (state, d)
begin
next_state <= state; -- default hold
case state is
when S_IDLE => if d = '1' then next_state <= S_GOT1; else next_state <= S_IDLE; end if;
when S_GOT1 => if d = '1' then next_state <= S_GOT2; else next_state <= S_IDLE; end if;
when S_GOT2 => if d = '1' then next_state <= S_GOT2; else next_state <= S_IDLE; end if;
end case;
end process;
-- STATE register: synchronous reset to idle.
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then state <= S_IDLE; else state <= next_state; end if;
end if;
end process;
-- MOORE output: state only => registered-clean, high the cycle AFTER the 2nd 1.
q <= '1' when state = S_GOT2 else '0';
end architecture; library ieee;
use ieee.std_logic_1164.all;
entity seq_moore_tb is
end entity;
architecture sim of seq_moore_tb is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal d : std_logic := '0';
signal q : std_logic;
constant SEQ_IN : std_logic_vector(0 to 7) := "01101110";
constant SEQ_OUT : std_logic_vector(0 to 7) := "00100110"; -- Moore: one cycle after each pair
begin
dut : entity work.seq_moore port map (clk => clk, rst => rst, d => d, q => q);
clkgen : process
begin
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end process;
stim : process
begin
rst <= '1'; d <= '0';
wait until rising_edge(clk); wait for 1 ns; rst <= '0';
for i in 0 to 7 loop
d <= SEQ_IN(i);
wait until rising_edge(clk); wait for 1 ns;
assert q = SEQ_OUT(i)
report "Moore mismatch: q /= expected this cycle" severity error;
end loop;
report "seq_moore self-check complete" severity note;
wait;
end process;
end architecture;4b. The Mealy FSM — output from state and input
The Mealy detector implements the same function with two states. It taps out from the state and the live input: out = (state == S_GOT1) & in — the instant a second 1 arrives while in S_GOT1, out goes high this cycle, without waiting to enter a third state. That is the same-cycle response and the one-state saving; the cost is that out is now combinational.
module seq_mealy (
input logic clk,
input logic rst,
input logic in,
output logic out // COMBINATIONAL: high the SAME cycle as the 2nd 1
);
typedef enum logic [0:0] {S_IDLE, S_GOT1} state_t; // one fewer state than Moore
state_t state, next_state;
always_comb begin
next_state = state;
unique case (state)
S_IDLE: next_state = in ? S_GOT1 : S_IDLE;
S_GOT1: next_state = in ? S_GOT1 : S_IDLE; // consecutive 1s keep us in GOT1
endcase
end
always_ff @(posedge clk)
if (rst) state <= S_IDLE;
else state <= next_state;
// OUTPUT logic — MEALY: reads STATE and the LIVE input. `out` is high the SAME
// cycle the second 1 arrives (in S_GOT1 with in=1). It is COMBINATIONAL: it can
// glitch as `in` settles, and `in` now drives a path straight to `out`.
assign out = (state == S_GOT1) && in;
endmoduleThe self-checking testbench drives the same sequence as 4a but checks out on the same cycle as the deciding input — the Mealy output is high the cycle the second 1 is present, one cycle earlier than the Moore reference. Aligning the two expectation vectors is the concrete proof of the latency difference.
module seq_mealy_tb;
logic clk = 1'b0, rst, in, out;
int errors = 0;
seq_mealy dut (.clk(clk), .rst(rst), .in(in), .out(out));
always #5 clk = ~clk;
// SAME input as the Moore TB: 0 1 1 0 1 1 1 0. But Mealy `out` is high the SAME
// cycle as each 2nd 1 => the expectation vector is shifted one cycle EARLIER.
localparam int LEN = 8;
logic [LEN-1:0] seq_in = 8'b0_1_1_0_1_1_1_0;
logic [LEN-1:0] seq_out = 8'b0_0_1_0_0_1_1_0; // note: aligned to the input cycle now
initial begin
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
for (int i = LEN-1; i >= 0; i--) begin
in = seq_in[i];
#1; // let the combinational Mealy output settle
assert (out === seq_out[i])
else begin $error("cycle %0d: in=%b out=%b exp=%b", LEN-1-i, in, out, seq_out[i]); errors++; end
@(posedge clk); // advance state for the next input bit
end
if (errors == 0) $display("PASS: Mealy out asserts the SAME cycle as the 2nd 1");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog Mealy form is the same two-state machine; out is a continuous assign off state and in, so it is combinational by construction.
module seq_mealy (
input wire clk,
input wire rst,
input wire in,
output wire out
);
localparam S_IDLE = 1'd0, S_GOT1 = 1'd1;
reg state, next_state;
always @(*) begin
next_state = state;
case (state)
S_IDLE: next_state = in ? S_GOT1 : S_IDLE;
S_GOT1: next_state = in ? S_GOT1 : S_IDLE;
default: next_state = S_IDLE;
endcase
end
always @(posedge clk)
if (rst) state <= S_IDLE;
else state <= next_state;
// MEALY output: state AND input => same cycle, combinational (can glitch).
assign out = (state == S_GOT1) & in;
endmodule module seq_mealy_tb;
reg clk, rst, in;
wire out;
integer i, errors;
reg [7:0] seq_in, seq_out;
seq_mealy dut (.clk(clk), .rst(rst), .in(in), .out(out));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
seq_in = 8'b0_1_1_0_1_1_1_0;
seq_out = 8'b0_0_1_0_0_1_1_0; // Mealy: aligned to the SAME cycle as the input
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
for (i = 7; i >= 0; i = i - 1) begin
in = seq_in[i];
#1; // settle the combinational output
if (out !== seq_out[i]) begin
$display("FAIL: cycle %0d in=%b out=%b exp=%b", 7-i, in, out, seq_out[i]);
errors = errors + 1;
end
@(posedge clk);
end
if (errors == 0) $display("PASS: Mealy out same cycle as the 2nd 1");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the Mealy output is a concurrent assignment reading both state and the input d, so it responds combinationally within the cycle.
library ieee;
use ieee.std_logic_1164.all;
entity seq_mealy is
port (
clk : in std_logic;
rst : in std_logic;
d : in std_logic;
q : out std_logic -- combinational Mealy output
);
end entity;
architecture rtl of seq_mealy is
type state_t is (S_IDLE, S_GOT1); -- one fewer state than Moore
signal state, next_state : state_t;
begin
process (state, d)
begin
next_state <= state;
case state is
when S_IDLE => if d = '1' then next_state <= S_GOT1; else next_state <= S_IDLE; end if;
when S_GOT1 => if d = '1' then next_state <= S_GOT1; else next_state <= S_IDLE; end if;
end case;
end process;
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then state <= S_IDLE; else state <= next_state; end if;
end if;
end process;
-- MEALY output: state AND input => same cycle, combinational.
q <= '1' when (state = S_GOT1 and d = '1') else '0';
end architecture; library ieee;
use ieee.std_logic_1164.all;
entity seq_mealy_tb is
end entity;
architecture sim of seq_mealy_tb is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal d : std_logic := '0';
signal q : std_logic;
constant SEQ_IN : std_logic_vector(0 to 7) := "01101110";
constant SEQ_OUT : std_logic_vector(0 to 7) := "00100110"; -- Mealy: same cycle as input
begin
dut : entity work.seq_mealy port map (clk => clk, rst => rst, d => d, q => q);
clkgen : process
begin
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end process;
stim : process
begin
rst <= '1'; d <= '0';
wait until rising_edge(clk); wait for 1 ns; rst <= '0';
for i in 0 to 7 loop
d <= SEQ_IN(i);
wait for 1 ns; -- settle the combinational output
assert q = SEQ_OUT(i)
report "Mealy mismatch: q /= expected same cycle" severity error;
wait until rising_edge(clk);
end loop;
report "seq_mealy self-check complete" severity note;
wait;
end process;
end architecture;4c. The Mealy-glitch bug — raw combinational output vs Moore / registered-Mealy fix
Pattern (c) is the flagship failure, shown as buggy vs fixed RTL in each language and dramatized in §7. The bug is not in the FSM: it is in what consumes the Mealy output. The buggy design routes the raw combinational Mealy out into something edge-sensitive — here, the clock (or a rising-edge count) of a downstream counter — so a mid-cycle glitch on out is latched as a spurious event. The fix is to feed the consumer a glitch-free output instead: either the Moore output (registered by construction) or a registered-Mealy output (flop the Mealy decision), each of which is stable and only changes on a clock edge. A zero-delay RTL sim will not itself produce the analog glitch, so the comment marks where the hazard lives; §5 covers how gate-level simulation and static timing expose it.
// BUGGY: the raw COMBINATIONAL Mealy output drives an edge-sensitive consumer.
// As `in` settles, mealy_out can GLITCH high-and-back; the downstream counter
// that increments on a rising edge of mealy_out records a SPURIOUS event.
module use_mealy_bad (
input logic clk, rst, in,
output logic [7:0] event_count
);
logic mealy_out;
seq_mealy u_fsm (.clk(clk), .rst(rst), .in(in), .out(mealy_out));
// DANGER: mealy_out is combinational; using it as an edge trigger latches glitches.
always_ff @(posedge mealy_out or posedge rst) // edge-sensitive on a combinational net!
if (rst) event_count <= '0;
else event_count <= event_count + 8'd1; // may count a glitch as a real event
endmodule
// FIXED: REGISTER the Mealy decision first, then let the (now glitch-free) registered
// output drive the consumer synchronously — no combinational net feeds an edge.
module use_mealy_good (
input logic clk, rst, in,
output logic [7:0] event_count
);
logic mealy_out, mealy_out_reg;
seq_mealy u_fsm (.clk(clk), .rst(rst), .in(in), .out(mealy_out));
always_ff @(posedge clk) begin
if (rst) mealy_out_reg <= 1'b0;
else mealy_out_reg <= mealy_out; // registered-Mealy: clean, +1 cycle
end
// Count synchronously on the clock, qualified by the REGISTERED (glitch-free) pulse.
always_ff @(posedge clk)
if (rst) event_count <= '0;
else if (mealy_out_reg) event_count <= event_count + 8'd1;
endmodule module mealy_glitch_tb;
logic clk = 1'b0, rst, in;
logic [7:0] event_count;
int errors = 0;
// Bind to use_mealy_good: counts exactly the real pairs. use_mealy_bad can
// over-count when the combinational Mealy output glitches into the edge trigger
// (a hazard a gate-level sim exposes — see section 5).
use_mealy_good dut (.clk(clk), .rst(rst), .in(in), .event_count(event_count));
always #5 clk = ~clk;
initial begin
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
// Drive 0 1 1 0 1 1 1 0 -> the pattern completes on cycles marked below.
in = 1'b0; @(posedge clk);
in = 1'b1; @(posedge clk);
in = 1'b1; @(posedge clk); // first pair
in = 1'b0; @(posedge clk);
in = 1'b1; @(posedge clk);
in = 1'b1; @(posedge clk); // second pair
in = 1'b1; @(posedge clk); // still 1s (overlapping, one more pair)
in = 1'b0; @(posedge clk); #1;
// Expect exactly 3 counted events for this stream; a glitch would inflate it.
assert (event_count === 8'd3)
else begin $error("event_count=%0d exp=3 (glitch over-count?)", event_count); errors++; end
if (errors == 0) $display("PASS: registered pulse counts exactly the real events");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog buggy/fixed pair is the same story: the bad module clocks a counter off the combinational Mealy net; the good module registers the pulse and counts synchronously.
// BUGGY: combinational Mealy output used as an edge trigger -> glitch counts as event.
module use_mealy_bad (
input wire clk, rst, in,
output reg [7:0] event_count
);
wire mealy_out;
seq_mealy u_fsm (.clk(clk), .rst(rst), .in(in), .out(mealy_out));
always @(posedge mealy_out or posedge rst) // edge on a COMBINATIONAL net -> hazard
if (rst) event_count <= 8'd0;
else event_count <= event_count + 8'd1;
endmodule
// FIXED: register the Mealy pulse, then count synchronously off the clock.
module use_mealy_good (
input wire clk, rst, in,
output reg [7:0] event_count
);
wire mealy_out;
reg mealy_out_reg;
seq_mealy u_fsm (.clk(clk), .rst(rst), .in(in), .out(mealy_out));
always @(posedge clk)
if (rst) mealy_out_reg <= 1'b0;
else mealy_out_reg <= mealy_out; // registered-Mealy: glitch-free, +1 cycle
always @(posedge clk)
if (rst) event_count <= 8'd0;
else if (mealy_out_reg) event_count <= event_count + 8'd1;
endmodule module mealy_glitch_tb;
reg clk, rst, in;
wire [7:0] event_count;
integer errors;
// Bind to use_mealy_good (clean). use_mealy_bad can over-count on a glitch.
use_mealy_good dut (.clk(clk), .rst(rst), .in(in), .event_count(event_count));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
rst = 1'b1; in = 1'b0;
@(posedge clk); #1; rst = 1'b0;
in = 1'b0; @(posedge clk);
in = 1'b1; @(posedge clk);
in = 1'b1; @(posedge clk); // pair 1
in = 1'b0; @(posedge clk);
in = 1'b1; @(posedge clk);
in = 1'b1; @(posedge clk); // pair 2
in = 1'b1; @(posedge clk); // pair 3 (overlapping run of 1s)
in = 1'b0; @(posedge clk); #1;
if (event_count !== 8'd3) begin
$display("FAIL: event_count=%0d exp=3 (glitch over-count?)", event_count);
errors = errors + 1;
end
if (errors == 0) $display("PASS: registered pulse counts exactly the real events");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the same bug is a process sensitive to a rising edge of the combinational Mealy net; the fix registers the pulse and counts on rising_edge(clk).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
-- BUGGY: the counter is clocked by the COMBINATIONAL Mealy output -> a glitch counts.
entity use_mealy_bad is
port ( clk, rst, d : in std_logic; event_count : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of use_mealy_bad is
signal mealy_out : std_logic;
signal cnt : unsigned(7 downto 0);
begin
u_fsm : entity work.seq_mealy port map (clk => clk, rst => rst, d => d, q => mealy_out);
process (mealy_out, rst) -- edge on a combinational net -> hazard
begin
if rst = '1' then cnt <= (others => '0');
elsif rising_edge(mealy_out) then cnt <= cnt + 1;
end if;
end process;
event_count <= std_logic_vector(cnt);
end architecture;
-- FIXED: register the Mealy pulse, then count synchronously on the clock.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity use_mealy_good is
port ( clk, rst, d : in std_logic; event_count : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of use_mealy_good is
signal mealy_out, mealy_out_reg : std_logic;
signal cnt : unsigned(7 downto 0);
begin
u_fsm : entity work.seq_mealy port map (clk => clk, rst => rst, d => d, q => mealy_out);
process (clk)
begin
if rising_edge(clk) then
if rst = '1' then
mealy_out_reg <= '0';
cnt <= (others => '0');
else
mealy_out_reg <= mealy_out; -- registered-Mealy: glitch-free, +1 cycle
if mealy_out_reg = '1' then cnt <= cnt + 1; end if;
end if;
end if;
end process;
event_count <= std_logic_vector(cnt);
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity mealy_glitch_tb is
end entity;
architecture sim of mealy_glitch_tb is
signal clk : std_logic := '0';
signal rst : std_logic := '1';
signal d : std_logic := '0';
signal event_count : std_logic_vector(7 downto 0);
begin
-- Bind to use_mealy_good (clean). use_mealy_bad can over-count on a glitch.
dut : entity work.use_mealy_good port map (clk => clk, rst => rst, d => d, event_count => event_count);
clkgen : process
begin
clk <= '0'; wait for 5 ns;
clk <= '1'; wait for 5 ns;
end process;
stim : process
begin
rst <= '1'; d <= '0';
wait until rising_edge(clk); wait for 1 ns; rst <= '0';
d <= '0'; wait until rising_edge(clk);
d <= '1'; wait until rising_edge(clk);
d <= '1'; wait until rising_edge(clk); -- pair 1
d <= '0'; wait until rising_edge(clk);
d <= '1'; wait until rising_edge(clk);
d <= '1'; wait until rising_edge(clk); -- pair 2
d <= '1'; wait until rising_edge(clk); -- pair 3 (overlapping)
d <= '0'; wait until rising_edge(clk); wait for 1 ns;
assert event_count = std_logic_vector(to_unsigned(3, 8))
report "event_count /= 3 (glitch over-count?)" severity error;
report "mealy_glitch self-check complete" severity note;
wait;
end process;
end architecture;Across all three languages the lesson is one sentence: the Mealy output is combinational, so it may only feed logic that samples it at a clock edge — the moment it drives anything edge-sensitive, register it first (registered-Mealy) or use the Moore output, so the consumer sees a glitch-free, clock-aligned pulse.
5. Verification Strategy
Moore vs Mealy verification is not "does it detect the pattern" — both do — it is "does the output appear on the right cycle, and is it clean enough for its consumer." That reframes the whole test: you drive the same input sequence into both machines and check when each output asserts.
The Mealy output asserts the same cycle as the deciding input; the Moore output asserts the cycle after; and any output that feeds edge-sensitive logic must be glitch-free (registered).
- Self-checking, cycle-aligned expectation vectors. Build one input vector and two expectation vectors — one aligned to the input cycle (Mealy) and one shifted a cycle later (Moore). The §4a and §4b testbenches drive the same
0 1 1 0 1 1 1 0stream and checkoutagainst the matching vector each cycle: SVassert (out === seq_out[i]), Verilogif (out !== seq_out[i]) $display("FAIL…"), VHDLassert q = SEQ_OUT(i) … severity error. The shift between the two vectors is the machine-checked proof of the one-cycle latency difference — the single most important property this page teaches. - Sample at the right moment. Because the Mealy output is combinational, the testbench must let it settle within the cycle before checking it (a small
#1after drivingin, before the check) — the same postponed-sampling discipline the v1 self-checking-TB standard requires. The Moore output, being registered, is checked after the clock edge that loaded the state. Sampling a combinational Mealy output at the wrong delta is itself a classic testbench bug. - Demonstrate the same-cycle combinational response. A directed check drives
inhigh within a cycle while the machine sits inS_GOT1and confirms the Mealyoutresponds in that cycle (before any edge) — proving the output tracks the live input, not just the state. Doing the same on the Moore machine showsoutdoes not move until the next edge. This is the behavioural difference made visible. - Assert the registered-Mealy is glitch-free / correctly counted. For the §4c consumer, drive a stream with a known number of real pattern completions and assert the event count equals that number exactly — the §4c testbenches expect
event_count === 3for the given stream. On the fixed (registered) path the count is exact; the bug is an over-count when a combinational glitch is latched by the edge-sensitive consumer. The invariant: a pulse that drives edge-sensitive logic must change only on a clock edge. - Conceptual invariants. Three properties must always hold: (i) both machines detect exactly the same set of input sequences (same function); (ii) the Mealy output for a given completed pattern is exactly one cycle earlier than the Moore output (the latency relationship); (iii) any output consumed by edge-sensitive logic never changes except on a clock edge (the glitch-freedom requirement). Property (iii) is the one a functional RTL sim can only approximate.
- The glitch is a gate-level concern. Be honest about the limit: a zero-delay RTL simulation does not produce the analog glitch on the combinational Mealy output, so §4c's buggy and fixed consumers can look identical functionally in RTL. The glitch — and the spurious edge it creates — is exposed by gate-level simulation with SDF timing (where combinational logic has real delays and hazards appear) and by static timing analysis, which reports the input-to-output combinational path on the Mealy output and flags it if it is the critical path. In RTL you structurally prevent it: never let a combinational output drive a clock, an async set/reset, or a rising-edge count — register it first.
- Corner cases and expected waveform. Exercise overlapping patterns (a run of
1s completes the pattern on every cycle after the first pair — the Mealy output stays high across the run while the Moore output is high one cycle later); reset asserted mid-pattern (both must return to idle and not emit a stale strobe); and a single isolated1(neither output fires). On a correct waveform, feeding0 1 1: the Mealyoutrises on the cycle of the second1and the Mooreoutrises on the following cycle — the visual signature of the whole page is those two strobes, one cycle apart, for the same event.
6. Common Mistakes
Assuming Moore and Mealy are interchangeable regardless of timing. The most common and most expensive mistake: treating the choice as a stylistic definition and swapping one for the other without checking the consumer's timing. They implement the same function but not the same timing — a Mealy output is a cycle earlier than the equivalent Moore output. Swap a Mealy detector for a Moore one and every downstream block that expected the strobe this cycle now gets it a cycle late (or vice-versa). Choose based on when the consumer needs the output, not on which is "cleaner."
A Mealy combinational output sampled by a downstream edge-triggered block. The signature bug (§7). Routing the raw combinational Mealy output to anything edge-sensitive — a flop's clock, an async set/reset, an asynchronous enable, a rising-edge counter — lets a mid-cycle glitch on that output register as a real, spurious event. It is intermittent and traffic-dependent (the glitch only occurs on certain input transitions) and invisible in zero-delay RTL. If a Mealy output must leave the block or drive edge-sensitive logic, register it (registered-Mealy) so it only changes on a clock edge.
A long input-to-output combinational path through a Mealy FSM failing timing. Because the Mealy output is f(state, in), the input sits on a combinational path straight to the output, and if the output then feeds more combinational logic, the path lengthens. On a fast clock this input-to-output arc can be the critical path — a real static-timing failure that a Moore machine (whose output is fed by the registered state) does not have. When the Mealy path is the bottleneck, register the output (registered-Mealy) to break the arc at the output flop.
Using Mealy where the one-cycle Moore latency was actually fine. Mealy is not automatically better for being faster. If the consumer is a synchronous block that samples on the edge and does not care about a single cycle of latency, a Moore output is the safer default — registered, glitch-free, no input-to-output path, no timing surprise. Reaching for Mealy when you did not need the same-cycle response buys you the glitch and timing risk for nothing. Use Mealy when the same-cycle response is a requirement, not a reflex.
Forgetting that registered-Mealy adds the latency back. The registered-Mealy compromise recovers Moore's clean, glitch-free, timing-safe output — but it does so by flopping the Mealy decision, which re-introduces exactly one cycle of latency on that registered output. Engineers sometimes reach for registered-Mealy to "keep the low latency and get a clean output," then are surprised the registered output lands on the same cycle a Moore output would. If you register the output, you have paid Moore's cycle; the value of registered-Mealy is a cleaner internal structure and fewer states, not zero latency.
Building a Moore machine without the extra state to hang the output on. A Moore detector often needs one more state than the Mealy equivalent — a dedicated "the pattern just completed" state whose label is the output. Trying to make a Moore output fire the same cycle as a Mealy one, by reading the input in the output logic, silently turns the machine back into a Mealy machine. If the output reads the input, it is Mealy, whatever you called it.
7. DebugLab
The event counter that double-counts — a Mealy glitch caught by an edge
The engineering lesson: a Mealy output is combinational — it responds the same cycle, but it can glitch and it puts the input on a straight path to the output, so it is only safe feeding logic that samples it at a clock edge and only closes timing when its input-to-output path is short. The two tells are unmistakable: a traffic-dependent over-count that appears only at gate level is a combinational output glitching into edge-sensitive logic; a failing max-delay path from an input pin to a downstream register is a Mealy output on the critical path. Both fixes are the same move — register the Mealy output (registered-Mealy) so it changes only on a clock edge and the arc ends at a flop, or use a Moore output if the one-cycle latency is acceptable — and both are identical across SystemVerilog, Verilog, and VHDL. The choice between Moore, pure Mealy, and registered Mealy is a deliberate trade of latency against combinational reactivity, not a definition.
8. Interview Q&A
9. Exercises
Design and reasoning exercises — no syntax drills. Each rehearses the "choose the output timing on purpose" habit.
Exercise 1 — Convert Mealy to Moore, and back
Start from a two-state Mealy detector that raises out the same cycle it sees the second 1 of a 11 pattern (out = (state == GOT1) & in). (a) Convert it to a Moore machine: draw the states (including the extra one), give the next-state transitions, and write the output equation as f(state) only. (b) State on which cycle each machine's output fires relative to the second 1, and why they differ by exactly one cycle. (c) Now convert the Moore machine back to Mealy without changing the detected sequence, and identify the one state the Mealy version does not need and why.
Exercise 2 — Pick the machine for the consumer
For each consumer, choose Moore, pure Mealy, or registered-Mealy and justify in one line: (a) a synchronous block that samples the strobe on the clock edge and does not care about one cycle of latency; (b) a same-cycle combinational grant that must be valid the cycle the request is seen; (c) a rising-edge counter that increments on the strobe; (d) an output that leaves the block onto a fast, timing-critical path but whose consumer can accept a cycle of latency. For each, name the specific failure the wrong choice would cause.
Exercise 3 — Explain the glitch and its blast radius
A Mealy FSM's combinational output feeds two consumers: (i) a downstream flop's D input, sampled at the clock edge, and (ii) a counter clocked by the rising edge of that same output. On a certain input transition the output glitches high-and-back mid-cycle. State, for each consumer, whether it is affected and precisely why. Then give the single RTL change that protects consumer (ii) without changing consumer (i)'s behaviour, and explain why that change is not free (what it costs).
Exercise 4 — Trace the latency and the extra state
Feed the stream in = 0 1 1 1 0 into both the two-state Mealy and the three-state Moore detector for 11. For each machine, write out the state each cycle and the output each cycle, and mark the cycle(s) on which out is high. Explain why the Mealy output stays high across the run of 1s while the Moore output is high a cycle later, and why the Moore machine's extra GOT2 state is what makes its output glitch-free and registered.
10. Related Tutorials
Continue in RTL Design Patterns (forward FSM links unlock as they ship):
- FSM Coding Styles (1/2/3-process) — Chapter 4.3; how the state-register / next-state / output split you tap for Moore vs Mealy is organized into one, two, or three processes, and where the output logic lives in each.
- FSM State Encoding (binary vs one-hot) — Chapter 4.4; how the state you decode for a Moore output is encoded, and why one-hot makes the output tap a single bit test.
- Safe FSM Design & Latch-Free Outputs — Chapter 4.5; keeping the output logic (Moore or Mealy) latch-free and glitch-safe, and recovering from illegal states.
- FSM + Datapath — Chapter 4.6; where Mealy outputs earn their keep as same-cycle datapath controls, and where Moore outputs keep control clean.
- valid/ready Handshake — Chapter 9.1; the canonical place a same-cycle Mealy-style combinational output (and its input-to-output path) shows up, and why it must be registered when it becomes a timing problem.
Backward / in-track dependencies:
- FSM Fundamentals — Chapter 4.1; the three-piece FSM model (state register + next-state logic + output logic) this page builds on — Moore vs Mealy is entirely about where the output logic taps.
- Multiplexers (2:1, N:1, one-hot) — Chapter 1.1; the next-state and output logic are muxes over the state, so the mux is the primitive under both machines.
- The Register Pattern (D-FF) — Chapter 2.1; the state register and the flop that makes a Moore (or registered-Mealy) output glitch-free.
- Synchronous Reset — Chapter 2.3; the sampled-on-the-edge reset both machines here use to return to idle.
- Pulse / Strobe Generators — Chapter 3; the one-cycle strobe both detectors emit, and why a clean single-cycle pulse must be registered when it drives edge-sensitive logic.
Verilog v1 prerequisites (the grammar this pattern transcribes into):
- Case Statements — the
case (state)that expresses both the next-state logic and a Moore output decode. - If-Else Statements — the conditional the output logic uses to combine state and input for a Mealy output.
- Blocking and Non-Blocking Assignments — why the state register uses non-blocking (
<=) while the combinational next-state and Mealy output logic use blocking (=).
11. Summary
- Moore vs Mealy is a hardware trade-off, not a definition. Two FSMs can implement the same function — detect the same sequence, raise the same flag — and still differ in when the output appears and how clean it is. The state register and next-state logic are identical; the only difference is where the output logic taps.
- Moore = output from state only: registered-clean, one cycle late. The output reads the state alone, so it is fed by the registered state flops — glitch-free, times like datapath, no input-to-output path — but it lands the cycle after the deciding input, and the machine often needs one extra state to hang the output on.
- Mealy = output from state and input: same cycle, combinational. The output reads the live input too, so it responds the same cycle and needs fewer states — but the output is combinational: it can glitch as the input settles, and it puts the input on a straight combinational path to the output that must meet timing.
- The signature bug is a Mealy glitch caught by an edge. A raw combinational Mealy output driving edge-sensitive logic (a clock, async set/reset, a rising-edge count) latches a mid-cycle glitch as a spurious event — intermittent, traffic-dependent, and invisible in zero-delay RTL (the DebugLab). Its sibling is the input-to-output path failing max-delay timing at speed. Both fixes are the same move.
- Registered-Mealy is the compromise — and it costs Moore's cycle. Flop the Mealy decision and the output becomes glitch-free and timing-safe (the arc ends at the flop), recovering Moore's cleanliness with fewer states — but the flop adds one cycle of latency back, so it lands when a Moore output would. Choose Moore, pure Mealy, or registered-Mealy by when and how clean the consumer needs the output — built and self-check-verified here in SystemVerilog, Verilog, and VHDL.