A reset reaches every control flop in the chip. A single net with thousands of loads is slow and has uncertain skew.
Large skew breaks recovery and removal margins. Plan the tree like a clock tree:
One source (pin, power-on detector, watchdog), combined into a single asynchronous arst_n.
One reset synchroniser per clock domain, in that domain's clock. Never share a synchroniser between clocks.
Distribute one settled reset per domain through a buffered tree. Independently synchronising the raw reset twice can release interacting regions on different cycles. Any registered distribution copies must sample the same settled output and meet their timing checks.
Buffer the synchronised reset like a clock net, balanced where recovery/removal need it.
Document which resets drive which blocks, in one table.
Read both charts from the raw reset at the top down to the flops that use it.
In the wrong tree, each synchroniser can resolve a release on a different clk_a edge, so the left and right branches of one domain may start one cycle apart.
In the right tree, domain A makes one release decision and the buffers only copy it. This is reset_tree in the testbench below.
Domain B still has its own synchroniser, because it runs on a different clock.
Design choice | Why |
|---|---|
Synchroniser per domain | Release must be aligned to the clock that uses it |
Two buffered branches in domain A | Splits the load downstream of one synchroniser |
Branches share one settled reset | One release decision; implementation must still meet recovery/removal at every sink |
Asynchronous source goes to every synchroniser, nowhere else | No domain sees the raw reset |
No. Metastability can delay one chain by a cycle. Synchronise once per interacting domain and distribute that settled result; verify the distribution tree with STA.
The testbench drives two unrelated clocks through 300 random assert and release cycles.
`timescale 1ns/1ps
module reset_sync #(parameter int STAGES = 2) (input logic clk, arst_n, output logic srst_n);
if (STAGES < 2) begin : g_stages_check // legal range: STAGES >= 2
$error("reset_sync: STAGES must be at least 2");
end
logic [STAGES-1:0] sr;
always_ff @(posedge clk or negedge arst_n)
if (!arst_n) sr <= '0;
else sr <= {sr[STAGES-2:0], 1'b1};
assign srst_n = sr[STAGES-1];
endmodule
// One synchroniser per clock domain; physical buffers distribute its settled output.
module reset_tree (
input logic clk_a, clk_b,
input logic arst_n, // the single asynchronous source (pin, POR, watchdog...)
output logic rst_a_left_n, rst_a_right_n,
output logic rst_b_n
);
logic rst_a_n;
reset_sync sync_a (.clk(clk_a), .arst_n, .srst_n(rst_a_n));
assign rst_a_left_n = rst_a_n;
assign rst_a_right_n = rst_a_n;
reset_sync sync_b (.clk(clk_b), .arst_n, .srst_n(rst_b_n));
endmodule
The test uses periods of 7 ns and 11 ns and checks both branches on every falling edge of clk_a. The branches share one RTL signal, so the test does not model physical reset skew; check that in implementation timing reports.
The two-clock test (7 ns and 11 ns periods, 300 random assert and release cycles) checks both branches on every falling edge of clk_a. The branches share one RTL signal, so it does not model physical reset skew or prove recovery/removal; those need implementation timing reports. Independent synchroniser chains would need a metastability model to expose unequal release latency.