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.)
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 flowThe 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.
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
endmoduleThe 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.
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
endmoduleThe 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.
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 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
endmoduleIn 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."
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; 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.
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
endmoduleThe 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).
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
endmoduleThe 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").
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 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
endmoduleVHDL 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.
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; 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.
// 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 // 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 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
endmoduleThe 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.
// 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 // 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 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
endmoduleIn 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.
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; 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; 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=1with garbage ond(8'hFFwhile the reset value is something else), advance one rising edge, and assertq === RESET_VAL. Driving garbage ondis deliberate: it proves the output came from the reset value, not from the data. The three §4a testbenches do exactly this — SVassert (q === RESET_VAL), Verilogif (q !== RESET_VAL) $display("FAIL…"), VHDLassert q = RESET_VAL report … severity error. - Reset beats enable (priority). Drive
rst=1, en=0across an edge and assertq === RESET_VAL. This is the property that distinguishes correctreset > enable > holdordering 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 knownd, advance an edge, and assertqtracksd(and the enable now gates updates: hold whenen=0, update whenen=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
Xonce reset has been sampled: SVassert (^q !== 1'bx), Verilogif (^q === 1'bx) $display("FAIL"), VHDLassert not is_x(q). This is the property that would catch the power-up bug of §7 — if the clock never ran during reset,qstaysXand 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 —
qstaysRESET_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 withenboth high (reset must win); and the two DebugLab/§6 failures — a clock that does not toggle during reset (the block never leavesX) and inverted priority (enswallowsrst). Each maps to a check above. - Expected waveform. On a correct run:
rstrises, and on the next risingclkedgeqsteps toRESET_VAL(not before — the change is edge-aligned, one clock afterrstasserts); whilerststays high,qholdsRESET_VALacross every edge; whenrstfalls, the next edge letsqfollowd/the update. The visual signature of the sync-reset-with-no-clock bug is the opposite:rstis high butclkis flat, andqsits atXthrough 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.
10. Related Tutorials
Continue in RTL Design Patterns (sibling and forward links unlock as they ship):
- The Register Pattern (D-FF) — Chapter 2.1; the flip-flop this page adds 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 > holdcomes 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:
- Multiplexers (2:1, N:1, one-hot) — Chapter 1.1; the reset-select and enable-select in front of the flop are 2:1 muxes — selection meeting state.
- Decoders · Encoders · Priority Encoders · Comparators · Parity & ECC Blocks · Encoding & Conversions — Chapter 1; the combinational building blocks that feed the registers this chapter builds.
- The RTL Design Mindset — Chapter 0.2; the State lens — a register with a reset is the smallest complete example of deliberate state initialization.
- What Is an RTL Design Pattern? — Chapter 0.1; why synchronous reset earns the name "pattern" — a recurring need, a reusable form, a synthesis reality, and a signature failure.
Verilog v1 prerequisites (the grammar this pattern transcribes into):
- Blocking and Non-Blocking Assignments — why every reset branch uses non-blocking
<=inside the clocked process, and how that models the flop. - Initial and Always Blocks — the
always @(posedge clk)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 random0/1(silicon), andXpropagates 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)loadRESET_VAL, else do the normal update. The sensitivity list is the clock edge only —rstlives 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 blockX(the §7 bug). And reset must be the firstifso 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
rstin the sensitivity list silently builds an asynchronous reset; testingenbeforerstinverts the priority. Verify with a clocked self-checking testbench: assertq === RESET_VALon the reset edge with garbage ond, assert reset beats a de-asserted enable, confirm the block leaves reset into normal operation, and check noXsurvives the first reset edge — the four checks shown here in SystemVerilog, Verilog, and VHDL.