RTL Design Patterns · Chapter 2 · Registers & Sequential Building Blocks
Asynchronous Reset
A synchronous reset is only as good as the clock that samples it, and sometimes there is no clock: a PLL is still locking, an emergency stop must freeze a block right now, or a clock-gated island sits with its clock parked off. An asynchronous reset lives in the flop's sensitivity list itself, so asserting it forces a known state immediately, even with no running clock. Assertion is the easy half. The hard half is de-assertion: because the release can land at any instant relative to the clock, it can hit the flop's recovery and removal window and drive it metastable, a hazard synchronous reset never has. This lesson also covers glitches on the reset distribution tree, and builds every pattern self-check-verified in SystemVerilog, Verilog, and VHDL.
Foundation14 min readRTL Design PatternsAsynchronous ResetRegistersRecovery RemovalSequential LogicReset Tree
Chapter 2 · Section 2.4 · Registers & Sequential Building Blocks
1. The Engineering Problem
You just built a synchronous reset (2.3), and it works — as long as the clock is running. Now consider three situations where it isn't. Power-up: the reset is driven by a power-on-reset that asserts at cold start, but the clock comes from a PLL that is still locking and not yet toggling; a synchronous reset asserted into a dead clock never samples and the block comes up X. Emergency stop: a safety condition fires and you must force a motor-control block to a safe idle state immediately — waiting up to a full clock period for the next edge is a period too long. A clock-gated island: a block whose clock is deliberately parked off to save power still needs to be forcible to a known state on demand, even while its clock is stopped.
In all three the requirement is identical and it is one that synchronous reset structurally cannot meet: force a known state the instant a signal asserts, with no dependence on — and no need for — a running clock. A synchronous reset is sampled on the clock edge, so no edge means no reset. What you need is a reset that acts because reset itself changed, not because a clock edge arrived.
That mechanism is the asynchronous reset. Where a synchronous reset is a data input tested inside the clocked process, an asynchronous reset is wired to a separate reset input on the flop and appears in the process's sensitivity list, so the flop reacts the moment reset asserts — clock or no clock. This page builds that form: the active-low rst_n convention, the immediate assert, and the one thing that makes asynchronous reset genuinely harder than synchronous — the de-assertion, whose timing relative to the clock is a real hazard (the recovery/removal window, and the metastability it can cause) that Section 2.5 resolves.
// SYNCHRONOUS reset (2.3): sampled ON the edge. If the clock is not running
// while rst is asserted (PLL still locking at power-up), no edge ever samples
// it -- the flop keeps its unknown power-up value. Reset written, does nothing.
always @(posedge clk)
if (rst) state <= IDLE; // needs an edge; a dead clock => no reset
else state <= next_state;
// ASYNCHRONOUS reset (this page): rst_n is in the SENSITIVITY LIST, so the flop
// reacts the instant rst_n falls -- no clock edge required. Active-low by
// convention. This forces the known state immediately, even with clk stopped:
// always @(posedge clk or negedge rst_n)
// if (!rst_n) state <= IDLE; // immediate, clock-independent assert
// else state <= next_state;2. Mental Model
3. Pattern Anatomy
An asynchronous-reset register is a normal register whose process reacts to the reset signal as well as the clock. Four parts define it, and each is the mirror image of the synchronous pattern from 2.3.
Reset is in the sensitivity list — this is the whole difference. The pattern lives inside always_ff @(posedge clk or negedge rst_n) (SystemVerilog / Verilog) or a VHDL process (clk, rst_n) whose body tests if rst_n = '0' … elsif rising_edge(clk). Critically, rst_n is now in the sensitivity/event list — the exact opposite of synchronous reset, where only clk is. Because the process is sensitive to the reset edge, it fires the instant rst_n changes, without waiting for a clock edge. That single placement — reset in the list, and its branch tested before the clock branch — is the entire definition of asynchronous reset. Leave rst_n out of the list and you have silently built a synchronous reset (the bug of §6).
Active-low convention (rst_n). By near-universal convention an asynchronous reset is active-low: the signal is named rst_n, reset is asserted when it is 0, and normal operation resumes when it is 1. Active-low is preferred because it fails safe (a floating or disconnected reset net pulls low and holds the block in reset rather than releasing it into an unknown state) and matches the async reset pin on standard-cell and FPGA flops. The reset value it loads (RESET_VAL) is still a deliberate design choice — 0 for a counter, the idle code for an FSM — exactly as in 2.3.
Immediate assert; the de-assert hazard (recovery/removal → metastability). Assertion is the easy half: pull rst_n low and the output goes to RESET_VAL immediately, clock or no clock. The de-assertion is the hazard. When rst_n rises, the flop must return to normal clocked behaviour on the very next edge — and the flop imposes two timing constraints on when that release may occur relative to the edge: recovery time (reset must be released at least this long before the active clock edge) and removal time (reset must stay asserted at least this long after the edge). Release rst_n inside that recovery/removal window — which, because it is asynchronous, it can do at any instant — and the flop can capture a partially-released reset and go metastable, resolving to 0 or 1 unpredictably (and different flops resolving differently, breaking a multi-flop state). This hazard is unique to asynchronous reset; it is what Section 2.5 (async-assert, sync-de-assert) exists to eliminate.
The trade-off vs synchronous (2.3). Asynchronous reset works with no clock (its headline advantage — power-up before PLL lock, emergency stop, gated clocks) and stays out of the datapath (no extra mux term on the critical path). Its costs are the mirror image: it needs a dedicated reset distribution tree (a low-skew network to every flop's reset pin), it adds recovery/removal timing arcs that static timing must sign off, and it carries the de-assert metastability hazard. Synchronous reset (2.3) trades exactly the other way — clean datapath-only timing, no reset tree, no recovery/removal, but it requires a live clock and adds a mux term. Weighing the two, and the async-assert/sync-de-assert pattern that takes the best of both, is the flagship Section 2.5; here we build the asynchronous side cleanly and name its release hazard.
Asynchronous-reset register — reset reacts on its own event, before the clock
data flowThe anatomy also fixes what an asynchronous reset does not need and must not have. It does not need a running clock to assert — that is the point. It must not be decoded through combinational logic or gated (a glitch on that logic becomes a spurious reset pulse straight into the flop's reset pin — the glitchy-reset bug of §6/§7). And its release must not be treated as free: the recovery/removal window is a real timing check, and an unsynchronized de-assertion is a latent metastability bug (2.5).
4. Real RTL Implementation
This is the core of the page. We build three patterns — (a) a width-generic register with an asynchronous active-low reset, (b) that register plus a load enable with the correct structure, and (c) the reset bug (missing sensitivity / glitchy reset, buggy vs fixed) — and each is shown in SystemVerilog, Verilog, and VHDL, with both an RTL block and a self-checking clocked testbench. The reset idea is identical in all three; only the spelling of "react to negedge rst_n or the clock, test reset first" changes.
Two disciplines run through every block. First, rst_n is in the sensitivity list and its branch is tested first — the async assert must be able to fire without a clock edge and must win over the data. Second, every testbench generates a real clock and asserts rst_n between clock edges (deliberately off an edge), then checks that q clears immediately — before the next edge — proving the reset is truly asynchronous; then it releases reset and confirms normal clocked operation resumes.
4a. Register with an asynchronous active-low reset — the base pattern, three ways
Start with the atom of Chapter 2 plus an asynchronous reset: a width-generic register whose process reacts to negedge rst_n and posedge clk, and tests rst_n first. When rst_n is low, q is forced to RESET_VAL immediately; otherwise, on a clock edge, q loads d. The reset is asynchronous because the process is sensitive to the reset transition, not only the clock.
module reg_async_rst #(
parameter int WIDTH = 8, // datapath width
parameter logic [WIDTH-1:0] RESET_VAL = '0 // KNOWN start value
)(
input logic clk, // clock edge triggers updates
input logic rst_n, // ASYNC, active-low
input logic [WIDTH-1:0] d, // next data
output logic [WIDTH-1:0] q // registered output
);
// ASYNCHRONOUS reset: rst_n is IN the sensitivity list (negedge rst_n), so
// the flop reacts the instant rst_n falls -- no clock edge required. rst_n
// is tested FIRST, so an asserted reset wins over the data update.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= RESET_VAL; // immediate, clock-independent assert
else q <= d; // normal update on the clock edge
end
endmoduleThe SystemVerilog testbench generates a free-running clock, then asserts rst_n between two edges with garbage on d and checks q === RESET_VAL before the next edge — this is the crux: a synchronous reset would still show the old value there, an asynchronous one has already cleared. It then releases rst_n, advances an edge, and checks q tracks d.
module reg_async_rst_tb;
localparam int WIDTH = 8;
localparam logic [WIDTH-1:0] RESET_VAL = 8'hA5; // non-zero, to prove it's RESET_VAL not 0
logic clk = 0, rst_n;
logic [WIDTH-1:0] d, q;
int errors = 0;
reg_async_rst #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut (.clk(clk), .rst_n(rst_n), .d(d), .q(q));
always #5 clk = ~clk; // free-running clock, 10ns period
initial begin
// Seed a known non-reset value on a clock edge first.
rst_n = 1'b1; d = 8'h3C; @(posedge clk); #1;
assert (q === 8'h3C)
else begin $error("seed failed: q=%h exp=3C", q); errors++; end
// 1. Assert reset BETWEEN edges (deliberately OFF a clock edge). An ASYNC
// reset must clear q IMMEDIATELY -- before the next rising edge.
#2 rst_n = 1'b0; d = 8'hFF; // mid-cycle assert, garbage on d
#1; // no clock edge has occurred yet
assert (q === RESET_VAL)
else begin $error("async assert: q=%h exp=%h (cleared without an edge)", q, RESET_VAL); errors++; end
assert (^q !== 1'bx) // no X survives the assert
else begin $error("q has X after reset: q=%h", q); errors++; end
// 2. Release reset (well clear of an edge), then clock -> NORMAL operation.
#2 rst_n = 1'b1; d = 8'h77;
@(posedge clk); #1;
assert (q === 8'h77)
else begin $error("post-reset update: q=%h exp=77", q); errors++; end
if (errors == 0) $display("PASS: async reset clears q immediately, then resumes");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog form is identical in intent — reg typing and if(q!==exp) $display("FAIL") instead of assert. The event list is still posedge clk or negedge rst_n, and if (!rst_n) is the first statement, so the reset is asynchronous and tested first.
module reg_async_rst #(
parameter WIDTH = 8,
parameter RESET_VAL = 8'h00
)(
input wire clk,
input wire rst_n, // ASYNC, active-low
input wire [WIDTH-1:0] d,
output reg [WIDTH-1:0] q
);
// rst_n IS in the event list -> the flop reacts to negedge rst_n without a
// clock edge (asynchronous). Tested first -> reset wins over the update.
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= RESET_VAL; // immediate, clock-independent assert
else q <= d;
end
endmodule module reg_async_rst_tb;
parameter WIDTH = 8;
parameter RESET_VAL = 8'hA5;
reg clk, rst_n;
reg [WIDTH-1:0] d;
wire [WIDTH-1:0] q;
integer errors;
reg_async_rst #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut (.clk(clk), .rst_n(rst_n), .d(d), .q(q));
initial clk = 1'b0;
always #5 clk = ~clk; // free-running clock
initial begin
errors = 0;
rst_n = 1'b1; d = 8'h3C; @(posedge clk); #1; // seed a known value
if (q !== 8'h3C) begin
$display("FAIL: seed q=%h exp=3C", q); errors = errors + 1;
end
// 1. Assert reset BETWEEN edges -> q must clear immediately (no edge).
#2 rst_n = 1'b0; d = 8'hFF;
#1;
if (q !== RESET_VAL) begin
$display("FAIL: async assert q=%h exp=%h (no edge yet)", q, RESET_VAL); errors = errors + 1;
end
if (^q === 1'bx) begin
$display("FAIL: q has X after reset q=%h", q); errors = errors + 1;
end
// 2. Release reset, then clock -> normal update.
#2 rst_n = 1'b1; d = 8'h77;
@(posedge clk); #1;
if (q !== 8'h77) begin
$display("FAIL: post-reset q=%h exp=77", q); errors = errors + 1;
end
if (errors == 0) $display("PASS: async reset clears immediately then resumes");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleIn VHDL the process sensitivity list is (clk, rst_n), and the order of the tests makes it asynchronous: if rst_n = '0' then … is checked before elsif rising_edge(clk) then …, so the reset branch fires on any change of rst_n regardless of the clock. This is the direct VHDL counterpart of "reset in the sensitivity list, tested before the edge."
library ieee;
use ieee.std_logic_1164.all;
entity reg_async_rst is
generic (
WIDTH : positive := 8;
RESET_VAL : std_logic_vector := "00000000" -- KNOWN start value
);
port (
clk : in std_logic;
rst_n : in std_logic; -- ASYNC, active-low
d : in std_logic_vector(WIDTH-1 downto 0);
q : out std_logic_vector(WIDTH-1 downto 0)
);
end entity;
architecture rtl of reg_async_rst is
begin
-- rst_n IS in the sensitivity list, and its branch is tested BEFORE
-- rising_edge(clk) -> the reset acts on a change of rst_n with no clock edge
-- (asynchronous) and has top priority over the update.
process (clk, rst_n)
begin
if rst_n = '0' then
q <= RESET_VAL; -- immediate, no clock needed
elsif rising_edge(clk) then
q <= d; -- normal clocked update
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity reg_async_rst_tb is
end entity;
architecture sim of reg_async_rst_tb is
constant WIDTH : positive := 8;
constant RESET_VAL : std_logic_vector(WIDTH-1 downto 0) := x"A5";
signal clk : std_logic := '0';
signal rst_n : std_logic;
signal d : std_logic_vector(WIDTH-1 downto 0);
signal q : std_logic_vector(WIDTH-1 downto 0);
begin
dut : entity work.reg_async_rst
generic map (WIDTH => WIDTH, RESET_VAL => RESET_VAL)
port map (clk => clk, rst_n => rst_n, d => d, q => q);
clk <= not clk after 5 ns; -- free-running clock
stim : process
begin
rst_n <= '1'; d <= x"3C"; -- seed a known value on an edge
wait until rising_edge(clk); wait for 1 ns;
assert q = x"3C" report "seed mismatch" severity error;
-- 1. Assert reset BETWEEN edges -> q must clear IMMEDIATELY (no edge).
wait for 2 ns; rst_n <= '0'; d <= x"FF";
wait for 1 ns;
assert q = RESET_VAL
report "async assert: q /= RESET_VAL (cleared without an edge)" severity error;
assert not is_x(q)
report "q has X after reset" severity error;
-- 2. Release reset, then clock -> normal update.
wait for 2 ns; rst_n <= '1'; d <= x"77";
wait until rising_edge(clk); wait for 1 ns;
assert q = x"77" report "post-reset update mismatch" severity error;
report "reg_async_rst self-check complete" severity note;
wait;
end process;
end architecture;4b. Asynchronous reset + enable — reset outside the edge, enable inside it
Now add the load enable from 2.2. The structure is the important lesson: the asynchronous reset branch stays outside and before the clocked branch (it must fire without an edge), while the enable is tested inside the clock branch (it is an ordinary synchronous input). So the shape is "if reset (async) → force RESET_VAL; else on the edge, if enable load d, else hold." The reset still wins over everything, immediately; the enable only gates the clocked update.
module reg_async_rst_en #(
parameter int WIDTH = 8,
parameter logic [WIDTH-1:0] RESET_VAL = '0
)(
input logic clk,
input logic rst_n, // async, highest priority
input logic en, // synchronous enable
input logic [WIDTH-1:0] d,
output logic [WIDTH-1:0] q
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= RESET_VAL; // 1. async reset: immediate, wins always
else if (en) q <= d; // 2. on the edge, enabled update
else q <= q; // 3. on the edge, hold (explicit)
end
endmoduleThe structure is the point: !rst_n is the outer branch (it fires off negedge rst_n, no edge needed), and en is tested only when the process was entered via the clock edge. The testbench proves the async reset overrides even a de-asserted enable between edges, then that with reset released the enable gates updates normally.
module reg_async_rst_en_tb;
localparam int WIDTH = 8;
localparam logic [WIDTH-1:0] RESET_VAL = 8'h5A;
logic clk = 0, rst_n, en;
logic [WIDTH-1:0] d, q;
int errors = 0;
reg_async_rst_en #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut
(.clk(clk), .rst_n(rst_n), .en(en), .d(d), .q(q));
always #5 clk = ~clk;
initial begin
// Seed a known non-reset value (rst_n=1, en=1).
rst_n = 1; en = 1; d = 8'h11; @(posedge clk); #1;
// 1. Async reset MUST override a DE-ASSERTED enable, BETWEEN edges.
#2 rst_n = 0; en = 0; d = 8'hFF; #1; // no clock edge here
assert (q === RESET_VAL)
else begin $error("async reset lost to disabled en: q=%h exp=%h", q, RESET_VAL); errors++; end
// 2. Release reset; rst_n=1, en=0 -> HOLD across an edge.
#2 rst_n = 1; en = 0; d = 8'h22; @(posedge clk); #1;
assert (q === RESET_VAL)
else begin $error("hold failed: q=%h exp=%h", q, RESET_VAL); errors++; end
// 3. rst_n=1, en=1 -> normal enabled update.
en = 1; d = 8'h77; @(posedge clk); #1;
assert (q === 8'h77)
else begin $error("enabled update: q=%h exp=77", q); errors++; end
if (errors == 0) $display("PASS: async reset wins immediately; enable gates the clocked update");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleThe Verilog version keeps the same shape — if (!rst_n) … else if (en) … else in an always @(posedge clk or negedge rst_n); the reset branch is the outer one, the enable inside the edge.
module reg_async_rst_en #(
parameter WIDTH = 8,
parameter RESET_VAL = 8'h00
)(
input wire clk,
input wire rst_n, // async, highest priority
input wire en,
input wire [WIDTH-1:0] d,
output reg [WIDTH-1:0] q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= RESET_VAL; // async reset: immediate, wins always
else if (en) q <= d; // enabled update (on the edge)
else q <= q; // hold (on the edge)
end
endmodule module reg_async_rst_en_tb;
parameter WIDTH = 8;
parameter RESET_VAL = 8'h5A;
reg clk, rst_n, en;
reg [WIDTH-1:0] d;
wire [WIDTH-1:0] q;
integer errors;
reg_async_rst_en #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut
(.clk(clk), .rst_n(rst_n), .en(en), .d(d), .q(q));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
rst_n = 1; en = 1; d = 8'h11; @(posedge clk); #1; // seed known value
#2 rst_n = 0; en = 0; d = 8'hFF; #1; // async reset vs disabled en, off-edge
if (q !== RESET_VAL) begin
$display("FAIL: async reset lost to disabled en q=%h exp=%h", q, RESET_VAL); errors=errors+1;
end
#2 rst_n = 1; en = 0; d = 8'h22; @(posedge clk); #1; // hold
if (q !== RESET_VAL) begin
$display("FAIL: hold failed q=%h exp=%h", q, RESET_VAL); errors=errors+1;
end
en = 1; d = 8'h77; @(posedge clk); #1; // enabled update
if (q !== 8'h77) begin
$display("FAIL: enabled update q=%h exp=77", q); errors=errors+1;
end
if (errors == 0) $display("PASS: async reset wins; enable gates the clocked update");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
endmoduleVHDL keeps the async reset as the leading if and nests the enable inside the rising_edge branch: if rst_n = '0' then … elsif rising_edge(clk) then (if en = '1' then … else … end if) end if. The reset is outside the edge; the enable is inside it.
library ieee;
use ieee.std_logic_1164.all;
entity reg_async_rst_en is
generic (
WIDTH : positive := 8;
RESET_VAL : std_logic_vector := "00000000"
);
port (
clk : in std_logic;
rst_n : in std_logic; -- async, highest priority
en : in std_logic;
d : in std_logic_vector(WIDTH-1 downto 0);
q : out std_logic_vector(WIDTH-1 downto 0)
);
end entity;
architecture rtl of reg_async_rst_en is
begin
process (clk, rst_n)
begin
if rst_n = '0' then
q <= RESET_VAL; -- async reset: immediate, wins
elsif rising_edge(clk) then
if en = '1' then
q <= d; -- enabled update (on the edge)
else
q <= q; -- hold (on the edge)
end if;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity reg_async_rst_en_tb is
end entity;
architecture sim of reg_async_rst_en_tb is
constant WIDTH : positive := 8;
constant RESET_VAL : std_logic_vector(WIDTH-1 downto 0) := x"5A";
signal clk : std_logic := '0';
signal rst_n, en : std_logic;
signal d : std_logic_vector(WIDTH-1 downto 0);
signal q : std_logic_vector(WIDTH-1 downto 0);
begin
dut : entity work.reg_async_rst_en
generic map (WIDTH => WIDTH, RESET_VAL => RESET_VAL)
port map (clk => clk, rst_n => rst_n, en => en, d => d, q => q);
clk <= not clk after 5 ns;
stim : process
begin
rst_n <= '1'; en <= '1'; d <= x"11"; -- seed a known non-reset value
wait until rising_edge(clk); wait for 1 ns;
wait for 2 ns; rst_n <= '0'; en <= '0'; d <= x"FF"; -- async reset vs disabled en, off-edge
wait for 1 ns;
assert q = RESET_VAL report "async reset lost to disabled enable" severity error;
wait for 2 ns; rst_n <= '1'; en <= '0'; d <= x"22"; -- hold
wait until rising_edge(clk); wait for 1 ns;
assert q = RESET_VAL report "hold failed" severity error;
en <= '1'; d <= x"77"; -- enabled update
wait until rising_edge(clk); wait for 1 ns;
assert q = x"77" report "enabled update mismatch" severity error;
report "reg_async_rst_en self-check complete" severity note;
wait;
end process;
end architecture;4c. The reset bug — missing sensitivity / glitchy reset, buggy vs fixed, in all three HDLs
Two traps break an asynchronous reset without a syntax error, and they are the ones §7 dramatizes. The first is the missing-sensitivity trap: leaving rst_n out of the event list (SV/Verilog) or testing it inside the rising_edge guard (VHDL) turns a reset you intended to be asynchronous into a synchronous one — it now waits for a clock edge, so at power-up with a dead clock it never fires and the block comes up X. RTL sim (clock toggling from time zero) hides it; gate-level/silicon exposes it. The second is the glitchy / gated async reset: decoding or gating the reset through combinational logic, so a momentary glitch on that logic becomes a spurious reset pulse straight into the flop's async reset pin, clearing real flops for no reason.
// BUGGY: rst_n is NOT in the event list -- only posedge clk is. This is NO LONGER
// an asynchronous reset; it is synchronous. At power-up with a dead clock
// it never fires. Intended async, silently built sync (RTL sim hides it).
module reg_missing_async (
input logic clk, rst_n,
input logic [7:0] d,
output logic [7:0] q
);
always_ff @(posedge clk) begin // <- rst_n MISSING = actually SYNC
if (!rst_n) q <= 8'h00;
else q <= d;
end
endmodule
// FIXED: rst_n IS in the event list -> reacts to negedge rst_n without a clock
// edge -- a true ASYNCHRONOUS reset that fires even with the clock stopped.
module reg_true_async (
input logic clk, rst_n,
input logic [7:0] d,
output logic [7:0] q
);
always_ff @(posedge clk or negedge rst_n) begin // <- rst_n in the list = ASYNC
if (!rst_n) q <= 8'h00;
else q <= d;
end
endmodule // BUGGY: the async reset pin is driven by COMBINATIONAL logic (a decode/gate). A
// momentary glitch on rst_gated -- a hazard in por_n & mode_ok -- becomes a
// spurious reset PULSE straight into the flop, clearing it for no reason.
module reg_gated_reset (
input logic clk, por_n, mode_ok,
input logic [7:0] d,
output logic [7:0] q
);
logic rst_gated;
assign rst_gated = por_n & mode_ok; // combinational reset net -> glitches
always_ff @(posedge clk or negedge rst_gated) begin
if (!rst_gated) q <= 8'h00; // a glitch here = spurious reset
else q <= d;
end
endmodule
// FIXED: drive the async reset pin from a CLEAN, dedicated reset net -- a single
// registered/synchronized reset source, no combinational decode on the path.
module reg_clean_reset (
input logic clk, rst_n, // one clean reset, no gating
input logic [7:0] d,
output logic [7:0] q
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 8'h00; // glitch-free reset pin
else q <= d;
end
endmodule module reset_bug_tb;
logic clk = 0, rst_n;
logic [7:0] d, q;
int errors = 0;
// Bind to reg_true_async to PASS; to reg_missing_async to see the sync trap.
reg_true_async dut (.clk(clk), .rst_n(rst_n), .d(d), .q(q));
always #5 clk = ~clk;
initial begin
rst_n = 1; d = 8'h9E; @(posedge clk); #1; // seed a value
// Assert reset BETWEEN edges: a true async reset must clear NOW, no edge.
#2 rst_n = 0; d = 8'hFF; #1;
assert (q === 8'h00)
else begin $error("reset did not clear off-edge: q=%h exp=00 (built sync?)", q); errors++; end
if (errors == 0) $display("PASS: async reset clears without a clock edge");
else $display("FAIL: %0d mismatches (reset is synchronous, not async)", errors);
$finish;
end
endmoduleThe Verilog pair is the same story with reg outputs; the missing-sensitivity trap is again posedge clk without or negedge rst_n, and the glitch trap is again a combinational net on the reset pin.
// BUGGY: rst_n NOT in the event list -> synchronous reset, not asynchronous.
module reg_missing_async (
input wire clk, rst_n,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk) begin // rst_n missing = actually sync
if (!rst_n) q <= 8'h00;
else q <= d;
end
endmodule
// FIXED: negedge rst_n in the list -> asynchronous, fires without a clock edge.
module reg_true_async (
input wire clk, rst_n,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk or negedge rst_n) begin // rst_n in the list = async
if (!rst_n) q <= 8'h00;
else q <= d;
end
endmodule // BUGGY: async reset pin driven by combinational logic -> a glitch = spurious reset.
module reg_gated_reset (
input wire clk, por_n, mode_ok,
input wire [7:0] d,
output reg [7:0] q
);
wire rst_gated = por_n & mode_ok; // combinational reset net -> glitches
always @(posedge clk or negedge rst_gated) begin
if (!rst_gated) q <= 8'h00; // a glitch here = spurious reset
else q <= d;
end
endmodule
// FIXED: async reset pin driven by a single clean, dedicated reset net.
module reg_clean_reset (
input wire clk, rst_n,
input wire [7:0] d,
output reg [7:0] q
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 8'h00; // glitch-free reset pin
else q <= d;
end
endmodule module reset_bug_tb;
reg clk, rst_n;
reg [7:0] d;
wire [7:0] q;
integer errors;
// Bind to reg_true_async to PASS; to reg_missing_async to observe the sync trap.
reg_true_async dut (.clk(clk), .rst_n(rst_n), .d(d), .q(q));
initial clk = 1'b0;
always #5 clk = ~clk;
initial begin
errors = 0;
rst_n = 1; d = 8'h9E; @(posedge clk); #1; // seed
#2 rst_n = 0; d = 8'hFF; #1; // assert reset off-edge
if (q !== 8'h00) begin
$display("FAIL: reset did not clear off-edge q=%h exp=00 (built sync?)", q); errors = errors + 1;
end
if (errors == 0) $display("PASS: async reset clears without a clock edge");
else $display("FAIL: %0d mismatches (reset is synchronous)", errors);
$finish;
end
endmoduleIn VHDL the missing-sensitivity trap appears when rst_n is tested inside the rising_edge guard (so it becomes synchronous); the asynchronous version tests rst_n before rising_edge with rst_n in the sensitivity list. The glitch trap is again a combinational signal driving the reset branch.
library ieee;
use ieee.std_logic_1164.all;
-- BUGGY: rst_n tested INSIDE rising_edge (and absent from the list) -> the reset
-- is SYNCHRONOUS, not the asynchronous behaviour intended.
entity reg_missing_async is
port ( clk, rst_n : in std_logic;
d : in std_logic_vector(7 downto 0);
q : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of reg_missing_async is
begin
process (clk) -- rst_n NOT in the list
begin
if rising_edge(clk) then
if rst_n = '0' then q <= (others => '0'); -- tested INSIDE edge = sync
else q <= d;
end if;
end if;
end process;
end architecture;
-- FIXED: rst_n in the list AND tested BEFORE rising_edge -> asynchronous.
entity reg_true_async is
port ( clk, rst_n : in std_logic;
d : in std_logic_vector(7 downto 0);
q : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of reg_true_async is
begin
process (clk, rst_n) -- rst_n in the list
begin
if rst_n = '0' then -- tested OUTSIDE edge = async
q <= (others => '0');
elsif rising_edge(clk) then
q <= d;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
-- BUGGY: async reset driven by combinational logic -> a glitch = spurious reset.
entity reg_gated_reset is
port ( clk, por_n, mode_ok : in std_logic;
d : in std_logic_vector(7 downto 0);
q : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of reg_gated_reset is
signal rst_gated : std_logic;
begin
rst_gated <= por_n and mode_ok; -- combinational reset net -> glitches
process (clk, rst_gated)
begin
if rst_gated = '0' then q <= (others => '0'); -- a glitch here = spurious reset
elsif rising_edge(clk) then q <= d;
end if;
end process;
end architecture;
-- FIXED: async reset driven by a single clean, dedicated reset net.
entity reg_clean_reset is
port ( clk, rst_n : in std_logic;
d : in std_logic_vector(7 downto 0);
q : out std_logic_vector(7 downto 0) );
end entity;
architecture rtl of reg_clean_reset is
begin
process (clk, rst_n)
begin
if rst_n = '0' then q <= (others => '0'); -- glitch-free reset pin
elsif rising_edge(clk) then q <= d;
end if;
end process;
end architecture; library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity reset_bug_tb is
end entity;
architecture sim of reset_bug_tb is
signal clk : std_logic := '0';
signal rst_n : std_logic;
signal d, q : std_logic_vector(7 downto 0);
begin
-- Bind to reg_true_async to PASS; to reg_missing_async to observe the sync trap.
dut : entity work.reg_true_async
port map (clk => clk, rst_n => rst_n, d => d, q => q);
clk <= not clk after 5 ns;
stim : process
begin
rst_n <= '1'; d <= x"9E"; -- seed a value
wait until rising_edge(clk); wait for 1 ns;
wait for 2 ns; rst_n <= '0'; d <= x"FF"; -- assert reset OFF an edge
wait for 1 ns;
assert q = x"00"
report "reset did not clear off-edge (built synchronous?)" severity error;
report "reset_bug self-check complete" severity note;
wait;
end process;
end architecture;Across all three languages the two lessons are one sentence each: keep rst_n in the sensitivity list and test it before the edge, or you have silently built a synchronous reset that dies with a stopped clock, and drive the async reset pin from a clean, glitch-free net — never through combinational decode or gating — or a hazard becomes a spurious reset.
5. Verification Strategy
An asynchronous-reset register is sequential, but its defining property is not edge-triggered — it is the opposite: the reset must take effect without a clock edge. So the testbench must generate a real clock (to verify normal operation and the release), yet the crucial check happens between edges. The single invariant that captures the pattern is:
The moment reset asserts, the output becomes the reset value — immediately, without waiting for a clock edge — and it stays there for as long as reset is held; after reset is released, the output resumes normal clocked operation on the next edge.
Everything below is that invariant, made checkable, mapped onto the clocked testbenches in §4. One caveat frames the section: the RTL simulator cannot fully show the de-assert (recovery/removal) hazard — an event-driven RTL sim treats the release as instantaneous and glitch-free, so the metastability it can cause is a real-delay / gate-level concern, exercised with SDF back-annotation and recovery/removal timing checks, not something an RTL sim proves safe. That gap is exactly why Section 2.5 (and Chapter 11) exist.
- Immediate assert, off a clock edge (self-checking). Generate a free-running clock, seed
qwith a known value, then assertrst_nbetween two rising edges with garbage ond, and — before the next edge — assertq === RESET_VAL. This is the crux: a synchronous reset would still show the seeded value at that point; an asynchronous one has already cleared. The three §4a testbenches do exactly this — SVassert (q === RESET_VAL)after a mid-cyclerst_n = 0, Verilogif (q !== RESET_VAL) $display("FAIL"), VHDLassert q = RESET_VAL report … severity error. Asserting off an edge is the whole point; a testbench that asserts reset on an edge would pass a synchronous reset by accident and never test the async behaviour. - Reset overrides even a de-asserted enable, immediately. With
rst_n=0, en=0between edges, assertq === RESET_VAL. This proves the async reset is the outer branch — it fires without an edge and regardless of the enable. The §4b testbenches assert this directly. - Held reset holds; release resumes normal operation. While
rst_nstays low,qholdsRESET_VALacross intervening edges (idempotent). After releasingrst_n(well clear of an edge, to avoid the recovery/removal window in the RTL sim), drive a knownd, advance an edge, and assertqtracksd(and the enable now gates updates). This confirms the block exits reset cleanly into ordinary behaviour. - No X after the assert. Assert the output carries no
Xonce reset has asserted: SVassert (^q !== 1'bx), Verilogif (^q === 1'bx) $display("FAIL"), VHDLassert not is_x(q)— the reset must land a defined value, not leaveX. - Corner cases and failure modes. Exercise: reset asserted with the clock held static (a true async reset still clears — this is the property a synchronous reset fails); the missing-sensitivity DUT (
reg_missing_async) — bind the §4c TB to it and the off-edge check fires, proving it is synchronous; a glitch on a gated reset net (a spurious clear); and — named but only fully shown at gate level — releasingrst_ninside the recovery/removal window, which an SDF-annotated gate-level sim with timing checks flags as a violation and which can drive the flop metastable. Each maps to a check above, except the last, which is the RTL-sim blind spot (→ 2.5). - Expected waveform. On a correct run:
rst_nfalls andqsteps toRESET_VALimmediately (not on the next edge — the change is aligned to the reset transition, clock-independent); whilerst_nstays low,qholdsRESET_VALregardless of the clock; whenrst_nrises, the next clock edge letsqfollowd. The visual signature of the missing-sensitivity bug is the opposite:qdoes not move whenrst_nfalls between edges — it waits for the next rising edge, revealing that the "async" reset is actually synchronous. And the de-assert hazard's signature — only visible with real delays — isqgoing metastable (an intermediate/oscillating value resolving unpredictably) whenrst_nreleases too close to an edge.
6. Common Mistakes
Omitting rst_n from the sensitivity list when you meant asynchronous. The signature asynchronous-reset failure. Writing always @(posedge clk) (SV/Verilog) — dropping or negedge rst_n — or testing rst_n inside the rising_edge guard (VHDL) does not make a "cleaner" async reset; it silently turns it synchronous. The block now waits for a clock edge to reset, so at power-up with a dead clock (PLL still locking) it never fires and comes up X. The trap is invisible in an RTL sim that toggles the clock from time zero, and surfaces only in gate-level/silicon — a synthesis-mismatch bug. If you intend asynchronous, rst_n must be in the event list and tested before the edge.
A combinationally-decoded or gated asynchronous reset. Driving the flop's async reset pin from combinational logic — assign rst_gated = por_n & mode_ok; or any decode/AND/OR feeding the reset — is dangerous because that logic can glitch. A momentary hazard on the reset net becomes a spurious reset pulse straight into the async reset pin, clearing real flops for no reason, intermittently and un-reproducibly. An asynchronous reset must come from a clean, dedicated, glitch-free source (a single registered/synchronized reset net), never through combinational gating. This is the async-specific mirror of the synchronous world's cleanliness rules.
Mixing reset polarities (active-high vs active-low). Asynchronous resets are active-low by convention (rst_n, asserted at 0); mixing an active-high rst and an active-low rst_n in the same design — or getting the sense backwards in one module (if (rst_n) where you meant if (!rst_n)) — inverts the reset and either holds the block permanently in reset or never resets it. Pick one convention (active-low rst_n), state it, and keep the polarity consistent; active-low is preferred because a floating net pulls low and fails safe (into reset).
Ignoring the de-assert (recovery/removal) hazard entirely. The most subtle mistake is treating the release of an asynchronous reset as free, the way the assert is. It is not. Because the de-assertion is asynchronous to the clock, it can land inside the flop's recovery (release-before-edge) or removal (release-after-edge) window and drive the flop metastable — resolving to 0 or 1 unpredictably, and different flops resolving differently, so a supposedly-reset multi-bit state comes out inconsistent. An RTL sim never shows this (it treats the release as instantaneous); it appears only with real delays / recovery-removal timing checks. The standard cure is asynchronous assert, synchronous de-assert — the reset synchronizer of Section 2.5 — which keeps the immediate assert but releases reset cleanly, aligned to the clock.
7. DebugLab
The block that reset perfectly in simulation and came up dead in silicon — an async reset that was never in the sensitivity list
The engineering lesson: an asynchronous reset is defined by its membership in the sensitivity list and a clean, glitch-free reset net — and its release is a separate hazard. The tell is a reset that reads correctly line-by-line but has the wrong structure: leave rst_n out of the event list and you built a synchronous reset that dies with a stopped clock ("passes in RTL, comes up X in silicon"); drive the reset pin through combinational logic and a glitch becomes a spurious reset. Two habits make the assert bulletproof, and they hold across SystemVerilog, Verilog, and VHDL: keep rst_n in the sensitivity list and test it before the clock edge, and drive the async reset pin from a single clean, dedicated net, never a combinational decode. And the third habit is the one this page can only name: the de-assertion is a real recovery/removal hazard that an RTL sim cannot show — release reset synchronously (async assert, sync de-assert, Section 2.5) so a release near an edge can never drive a flop metastable.
8. Interview Q&A
9. Exercises
Design and reasoning exercises — no syntax drills. Each rehearses the "reset in the sensitivity list, immediate assert, hazardous release" model.
Exercise 1 — Prove the reset is really asynchronous
You are handed a register described as having an asynchronous active-low reset. (a) Write the one self-checking testbench sequence that would distinguish a genuine asynchronous reset from a synchronous one built by accident (say exactly when you assert rst_n relative to the clock, and what you check, and when). (b) Explain why a testbench that asserts rst_n on a rising clock edge would pass both the async and the sync implementations, hiding the bug. (c) Name the one line in the RTL that decides which one you actually built.
Exercise 2 — Assert is free, release is the hazard
A block uses a textbook asynchronous reset. (a) Explain why asserting rst_n is safe at any instant, but releasing it is not. (b) Define recovery and removal time in one sentence each, relative to the clock edge. (c) Describe the failure that occurs if rst_n is released 100 ps before a rising edge when the flop's recovery time is 300 ps — including why different flops in the same reset group may resolve to different values. (d) Name the design pattern that eliminates this hazard while keeping the immediate assert, and say in one line how it works. (Hint: async assert, synchronous de-assert.)
Exercise 3 — Asynchronous or synchronous, from the symptom
For each observed symptom, state whether the reset in the design is most likely asynchronous or synchronous, and give the one-line reasoning: (a) the block resets correctly in RTL sim but comes up X in gate-level sim that models PLL lock time; (b) the reset takes effect immediately when rst_n asserts, even with the clock held static; (c) static timing reports recovery and removal violations on the reset path; (d) an occasional, un-reproducible spurious clear of a register with no reset command in sight, traced to a hazard on the reset net; (e) a reset pulse narrower than a clock period, asserted between two edges, is simply missed. (Hint: some of these are signatures of asynchronous reset and some of synchronous.)
Exercise 4 — Choose the reset style, then justify it
For each block, state whether you would use synchronous or asynchronous reset (or async-assert/sync-de-assert), and give one sentence of why: (a) a block that must be forced to a safe idle state on an emergency-stop input while its clock may be gated off; (b) a deeply-pipelined datapath on an FPGA whose fabric already powers up in a known state; (c) a control FSM that must come up in IDLE at cold power-up before the PLL has locked; (d) a block on a clock-gated power island that must be resettable while parked. Then, in one line, explain why "just use an asynchronous reset everywhere" is not a safe default — name the cost it adds that synchronous reset avoids.
10. Related Tutorials
Continue in RTL Design Patterns (sibling and forward links unlock as they ship):
- The Register Pattern (D-FF) — Chapter 2.1; the flip-flop this page adds an asynchronous reset to — the unit of state, and the
<=non-blocking discipline every reset branch uses. - Register with Enable / Load — Chapter 2.2; the load-enable this page nests inside the clock branch while the async reset stays outside it.
- Synchronous Reset — Chapter 2.3; the other half of reset — sampled on the clock edge, tested inside the process, with clean datapath timing and no recovery/removal; this page is its clock-independent counterpart.
- Reset Strategy — Chapter 2.5 (flagship); when to choose synchronous vs asynchronous, and the async-assert / synchronous-de-assert reset synchronizer that eliminates this page's release hazard.
- Shift Registers — Chapter 2.6; a chain of the reset-bearing registers built here, where a known reset value seeds a serial pipeline.
Forward — where the release hazard is paid off:
- Metastability — Chapter 11.1; the physics behind the recovery/removal failure — why a flop clocked in its forbidden window resolves unpredictably.
- Two-Flop Synchronizer — Chapter 11.2; the synchronizer whose logic the reset synchronizer (2.5) reuses to release an async reset cleanly.
Backward / method:
- Multiplexers (2:1, N:1, one-hot) — Chapter 1.1; the enable-select in front of the flop is a 2:1 mux — selection meeting state.
- Decoders · Encoders · Priority Encoders · Comparators · Parity & ECC Blocks · Encoding & Conversions — Chapter 1; the combinational building blocks that feed the registers this chapter builds.
- The RTL Design Mindset — Chapter 0.2; the State lens — a register with a reset is the smallest complete example of deliberate state initialization.
- What Is an RTL Design Pattern? — Chapter 0.1; why asynchronous reset earns the name "pattern" — a recurring need, a reusable form, a synthesis reality, and a signature failure.
Verilog v1 prerequisites (the grammar this pattern transcribes into):
- Blocking and Non-Blocking Assignments — why every reset branch uses non-blocking
<=inside the clocked process, and how that models the flop. - Initial and Always Blocks — the
always @(posedge clk or negedge rst_n)construct the whole pattern lives inside, and why the reset transition belongs in the event list. - Level-Sensitive Timing — the edge-vs-level distinction underneath why an asynchronous reset acts on a transition rather than being sampled by an edge.
- If–Else Statements — the
if (!rst_n) … else …structure that puts the async reset branch before the clocked update. - Case Statements — the alternative spelling of the reset/update select when the block's behaviour grows beyond two branches.
11. Summary
- Sometimes you must force a known state with no clock — that is what an asynchronous reset is for. At power-up before a PLL locks, on a clock-gated island, or on an emergency stop, a synchronous reset (2.3) cannot fire — no edge, no reset. An asynchronous reset takes effect the instant it asserts, clock or no clock; this page builds that form.
- An asynchronous reset is in the flop's sensitivity list, not its data path. The pattern is
always_ff @(posedge clk or negedge rst_n)withif (!rst_n)tested first (VHDL:process (clk, rst_n)testingrst_n = '0'beforerising_edge). Active-lowrst_nby convention. Because reset is in the list, the flop reacts to the reset transition itself — no clock edge needed. That single placement is the entire definition of asynchronous. - Assert is free; the de-assertion is the hazard. Asserting
rst_nforcesRESET_VALimmediately and safely. Releasing it is where the danger lives: because the de-assertion is asynchronous to the clock, a release near an edge can violate the flop's recovery/removal window and drive it metastable — a failure a synchronous reset never has. The cure is async assert, synchronous de-assert (the reset synchronizer of 2.5). - The trade-off vs synchronous (2.3). Asynchronous reset works with no clock and stays out of the datapath (no critical-path mux term); its costs are the mirror image — a dedicated reset tree, recovery/removal timing arcs, and the de-assert metastability hazard. Synchronous reset trades the other way. Choosing between them is the flagship 2.5.
- Two signature traps, and how verification catches them. Omitting
rst_nfrom the sensitivity list silently builds a synchronous reset that dies with a stopped clock; a glitchy/gated reset net turns a hazard into a spurious clear. Verify with a clocked self-checking testbench that assertsrst_noff a clock edge and checksqclears immediately (a synchronous reset would not), then confirms clean operation after release and noXafter the assert — the checks shown here in SystemVerilog, Verilog, and VHDL. The de-assert (recovery/removal) hazard the RTL sim cannot fully show is exactly why Section 2.5 exists.