Skip to content

RTL Design Patterns · Chapter 2 · Registers & Sequential Building Blocks

Synchronous Reset

At power-up a flip-flop holds an unknown value, and a datapath seeded with unknowns computes more unknowns, so every design needs a way to force a known starting state before it can behave predictably. A synchronous reset does this by sampling the reset signal on the clock edge like any other input, tested first inside the clocked process, so on each rising edge the flop either loads its reset value or does its normal update. This lesson shows why that choice needs a live clock to take effect, why it times like ordinary datapath logic with no separate reset tree, and the strict reset-beats-enable-beats-hold priority. You will also meet its two silent traps: a stopped clock during reset and an inverted reset-versus-enable priority. All of it is coded in SystemVerilog, Verilog, and VHDL.

Foundation14 min readRTL Design PatternsSynchronous ResetRegistersReset PrioritySequential LogicPower-Up State

Chapter 2 · Section 2.3 · Registers & Sequential Building Blocks

1. The Engineering Problem

You have built a counter — the simplest sequential block there is. It is a register plus an incrementer, and on every clock edge it takes its current value, adds one, and stores the result. You wire it up, run it, and the counter reads X. Next cycle it reads X + 1, which is still X. It never counts, because it never had a first number to count from.

This is not a wiring mistake. It is the default state of all sequential logic. A flip-flop is a physical storage element, and when the chip powers up — before a single clock edge has landed — every flop holds whatever charge happened to settle on its internal nodes. In simulation that undefined value is X; in real silicon it is an unpredictable, per-flop, per-power-up 0 or 1. Any datapath that reads an X-valued register and computes on it produces more X, and the corruption spreads: one un-initialized state bit can turn an entire block's output into garbage that no amount of correct combinational logic can clean up.

So every stateful design needs an answer to one question: how does this block reach a known initial state? It is not optional and it is not automatic — the state elements will not initialize themselves to anything you can rely on. You must design a mechanism that, on demand, overwrites the unknown contents of your registers with a defined value (0 for a counter, an idle code for an FSM, all-ones for a leftover-full flag) so the block starts from a place you chose rather than a place physics chose. That mechanism is reset, and this page builds the synchronous form of it: a reset that is sampled on the clock edge, exactly like every other input to the flop, and that forces the known state the moment that edge arrives while reset is asserted. (The asynchronous form — reset that acts without waiting for an edge — is Section 2.4; here we build the synchronous half and the discipline that makes it correct.)

the-need.v — a counter with no reset never starts
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   reg [7:0] count;
 
   // BROKEN: no way to seed a known value. At power-up `count` is X.
   always @(posedge clk)
       count <= count + 1'b1;      // X + 1 == X, forever. It never counts.
 
   // The fix is a RESET input that, when asserted, overwrites the unknown
   // contents with a KNOWN value on the clock edge — the pattern this page builds:
   //   always @(posedge clk)
   //       if (rst) count <= 8'd0;   // reset tested FIRST => known start state
   //       else     count <= count + 1'b1;

2. Mental Model

3. Pattern Anatomy

A synchronous-reset register is a normal register with one extra branch at the very top of its clocked process. Four parts define it.

Reset tested first, inside the edge. The whole pattern lives inside always_ff @(posedge clk) (SystemVerilog), always @(posedge clk) (Verilog), or if rising_edge(clk) then … (VHDL). Critically, rst is not in the sensitivity list — only clk is. The reset is evaluated after the edge fires, as the first if inside the process. That placement is the entire difference between synchronous and asynchronous: synchronous reset is checked because an edge happened; asynchronous reset (2.4) is checked because reset itself happened, which is why it appears in the sensitivity list. Put rst in the sensitivity list of a block you intended to be synchronous and you have silently built an asynchronous reset — the accidental-async trap of §6.

The reset value. Reset does not mean "go to zero" — it means "go to the known value this block must start from." For a counter that's 0; for a one-hot FSM it's the idle-state one-hot code; for a full flag it might be 0, for an empty flag 1. The reset value (RESET_VAL) is a deliberate design decision and must be an actual known constant, not left implicit — a reset that loads an unknown value resets nothing.

Priority: reset > enable > hold. When the register also has a load enable (2.2), the reset branch must come first: on the edge, if rst load RESET_VAL; else if en load d; else hold. Reset outranks enable so the block can always be forced to its known state regardless of whether it is currently enabled. Invert that order — test en before rst — and a de-asserted enable makes the block ignore reset entirely (§6, §7).

It participates in ordinary setup/hold — the async trade-off (preview 2.4). Because the reset is just a synchronous input to the flop, it fans in through the data path and is timed as a normal setup/hold check at the D pin — no separate reset distribution network, and no recovery/removal arcs at all. That is synchronous reset's headline advantage: it is timing-friendly and needs no special reset-timing sign-off. The costs are the mirror image: it requires a live clock to take effect (a stopped clock during reset = no reset, §7), and the reset logic adds to the datapath in front of every flop (an extra mux term that can sit on the critical path). Asynchronous reset (2.4) trades exactly the other way — it works with no clock and stays out of the datapath, but adds a reset tree and recovery/removal timing and the async-deassert hazard. Weighing the two is the flagship 2.5; here we simply build the synchronous side cleanly.

Synchronous-reset register — the ordered question on the clock edge

data flow
Synchronous-reset register — the ordered question on the clock edgerising edge ofclknothing happens until an edge arrives1. rst asserted?tested FIRST — highest priorityload RESET_VALknown start state (0, idle, ...)2. en asserted?only reached when rst is lowload dnormal update3. else hold qkeep current value
Every path starts at the clock edge — no edge, no reset (this is why a stopped clock disables a synchronous reset). Inside the edge the process asks reset FIRST: if RST is high, load RESET_VAL and stop; only if RST is low does it consider EN and then hold. The order rst > en > hold is the priority, and it is the same in SystemVerilog, Verilog, and VHDL — three spellings of one ordered question sampled by the clock.

The anatomy also fixes what a synchronous reset does not do. It does not act between edges: assert rst mid-cycle and nothing changes until the next rising edge samples it. It does not survive a dead clock: gate the clock off during power-up reset and the flops never sample the reset, coming up X and staying X until the first real edge — the power-up bug of §7. And it does not need — must not have — rst in the sensitivity list; the only edge that matters is the clock's.

4. Real RTL Implementation

This is the core of the page. We build three patterns — (a) a width-generic register with a synchronous reset, (b) that register plus a load enable with the correct reset > enable > hold priority, and (c) the reset bug (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 "on the edge, test reset first" changes.

Two disciplines run through every block. First, rst is tested first and is never in the sensitivity list — only the clock edge triggers the process, so the reset stays synchronous. Second, every testbench generates a real clock and drives a reset sequence, because a synchronous reset that is never clocked is never tested; the checks confirm q becomes RESET_VAL on the reset edge regardless of d, that reset beats enable, and that no X survives the first reset edge.

4a. Register with a synchronous reset — the base pattern, three ways

Start with the atom of Chapter 2 plus a reset: a width-generic register whose clocked process tests rst first. When rst is high on the edge, q loads RESET_VAL; otherwise q loads d. The reset is synchronous because the process is sensitive to clk only.

reg_sync_rst.sv — width-generic register, synchronous reset (rst tested first)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst #(
       parameter int              WIDTH     = 8,          // datapath width
       parameter logic [WIDTH-1:0] RESET_VAL = '0         // KNOWN start value
   )(
       input  logic             clk,                      // ONLY the edge triggers
       input  logic             rst,                      // synchronous, active-high
       input  logic [WIDTH-1:0] d,                        // next data
       output logic [WIDTH-1:0] q                         // registered output
   );
       // Synchronous reset: the process is sensitive to the CLOCK EDGE ONLY.
       // rst is NOT in the sensitivity list, so it is sampled by the edge like any
       // other input. Testing it FIRST gives reset top priority over the update.
       always_ff @(posedge clk) begin
           if (rst) q <= RESET_VAL;      // on the edge, if reset -> load known value
           else     q <= d;              // else normal update
       end
   endmodule

The SystemVerilog testbench generates a free-running clock, then drives a reset sequence: hold rst high across an edge with random d and check q === RESET_VAL (reset beats data), release rst and check q tracks d (normal operation resumes), and confirm q is never X after the first reset edge.

reg_sync_rst_tb.sv — clocked self-check: q===RESET_VAL on reset edge, no X after
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_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;
       logic [WIDTH-1:0] d, q;
       int               errors = 0;
 
       reg_sync_rst #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut (.clk(clk), .rst(rst), .d(d), .q(q));
 
       always #5 clk = ~clk;                              // free-running clock, 10ns period
 
       initial begin
           // 1. Assert reset across an edge with GARBAGE on d -> q must ignore d.
           rst = 1'b1; d = 8'hFF;
           @(posedge clk); #1;
           assert (q === RESET_VAL)
               else begin $error("reset edge: q=%h exp=%h", q, RESET_VAL); errors++; end
           assert (^q !== 1'bx)                           // no X survives the reset edge
               else begin $error("q has X after reset: q=%h", q); errors++; end
 
           // 2. Release reset -> block leaves reset into NORMAL operation.
           rst = 1'b0; d = 8'h3C;
           @(posedge clk); #1;
           assert (q === 8'h3C)
               else begin $error("post-reset update: q=%h exp=3C", q); errors++; end
 
           if (errors == 0) $display("PASS: sync reset loads RESET_VAL on the edge, then resumes");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

The Verilog form is identical in intent — reg typing and $random/if(q!==exp) $display("FAIL") instead of assert. The if (rst) is still the first statement in an always @(posedge clk) block with clk as the only edge.

reg_sync_rst.v — synchronous-reset register in Verilog (posedge clk only)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst #(
       parameter WIDTH     = 8,
       parameter RESET_VAL = 8'h00
   )(
       input  wire             clk,                       // ONLY edge in the list
       input  wire             rst,                       // synchronous, active-high
       input  wire [WIDTH-1:0] d,
       output reg  [WIDTH-1:0] q
   );
       // Sensitivity is posedge clk ONLY -> rst is sampled by the edge (synchronous).
       // rst tested first -> reset has priority over the data update.
       always @(posedge clk) begin
           if (rst) q <= RESET_VAL;
           else     q <= d;
       end
   endmodule
reg_sync_rst_tb.v — clocked self-check (compare, print PASS/FAIL)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst_tb;
       parameter WIDTH     = 8;
       parameter RESET_VAL = 8'hA5;
       reg              clk, rst;
       reg  [WIDTH-1:0] d;
       wire [WIDTH-1:0] q;
       integer          errors;
 
       reg_sync_rst #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut (.clk(clk), .rst(rst), .d(d), .q(q));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;                              // free-running clock
 
       initial begin
           errors = 0;
           // 1. Reset across an edge with garbage on d.
           rst = 1'b1; d = 8'hFF;
           @(posedge clk); #1;
           if (q !== RESET_VAL) begin
               $display("FAIL: reset edge q=%h exp=%h", q, RESET_VAL); errors = errors + 1;
           end
           if (^q === 1'bx) begin                         // X-check after reset
               $display("FAIL: q has X after reset q=%h", q);          errors = errors + 1;
           end
           // 2. Release reset -> normal update.
           rst = 1'b0; d = 8'h3C;
           @(posedge clk); #1;
           if (q !== 8'h3C) begin
               $display("FAIL: post-reset q=%h exp=3C", q);            errors = errors + 1;
           end
           if (errors == 0) $display("PASS: sync reset loads RESET_VAL then resumes");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

In VHDL the same register is a clocked process whose only sensitivity is clk; the guard if rising_edge(clk) then fires on the edge, and inside it if rst = '1' then tests reset first. Because rst is examined inside the rising_edge guard (not as a separate sensitivity), it is synchronous — the direct VHDL counterpart of "reset in the body, not the sensitivity list."

reg_sync_rst.vhd — synchronous-reset register in VHDL (rst inside rising_edge)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity reg_sync_rst is
       generic (
           WIDTH     : positive := 8;
           RESET_VAL : std_logic_vector := "00000000"     -- KNOWN start value
       );
       port (
           clk : in  std_logic;
           rst : in  std_logic;                           -- synchronous, active-high
           d   : in  std_logic_vector(WIDTH-1 downto 0);
           q   : out std_logic_vector(WIDTH-1 downto 0)
       );
   end entity;
 
   architecture rtl of reg_sync_rst is
   begin
       -- Sensitivity list is clk ONLY. rst is tested INSIDE rising_edge, so it is
       -- sampled by the edge (synchronous) and, tested first, has top priority.
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   q <= RESET_VAL;                        -- on the edge: load known value
               else
                   q <= d;                                -- normal update
               end if;
           end if;
       end process;
   end architecture;
reg_sync_rst_tb.vhd — clocked self-check (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_sync_rst_tb is
   end entity;
 
   architecture sim of reg_sync_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 : 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_sync_rst
           generic map (WIDTH => WIDTH, RESET_VAL => RESET_VAL)
           port map (clk => clk, rst => rst, d => d, q => q);
 
       clk <= not clk after 5 ns;                         -- free-running clock
 
       stim : process
       begin
           -- 1. Reset across an edge with garbage on d -> q must load RESET_VAL.
           rst <= '1'; d <= x"FF";
           wait until rising_edge(clk); wait for 1 ns;
           assert q = RESET_VAL
               report "reset edge: q /= RESET_VAL" severity error;
           assert not is_x(q)                             -- no X survives the reset edge
               report "q has X after reset" severity error;
           -- 2. Release reset -> normal update.
           rst <= '0'; d <= x"3C";
           wait until rising_edge(clk); wait for 1 ns;
           assert q = x"3C"
               report "post-reset update mismatch" severity error;
           report "reg_sync_rst self-check complete" severity note;
           wait;
       end process;
   end architecture;

4b. Synchronous reset + enable — priority reset > enable > hold

Now add the load enable from 2.2. The register holds by default, updates only when en is high, and is forced to RESET_VAL when rst is high — and rst must be tested before en, so reset can seize the flop even while it is disabled. The priority is literally the order of the if / else if / else.

reg_sync_rst_en.sv — reset > enable > hold (reset tested BEFORE enable)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst_en #(
       parameter int              WIDTH     = 8,
       parameter logic [WIDTH-1:0] RESET_VAL = '0
   )(
       input  logic             clk,
       input  logic             rst,                      // priority 1 (highest)
       input  logic             en,                       // priority 2
       input  logic [WIDTH-1:0] d,
       output logic [WIDTH-1:0] q
   );
       always_ff @(posedge clk) begin
           if      (rst) q <= RESET_VAL;   // 1. reset wins, even when en is low
           else if (en)  q <= d;           // 2. enabled update
           else          q <= q;           // 3. hold (explicit for readability)
       end
   endmodule

The ordering is the whole point: because rst is the first if, an asserted reset overrides a de-asserted enable. The testbench proves exactly that — it drives rst=1, en=0 across an edge and asserts q still becomes RESET_VAL, then checks that with rst=0 the enable gates updates normally (hold when en=0, update when en=1).

reg_sync_rst_en_tb.sv — reset beats a de-asserted enable; enable gates otherwise
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst_en_tb;
       localparam int              WIDTH     = 8;
       localparam logic [WIDTH-1:0] RESET_VAL = 8'h5A;
       logic             clk = 0, rst, en;
       logic [WIDTH-1:0] d, q;
       int               errors = 0;
 
       reg_sync_rst_en #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut
           (.clk(clk), .rst(rst), .en(en), .d(d), .q(q));
 
       always #5 clk = ~clk;
 
       initial begin
           // Seed a known non-reset value first (rst=0, en=1).
           rst = 0; en = 1; d = 8'h11; @(posedge clk); #1;
 
           // 1. reset MUST beat a DE-ASSERTED enable: rst=1, en=0.
           rst = 1; en = 0; d = 8'hFF; @(posedge clk); #1;
           assert (q === RESET_VAL)
               else begin $error("reset lost to disabled en: q=%h exp=%h", q, RESET_VAL); errors++; end
 
           // 2. rst low, en low -> HOLD (q keeps RESET_VAL).
           rst = 0; 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 low, en high -> normal update.
           rst = 0; 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: priority rst > en > hold holds");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

The Verilog version is the same if (rst) … else if (en) … else chain in an always @(posedge clk); the testbench self-checks with if(q!==exp) $display("FAIL").

reg_sync_rst_en.v — reset > enable > hold in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst_en #(
       parameter WIDTH     = 8,
       parameter RESET_VAL = 8'h00
   )(
       input  wire             clk,
       input  wire             rst,                       // highest priority
       input  wire             en,
       input  wire [WIDTH-1:0] d,
       output reg  [WIDTH-1:0] q
   );
       always @(posedge clk) begin
           if      (rst) q <= RESET_VAL;   // reset FIRST -> beats a low en
           else if (en)  q <= d;           // enabled update
           else          q <= q;           // hold
       end
   endmodule
reg_sync_rst_en_tb.v — self-checking: reset beats disabled enable, enable gates otherwise
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reg_sync_rst_en_tb;
       parameter WIDTH     = 8;
       parameter RESET_VAL = 8'h5A;
       reg              clk, rst, en;
       reg  [WIDTH-1:0] d;
       wire [WIDTH-1:0] q;
       integer          errors;
 
       reg_sync_rst_en #(.WIDTH(WIDTH), .RESET_VAL(RESET_VAL)) dut
           (.clk(clk), .rst(rst), .en(en), .d(d), .q(q));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;
 
       initial begin
           errors = 0;
           rst = 0; en = 1; d = 8'h11; @(posedge clk); #1;      // seed known value
 
           rst = 1; en = 0; d = 8'hFF; @(posedge clk); #1;      // reset vs disabled en
           if (q !== RESET_VAL) begin
               $display("FAIL: reset lost to disabled en q=%h exp=%h", q, RESET_VAL); errors=errors+1;
           end
 
           rst = 0; 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
 
           rst = 0; 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: priority rst > en > hold holds");
           else             $display("FAIL: %0d mismatches", errors);
           $finish;
       end
   endmodule

VHDL nests the priority if inside the rising_edge guard: if rst = '1' then … elsif en = '1' then … else …. The elsif chain is the priority — rst first, en second, hold last.

reg_sync_rst_en.vhd — reset > enable > hold in VHDL (elsif = priority)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   entity reg_sync_rst_en is
       generic (
           WIDTH     : positive := 8;
           RESET_VAL : std_logic_vector := "00000000"
       );
       port (
           clk : in  std_logic;
           rst : in  std_logic;                           -- 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_sync_rst_en is
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if rst = '1' then
                   q <= RESET_VAL;                        -- 1. reset wins over a low en
               elsif en = '1' then
                   q <= d;                                -- 2. enabled update
               else
                   q <= q;                                -- 3. hold
               end if;
           end if;
       end process;
   end architecture;
reg_sync_rst_en_tb.vhd — self-checking priority TB (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_sync_rst_en_tb is
   end entity;
 
   architecture sim of reg_sync_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, 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_sync_rst_en
           generic map (WIDTH => WIDTH, RESET_VAL => RESET_VAL)
           port map (clk => clk, rst => rst, en => en, d => d, q => q);
 
       clk <= not clk after 5 ns;
 
       stim : process
       begin
           rst <= '0'; en <= '1'; d <= x"11";             -- seed a known non-reset value
           wait until rising_edge(clk); wait for 1 ns;
 
           rst <= '1'; en <= '0'; d <= x"FF";             -- reset MUST beat a low enable
           wait until rising_edge(clk); wait for 1 ns;
           assert q = RESET_VAL report "reset lost to disabled enable" severity error;
 
           rst <= '0'; en <= '0'; d <= x"22";             -- hold
           wait until rising_edge(clk); wait for 1 ns;
           assert q = RESET_VAL report "hold failed" severity error;
 
           rst <= '0'; 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_sync_rst_en self-check complete" severity note;
           wait;
       end process;
   end architecture;

4c. The reset bug — buggy vs fixed, in all three HDLs

Two traps break a synchronous reset without a syntax error, and they are the ones §7 dramatizes. The first is the accidental-async trap: adding rst to the sensitivity list (SV/Verilog) or making the process trigger on rst (VHDL) turns a reset you intended to be synchronous into an asynchronous one — it now fires the instant rst changes, not on the clock edge, changing the block's timing model and reset behaviour. The second is priority inversion: testing en before rst, so a de-asserted enable makes the block ignore reset. Both are shown buggy-vs-fixed below.

reset_bug.sv — accidental-async (BUGGY) vs synchronous (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: rst is in the sensitivity list. This is NO LONGER a synchronous reset --
   //        the block fires whenever rst changes, so the reset acts asynchronously.
   //        Intended synchronous, silently built asynchronous (different timing model).
   module reg_accidental_async (
       input  logic clk, rst,
       input  logic [7:0] d,
       output logic [7:0] q
   );
       always_ff @(posedge clk or posedge rst) begin   // <- rst in the list = ASYNC
           if (rst) q <= 8'h00;
           else     q <= d;
       end
   endmodule
 
   // FIXED: sensitivity is the clock edge ONLY. rst is sampled on the edge -- a true
   //        SYNCHRONOUS reset, timed as ordinary datapath logic.
   module reg_true_sync (
       input  logic clk, rst,
       input  logic [7:0] d,
       output logic [7:0] q
   );
       always_ff @(posedge clk) begin                  // <- clk ONLY = SYNC
           if (rst) q <= 8'h00;
           else     q <= d;
       end
   endmodule
reset_bug_priority.sv — priority inversion (BUGGY) vs reset-first (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: enable tested BEFORE reset. When en is low, the reset branch is never
   //        reached, so an asserted rst is IGNORED whenever the block is disabled.
   module reg_priority_bad (
       input  logic clk, rst, en,
       input  logic [7:0] d,
       output logic [7:0] q
   );
       always_ff @(posedge clk) begin
           if      (en)  q <= d;           // en checked first...
           else if (rst) q <= 8'h00;       // ...so rst only matters when en is LOW: wrong
       end
   endmodule
 
   // FIXED: reset tested first -> reset always wins, regardless of en.
   module reg_priority_good (
       input  logic clk, rst, en,
       input  logic [7:0] d,
       output logic [7:0] q
   );
       always_ff @(posedge clk) begin
           if      (rst) q <= 8'h00;       // reset FIRST
           else if (en)  q <= d;
       end
   endmodule
reset_bug_tb.sv — assert rst wins even when en is low; catches the inversion
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reset_bug_tb;
       logic clk = 0, rst, en;
       logic [7:0] d, q;
       int errors = 0;
 
       // Bind to reg_priority_good to PASS; to reg_priority_bad to see the inversion.
       reg_priority_good dut (.clk(clk), .rst(rst), .en(en), .d(d), .q(q));
 
       always #5 clk = ~clk;
 
       initial begin
           rst = 0; en = 1; d = 8'h9E; @(posedge clk); #1;   // seed a value
           // Assert reset WHILE DISABLED: a correct sync reset must still fire.
           rst = 1; en = 0; d = 8'hFF; @(posedge clk); #1;
           assert (q === 8'h00)
               else begin $error("reset ignored while en low: q=%h exp=00", q); errors++; end
           if (errors == 0) $display("PASS: reset wins over a de-asserted enable");
           else             $display("FAIL: %0d mismatches (priority inverted)", errors);
           $finish;
       end
   endmodule

The Verilog pair is the same story with reg outputs; the accidental-async trap is again or posedge rst in the event list, the inversion is again en before rst.

reset_bug.v — accidental-async (BUGGY) vs synchronous (FIXED) in Verilog
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: rst in the event list -> the reset is ASYNCHRONOUS, not synchronous.
   module reg_accidental_async (
       input  wire clk, rst,
       input  wire [7:0] d,
       output reg  [7:0] q
   );
       always @(posedge clk or posedge rst) begin     // rst here = async reset
           if (rst) q <= 8'h00;
           else     q <= d;
       end
   endmodule
 
   // FIXED: posedge clk ONLY -> synchronous reset, sampled on the edge.
   module reg_true_sync (
       input  wire clk, rst,
       input  wire [7:0] d,
       output reg  [7:0] q
   );
       always @(posedge clk) begin                     // clk only = sync reset
           if (rst) q <= 8'h00;
           else     q <= d;
       end
   endmodule
reset_bug_priority.v — priority inversion (BUGGY) vs reset-first (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // BUGGY: en before rst -> reset ignored while the block is disabled.
   module reg_priority_bad (
       input  wire clk, rst, en,
       input  wire [7:0] d,
       output reg  [7:0] q
   );
       always @(posedge clk) begin
           if      (en)  q <= d;
           else if (rst) q <= 8'h00;      // only reached when en is low: wrong
       end
   endmodule
 
   // FIXED: rst first -> reset always wins.
   module reg_priority_good (
       input  wire clk, rst, en,
       input  wire [7:0] d,
       output reg  [7:0] q
   );
       always @(posedge clk) begin
           if      (rst) q <= 8'h00;
           else if (en)  q <= d;
       end
   endmodule
reset_bug_tb.v — self-checking; reset must win over a de-asserted enable
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   module reset_bug_tb;
       reg  clk, rst, en;
       reg  [7:0] d;
       wire [7:0] q;
       integer errors;
 
       // Bind to reg_priority_good to PASS; to reg_priority_bad to observe the inversion.
       reg_priority_good dut (.clk(clk), .rst(rst), .en(en), .d(d), .q(q));
 
       initial clk = 1'b0;
       always #5 clk = ~clk;
 
       initial begin
           errors = 0;
           rst = 0; en = 1; d = 8'h9E; @(posedge clk); #1;    // seed
           rst = 1; en = 0; d = 8'hFF; @(posedge clk); #1;    // reset while disabled
           if (q !== 8'h00) begin
               $display("FAIL: reset ignored while en low q=%h exp=00", q); errors = errors + 1;
           end
           if (errors == 0) $display("PASS: reset wins over a de-asserted enable");
           else             $display("FAIL: %0d mismatches (priority inverted)", errors);
           $finish;
       end
   endmodule

In VHDL the accidental-async trap appears when the process sensitivity list (or a leading if rst = '1' guard before the rising_edge test) makes reset act off the edge; the synchronous version tests rst inside rising_edge. The inversion is again en checked before rst.

reset_bug.vhd — accidental-async (BUGGY) vs synchronous (FIXED) in VHDL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   -- BUGGY: rst is in the sensitivity list AND tested before rising_edge, so the
   --        reset acts asynchronously -- not the synchronous behaviour intended.
   entity reg_accidental_async is
       port ( clk, rst : 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_accidental_async is
   begin
       process (clk, rst)                              -- rst in the list = async
       begin
           if rst = '1' then                           -- tested OUTSIDE rising_edge
               q <= (others => '0');
           elsif rising_edge(clk) then
               q <= d;
           end if;
       end process;
   end architecture;
 
   -- FIXED: sensitivity is clk ONLY; rst tested INSIDE rising_edge -> synchronous.
   entity reg_true_sync is
       port ( clk, rst : 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_sync is
   begin
       process (clk)                                   -- clk only
       begin
           if rising_edge(clk) then
               if rst = '1' then q <= (others => '0'); -- sampled on the edge = sync
               else              q <= d;
               end if;
           end if;
       end process;
   end architecture;
reset_bug_priority.vhd — priority inversion (BUGGY) vs reset-first (FIXED)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   library ieee;
   use ieee.std_logic_1164.all;
 
   -- BUGGY: en tested before rst -> reset ignored while disabled.
   entity reg_priority_bad is
       port ( clk, rst, en : 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_priority_bad is
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if    en  = '1' then q <= d;                 -- en first...
               elsif rst = '1' then q <= (others => '0');   -- ...rst only when en low: wrong
               end if;
           end if;
       end process;
   end architecture;
 
   -- FIXED: rst tested first -> reset always wins.
   entity reg_priority_good is
       port ( clk, rst, en : 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_priority_good is
   begin
       process (clk)
       begin
           if rising_edge(clk) then
               if    rst = '1' then q <= (others => '0');   -- reset FIRST
               elsif en  = '1' then q <= d;
               end if;
           end if;
       end process;
   end architecture;
reset_bug_tb.vhd — self-checking; reset must beat a de-asserted enable
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, en : std_logic;
       signal d, q : std_logic_vector(7 downto 0);
   begin
       -- Bind to reg_priority_good to PASS; to reg_priority_bad to observe the inversion.
       dut : entity work.reg_priority_good
           port map (clk => clk, rst => rst, en => en, d => d, q => q);
 
       clk <= not clk after 5 ns;
 
       stim : process
       begin
           rst <= '0'; en <= '1'; d <= x"9E";             -- seed a value
           wait until rising_edge(clk); wait for 1 ns;
           rst <= '1'; en <= '0'; d <= x"FF";             -- reset WHILE disabled
           wait until rising_edge(clk); wait for 1 ns;
           assert q = x"00"
               report "reset ignored while enable low (priority inverted)" 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 out of the sensitivity list (test it inside the edge) or you have silently built an asynchronous reset, and test rst before en or a disabled block ignores its reset.

5. Verification Strategy

A synchronous-reset register is sequential, so its correctness is a small set of edge-triggered properties, and every one of them requires a running clock to observe — a testbench that forgets to toggle the clock will pass a broken reset by never testing it. The single invariant that captures the pattern is:

On any rising edge where reset is asserted, the output becomes the reset value — regardless of the data and regardless of the enable — and after that edge the output is a known (non-X) value.

Everything below is that invariant, made checkable, mapped onto the clocked testbenches in §4.

  • Reset-on-the-edge, self-checking. Generate a free-running clock, drive rst=1 with garbage on d (8'hFF while the reset value is something else), advance one rising edge, and assert q === RESET_VAL. Driving garbage on d is deliberate: it proves the output came from the reset value, not from the data. The three §4a testbenches do exactly this — SV assert (q === RESET_VAL), Verilog if (q !== RESET_VAL) $display("FAIL…"), VHDL assert q = RESET_VAL report … severity error.
  • Reset beats enable (priority). Drive rst=1, en=0 across an edge and assert q === RESET_VAL. This is the property that distinguishes correct reset > enable > hold ordering from the inverted bug of §6 — with the enable de-asserted, only a reset that is tested first can still force the known value. The §4b testbenches assert this directly, and the §4c bug TB is built around it (bind it to the inverted DUT and it fails).
  • Leaving reset into normal operation. After the reset edge, de-assert rst, drive a known d, advance an edge, and assert q tracks d (and the enable now gates updates: hold when en=0, update when en=1). This confirms the block exits reset cleanly into ordinary behaviour rather than sticking in the reset value.
  • No X after the first reset edge. Assert the output carries no X once reset has been sampled: SV assert (^q !== 1'bx), Verilog if (^q === 1'bx) $display("FAIL"), VHDL assert not is_x(q). This is the property that would catch the power-up bug of §7 — if the clock never ran during reset, q stays X and this check fires. It is the difference between "reset was written" and "reset actually took effect."
  • Corner cases and failure modes. Exercise: reset asserted for multiple consecutive edges (idempotent — q stays RESET_VAL); reset asserted mid-cycle then de-asserted before an edge (a true sync reset does nothing until the next edge — a synchronous reset can be missed if it isn't wide enough to be sampled); reset with en both high (reset must win); and the two DebugLab/§6 failures — a clock that does not toggle during reset (the block never leaves X) and inverted priority (en swallows rst). Each maps to a check above.
  • Expected waveform. On a correct run: rst rises, and on the next rising clk edge q steps to RESET_VAL (not before — the change is edge-aligned, one clock after rst asserts); while rst stays high, q holds RESET_VAL across every edge; when rst falls, the next edge lets q follow d/the update. The visual signature of the sync-reset-with-no-clock bug is the opposite: rst is high but clk is flat, and q sits at X through the whole reset window because no edge ever sampled the reset.

6. Common Mistakes

Synchronous reset with no running clock at power-up. The signature synchronous-reset failure. Because the reset is sampled on the clock edge, it takes effect only if edges are arriving. If the clock is gated off, stopped, or simply hasn't started (a PLL still locking at power-up) while rst is asserted, the flops never sample the reset — they keep their unknown power-up contents and the block comes up X and stays X until the first real edge. This is a classic power-up X-propagation bug, and it is invisible in an RTL sim that happens to toggle the clock throughout reset. The rule: a synchronous reset is only as good as the clock that samples it — the reset window must overlap live, running clock edges, or the reset does nothing.

Reset priority inverted (enable tested before reset). Writing if (en) … else if (rst) … puts the reset branch behind the enable, so whenever the block is disabled (en=0) the reset is never evaluated — an asserted reset is silently ignored exactly when you most want it to fire. Reset must be the first if in the process (reset > enable > hold) so it can always force the known state regardless of enable. The §4c/§7 bug is precisely this ordering.

Reset value not actually known or driven. Reset means "load a known value," so the reset value must be a real constant (0, the idle one-hot code, 1 for an initially-full flag), stated deliberately — not left implicit and not sourced from something that is itself X. A reset that assigns an unknown value resets nothing; the flop leaves reset still holding X. Every reset branch must load a defined RESET_VAL.

Putting rst in the sensitivity list when you meant synchronous. always @(posedge clk or posedge rst) (SV/Verilog), or a VHDL process that tests rst outside the rising_edge guard, does not add a "faster" synchronous reset — it turns the reset asynchronous. The block now reacts the instant rst changes rather than on the clock edge, changing its timing model (it now needs recovery/removal checks and a reset tree) and its behaviour. If you intended synchronous, the only edge in the list is the clock's, and rst is tested inside it. (Choosing async on purpose is Section 2.4 — but do it deliberately, not by an accidental sensitivity-list entry.)

Assuming a narrow reset pulse will be caught. Because a synchronous reset is only sampled at edges, a reset pulse narrower than a clock period can fall entirely between two edges and never be sampled — the reset is simply missed. A synchronous reset must be held across at least one full rising edge (in practice, several, to cover clock uncertainty) to be guaranteed to take effect. This is the flip side of "needs a running clock": it also needs to stay asserted long enough for an edge to see it.

7. DebugLab

The block that powered up as X and never recovered — a synchronous reset with no clock to sample it

The engineering lesson: a synchronous reset is only as good as the clock that samples it — reset written perfectly in RTL does nothing if no clock edge occurs while it is asserted. The tell is a bug that appears only when the clock's real start-up is modelled ("passes in RTL, comes up X in silicon / gate-level"): RTL sims toggle the clock from time zero and hide the whole class of power-up problems. Two habits make it impossible, and they hold across SystemVerilog, Verilog, and VHDL: sequence reset so its asserted window overlaps live, running clock edges (release reset only after the clock is stable), and — the companion priority rule from §6 — test reset first inside the process (reset > enable > hold) so that once edges do arrive, reset actually wins. When "force a known state with no running clock" is a hard requirement, that is the deliberate case for asynchronous reset (2.4), not a broken synchronous one.

8. Interview Q&A

9. Exercises

Design and reasoning exercises — no syntax drills. Each rehearses the "reset is a synchronous input, tested first, sampled by the clock" model.

Exercise 1 — Predict the power-up behaviour

A block uses a textbook synchronous reset (if (rst) state <= IDLE; else …). Its reset comes from a power-on-reset that holds rst high for 100 ns after power-up; its clock comes from a PLL that begins toggling only 150 ns after power-up. (a) On a pure RTL sim whose clock toggles from time zero, does the block reset correctly? (b) In silicon, what value does state hold at 120 ns, and why? (c) Give two independent design changes that make the reset reliable, and name what each costs. (Hint: the reset is only as good as the clock that samples it.)

Exercise 2 — Order the priority, then break it

Write (in words or RTL, any HDL) a register with a synchronous reset and a load enable, and state the priority order explicitly. Then: (i) show the one-line change that inverts the priority so a de-asserted enable makes the block ignore reset, (ii) describe the exact stimulus a self-checking testbench would use to catch that inversion (which signals, in what state, checked after which edge), and (iii) explain why simply testing "reset works when en=1" would let the bug slip through.

Exercise 3 — Synchronous or asynchronous, from the symptom

For each observed symptom, state whether the reset in the design is most likely synchronous or asynchronous, 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 asserts, even with the clock held static; (c) static timing reports recovery and removal violations on the reset path; (d) a 3 ns reset pulse (clock period 10 ns) is sometimes ignored entirely. (Hint: two of these are signatures of synchronous reset and two of asynchronous.)

Exercise 4 — Choose the reset value

For each register, state the correct RESET_VAL and one sentence of why: (a) an up-counter that must start counting from the bottom; (b) a one-hot FSM state register whose safe start state is IDLE; (c) a FIFO empty flag; (d) a FIFO full flag; (e) a "configuration valid" flag that must be low until software writes the config. Then explain, in one line, why a reset that loads an unknown value (e.g. sourced from another still-X register) resets nothing.

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 a 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 prioritizes below reset — where reset > enable > hold comes from.
  • Asynchronous Reset — Chapter 2.4; the other half of reset — force a known state without waiting for a clock edge, with its reset tree and recovery/removal timing.
  • Reset Strategy — Chapter 2.5 (flagship); when to choose synchronous vs asynchronous, async-assert/sync-deassert, and the reset synchronizer.
  • Shift Registers — Chapter 2.6; a chain of the reset-bearing registers built here, and where a known reset value seeds a serial pipeline.

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) construct the whole pattern lives inside, and why the sensitivity list is the clock edge only.
  • If–Else Statements — the priority if (rst) … else if (en) … chain that is the reset-first ordering.
  • Case Statements — the alternative spelling of the reset/enable/hold select when it grows beyond two branches.
  • Level-Sensitive Timing — the edge-vs-level distinction underneath why a synchronous reset must be sampled by an edge rather than acting on a level.

11. Summary

  • A register comes up unknown; reset is how it reaches a known state. At power-up every flop holds X (sim) / a random 0/1 (silicon), and X propagates through the datapath — so every stateful design must deliberately force a known initial value. Reset is that mechanism; this page builds the synchronous form.
  • A synchronous reset is just another synchronous input, tested first inside the clocked process. On the rising edge, if (rst) load RESET_VAL, else do the normal update. The sensitivity list is the clock edge onlyrst lives in the body, not the list. That single choice is the entire definition of synchronous.
  • It needs a running clock, and its priority is reset > enable > hold. Because reset is sampled on the edge, no edge means no reset — a stopped/gated clock during power-up leaves the block X (the §7 bug). And reset must be the first if so it overrides a de-asserted enable; invert the order and reset is silently ignored while disabled.
  • The timing trade-off vs asynchronous (preview 2.4). Synchronous reset times like ordinary datapath logic — no separate reset tree, no recovery/removal checks — which is timing-friendly; the costs are that it requires a live clock and adds a mux term to the datapath. Asynchronous reset trades the other way. Choosing between them is the flagship 2.5.
  • Two signature traps, and how verification catches them. Putting rst in the sensitivity list silently builds an asynchronous reset; testing en before rst inverts the priority. Verify with a clocked self-checking testbench: assert q === RESET_VAL on the reset edge with garbage on d, assert reset beats a de-asserted enable, confirm the block leaves reset into normal operation, and check no X survives the first reset edge — the four checks shown here in SystemVerilog, Verilog, and VHDL.