Skip to content

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.

the-need.v — a synchronous reset asserted into a dead clock does nothing
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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 flow
Asynchronous-reset register — reset reacts on its own event, before the clockrst_n falls(assert)reset in the sensitivity list — fires NOWforce q =RESET_VALimmediate, NO clock edge neededrising edge ofclkreached only while rst_n is high1. rst_nasserted?tested FIRST inside the process2. else load dnormal clocked updaterst_n rises(release)HAZARD: recovery/removal → metastability
Two independent triggers reach the flop. The RST_N-falls path (left) fires the instant reset asserts — no clock edge, so it works with the clock stopped; that is the entire reason asynchronous reset exists. The clock-edge path (right) is ordinary clocked behaviour, but the process still tests RST_N FIRST, so an asserted reset wins over the data. The dangerous transition is the RELEASE: RST_N rising near a clock edge can violate the flop's recovery/removal window and drive it metastable — the hazard synchronous reset never has, and the reason Section 2.5 de-asserts reset synchronously. Same structure in SystemVerilog, Verilog, and VHDL — three spellings of react to reset OR the edge, reset first.

The 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.

reg_async_rst.sv — width-generic register, async active-low reset (rst_n in the list)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

The 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.

reg_async_rst_tb.sv — assert rst_n OFF an edge; q clears IMMEDIATELY, then resumes
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

The 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.

reg_async_rst.v — asynchronous active-low reset in Verilog (negedge rst_n in the list)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
reg_async_rst_tb.v — clocked self-check: q clears off-edge, then resumes (PASS/FAIL)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

In 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."

reg_async_rst.vhd — asynchronous active-low reset in VHDL (rst_n before rising_edge)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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;
reg_async_rst_tb.vhd — clocked self-check: assert q clears off-edge (assert report severity)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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.

reg_async_rst_en.sv — async reset (outside edge) + synchronous enable (inside edge)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

The 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.

reg_async_rst_en_tb.sv — reset overrides a disabled enable, off-edge; enable gates otherwise
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

The 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.

reg_async_rst_en.v — async reset + synchronous enable in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
reg_async_rst_en_tb.v — self-checking: reset overrides disabled enable off-edge
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

VHDL 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.

reg_async_rst_en.vhd — async reset outside the edge, enable inside it (VHDL)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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;
reg_async_rst_en_tb.vhd — self-checking: reset overrides disabled enable off-edge (assert report)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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.

reset_bug.sv — missing sensitivity (BUGGY, becomes sync) vs async (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
reset_bug_glitch.sv — gated/glitchy reset (BUGGY) vs clean reset net (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
reset_bug_tb.sv — assert reset clears q OFF an edge; catches the missing-sensitivity trap
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

The 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.

reset_bug.v — missing sensitivity (BUGGY) vs async (FIXED) in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
reset_bug_glitch.v — gated/glitchy reset (BUGGY) vs clean reset net (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
reset_bug_tb.v — self-checking; reset must clear q off a clock edge
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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
   endmodule

In 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.

reset_bug.vhd — missing sensitivity (BUGGY) vs async (FIXED) in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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;
reset_bug_glitch.vhd — gated/glitchy reset (BUGGY) vs clean reset net (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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;
reset_bug_tb.vhd — self-checking; reset must clear q off a clock edge
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 q with a known value, then assert rst_n between two rising edges with garbage on d, and — before the next edge — assert q === 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 — SV assert (q === RESET_VAL) after a mid-cycle rst_n = 0, Verilog if (q !== RESET_VAL) $display("FAIL"), VHDL assert 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=0 between edges, assert q === 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_n stays low, q holds RESET_VAL across intervening edges (idempotent). After releasing rst_n (well clear of an edge, to avoid the recovery/removal window in the RTL sim), drive a known d, advance an edge, and assert q tracks d (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 X once reset has asserted: SV assert (^q !== 1'bx), Verilog if (^q === 1'bx) $display("FAIL"), VHDL assert not is_x(q) — the reset must land a defined value, not leave X.
  • 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 — releasing rst_n inside 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_n falls and q steps to RESET_VAL immediately (not on the next edge — the change is aligned to the reset transition, clock-independent); while rst_n stays low, q holds RESET_VAL regardless of the clock; when rst_n rises, the next clock edge lets q follow d. The visual signature of the missing-sensitivity bug is the opposite: q does not move when rst_n falls 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 — is q going metastable (an intermediate/oscillating value resolving unpredictably) when rst_n releases 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.

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:

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) with if (!rst_n) tested first (VHDL: process (clk, rst_n) testing rst_n = '0' before rising_edge). Active-low rst_n by 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_n forces RESET_VAL immediately 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_n from 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 asserts rst_n off a clock edge and checks q clears immediately (a synchronous reset would not), then confirms clean operation after release and no X after 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.