Digital Design / Module 10: Clock domain crossing
59. Reset synchronizers and RDC
Treat reset as a crossing too.
See it move
The idea
Reset deserves as much care as a clock. If it's released at a bad time, flops can go , or different parts of a design can start a cycle apart. A reset fixes this for one , and each domain needs its own.
The reset synchronizer
module reset_sync (input clk, arst_n, output rst_n);
reg r1, r2;
always_ff @(posedge clk or negedge arst_n)
if (!arst_n) begin
r1 <= 1'b0;
r2 <= 1'b0;
end else begin
r1 <= 1'b1;
r2 <= r1;
end
assign rst_n = r2;
endmoduleAssertion is asynchronous, so reset works without a clock, and reaches every flop at once. Release is synchronized: the 1 must shift through two flops before rst_n goes high, which aligns it to the clock. The first flop can go metastable, as with a normal synchronizer, and the second gives it time to settle.
Reset domain crossings (RDC)
Clocks aren't the only concern. Suppose flop A has async reset R1, and flop B has reset R2 on the same clock. If R1 asserts while B is not in reset, then A's output changes at an arbitrary moment, regardless of the clock. B can capture it during a transition and go metastable, with no clock crossing in sight. This is a reset domain crossing.
Handling RDC
- Reset the receiver first or at the same time, whenever possible.
- Isolate signals during reset, by gating them to a safe value.
- Sequence resets: release upstream blocks in a known order, so that nobody receives data before it can be handled.
- Run an RDC check. Normal CDC tools don't always cover it.
Multiple clock domains
Each domain has its own synchronizer, driven by a common global reset. Domains leave reset at slightly different times. and that cross between them should be designed to stay quiet until both sides are ready.
Common mistake
Using one synchronized reset in every clock domain. The release is aligned only for the domain it was synchronized to. In any other domain, it's effectively asynchronous, which is exactly what and failures come from.
Key ideas
- Assert async, de-assert synchronously.
- Asynchronous resets in one domain can corrupt another.
- Sequence resets across domains.
Your exercise
Find the bug
This code has one deliberate bug. Read the symptom, then click the line you think is wrong.
The fix
r2 <= r1;
Why it failed
If the second flop takes a constant 1, the output releases on the first edge after reset goes away. That first flop's edge is exactly the one that may go metastable, and nothing has filtered it. Feeding r1 into r2 adds the stage that gives it a cycle to settle.
Interview questions
coreWhat is RDC?
A reset domain crossing: a flop reset by one async reset drives a flop in another reset domain, risking metastability.
basicWhy do designs assert reset asynchronously but release it synchronously?
Asynchronous assertion puts every flop into reset immediately, even when the clock is not running. Release is different: if reset is removed too close to a clock edge, flops can go metastable or come out of reset on different cycles. A reset synchronizer shifts a 1 through two flops after the reset input goes away, so the release reaches the design just after a clock edge, with nearly a full period before the next one.
basicWhat are recovery and removal checks?
They are the reset equivalents of setup and hold for the moment an asynchronous reset is released. Recovery requires the release to happen a minimum time before the next active clock edge. Removal requires it to happen a minimum time after the previous edge. If either is violated, a flop may not know whether it is still in reset at that edge, and can go metastable or leave reset a cycle late.
coreReview this reset synchronizer. What is wrong, and when does its output rise?
module reset_sync_bug (input clk, arst_n, output rst_n);
reg r1, r2;
always @(posedge clk or negedge arst_n)
if (!arst_n) begin r1 <= 1'b0; r2 <= 1'b0; end
else begin r1 <= 1'b1; r2 <= 1'b1; end
assign rst_n = r2;
endmoduleThe second flop loads a constant 1 instead of r1, so r1 does nothing. The output rises on the very first clock edge after the reset input is released. That is the edge where the release may be too close to the clock, and there is no second stage to give it time to settle. The fix is r2 <= r1;, which makes the output rise one clock later, after the first flop has had a full period to settle.
coreA design synchronizes reset once to clk_a and uses that reset in both the clk_a and clk_b domains. What is the problem?
The synchronized release is aligned only to clk_a edges. Since clk_b is unrelated, that release can land anywhere relative to a clk_b edge, including inside its recovery and removal window. To clk_b, the reset is effectively asynchronous again. Each clock domain needs its own reset synchronizer, all fed from the same global reset.
coreA sender and a receiver run on the same clock but have separate asynchronous resets. Resetting only the sender corrupts the receiver now and then. Why, and how do you fix it?
Asserting the sender's reset changes its outputs at an arbitrary moment, regardless of the clock, so the receiver can see data and valid changing right at its clock edge. It may capture a mix of old and cleared bits, or go metastable, with no clock crossing involved. This is a reset domain crossing. Fixes are to reset the receiver at the same time or before the sender, or to drive the crossing signals to a safe value, such as valid low, on a clock edge before the reset is asserted.
coreTwo clock domains leave reset at slightly different times. What should a handshake or FIFO between them do during that gap?
It should stay quiet until both sides are out of reset. If one side starts sending while the other is still held in reset, words can be lost or the receiver can see a partial transfer. A common approach is to hold off valid, requests or writes until the local side also knows the other side is ready, for example through a synchronized ready signal. Upstream blocks are then released in a known order, so nobody sends before the receiver can handle it.
advancedA reset synchronizer drives 50,000 flops. Its release is aligned to the clock, so why can the release still fail timing?
The synchronized release still has to travel through a large buffer tree to reach every flop, and it must arrive at each one inside the recovery and removal limits for the next clock edge. With a big fan-out, that tree can be slow, so some flops may see the release in time and others a cycle later. This is checked with recovery and removal timing on the reset network, and fixed by buffering, by splitting the reset into local synchronizers, or by adding pipeline stages on the release path.
advancedIn an async FIFO, only the write side is reset while traffic is running. The reader later outputs some words a second time. Explain.
The write pointer jumps back to zero, which is a jump of many steps, not the single step that Gray crossing assumes. The reader then compares its own pointer with this new write pointer, and the lap bit can make it look as though the writer is a whole lap ahead. The reader believes the FIFO holds words that it has already read, and reads the old entries again. Both sides of the FIFO should be reset together from one global reset, each through its own synchronizer.