Reset across clock domains
Give each clock domain its own reset release, see why the two releases land at different times, and make the first event after reset wait until the receiver is awake.
- Lesson
- R6.5
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
After every reset, two of the first three events sent to a slow domain vanished. The sending domain left reset at edge 3 of its clock; the receiving domain, whose clock was still starting, left reset near edge 16. In between, the sender flipped its toggle twice, and the receiver woke to a toggle back at 0.
clk, edges 2 to 24. t2 is the receiver's synchronized copy of the toggle; got counts the events it has received.- Cycle 1
Edge 3: the sender's domain is out of reset, so
ready_ais 1. - Cycle 2
Edge 4: event 1. The toggle goes to 1 while the receiver is still in reset.
- Cycle 10
Edge 12: event 2. The toggle goes back to 0, still before the receiver wakes.
- Cycle 14
Edge 16: the receiver is out of reset, and its flops read a toggle of 0, the same as their reset value. Events 1 and 2 left no trace.
- Cycle 22
Edge 24: event 3, sent at edge 20, arrives. Three sent, one received.
Measured on the lab bench with a probe line. The receiver's clock starts at 150 ns, like a clock from a PLL that is still locking when the reset pin rises.
One pin, two releases
R2.2 gave one domain a reset synchronizer: the pin asserts reset at once, and the release passes through two flops of the domain's own clock. Two domains need two synchronizers, one per clock, because each release must land just after an edge of the clock it releases.
So the two domains leave reset at different times. The gap depends on both clocks, and on whether the slower one is running yet. Nothing in the sender tells it the receiver is still asleep.
clk_a, the bottom row domain B on clk_b.- Assert
The pin falls and both domains are in reset at once, with or without their clocks.
- A releases first
A's clock is running, so two of its edges release A. In the lab that is edge 3.
Follow the calls, values and waits
- data release: ra_q1, rst_a_n → tog_a
- A sends too soon
Without the flag, A sends as soon as it is out of reset. Its toggle reaches flops that are held at 0.
Follow the calls, values and waits
- data event: tog_a → t1, t2, t3
- B says it is awake
The fix: B raises
up_bone edge after its release, and the flag crosses into A through two flops. A sends only whenready_ais 1.Follow the calls, values and waits
- data release: rb_q1, rst_b_n → t1, t2, t3
- data ready: t1, t2, t3 → up_b to up_q2
Read the diagram as text
- rst_n pin: shared by both domains
- ra_q1, rst_a_n: release on clk_a
- tog_a: flips once per event
- rb_q1, rst_b_n: release on clk_b
- t1, t2, t3: two flops, edge detect
- up_b to up_q2: B is awake, told to A
- rst_n pin → ra_q1, rst_a_n: assert at once
- rst_n pin → rb_q1, rst_b_n: assert at once
- ra_q1, rst_a_n → tog_a: rst_a_n
- rb_q1, rst_b_n → t1, t2, t3: rst_b_n
- tog_a → t1, t2, t3: toggle
- t1, t2, t3 → up_b to up_q2: up_b
The ready flag is a register in B that crosses like any other bit: from a flop, through two flops, into A.
What survives a sleeping receiver
A toggle is a level. It stays where the last event left it, so one event sent into a sleeping domain is not lost: the receiver wakes, reads the new level and sees an edge. Two events in the gap flip the toggle back, and the receiver sees nothing at all.
| Receiver clock (measured) | Without the ready flag | With the ready flag |
|---|---|---|
| 13 ns, starts at 150 ns (the lab) | 3 sent, 1 received | 3 sent, 3 received |
| 27 ns, starts at 150 ns | 3 sent, 1 received | 3 sent, 3 received |
| 13 ns, running from the start | 3 sent, 3 received | 3 sent, 3 received |
| 27 ns, running from the start | 3 sent, 3 received; event 1 was sent during B's reset | 3 sent, 3 received |
The lab bench with the receiver's clock changed. A pulse that lasts one cycle, or a level held for a fixed count, is not a level: it can expire before the receiver wakes.
Two answers work. The brief can promise that the receiver leaves reset first. Or, for any order of release, the sender waits for proof that the receiver is awake. That proof is a ready flag the receiver raises after its release, or the first ack of a handshake.
Waiting for ready
A trusts its own reset
// A may send once its own reset is released.
assign ready_a = rst_a_n;
A sends while B may still be in reset. Two events in the gap cancel.
A waits for B
// A may send once its own reset is released and B has said it is out of reset.
logic up_b, up_q1, up_q2;
always_ff @(posedge clk_b or negedge rst_b_n) // B: 1 from the first edge after release
if (!rst_b_n) up_b <= 1'b0;
else up_b <= 1'b1;
always_ff @(posedge clk_a or negedge rst_a_n) // A: B's flag through two flops
if (!rst_a_n) begin up_q1 <= 1'b0; up_q2 <= 1'b0; end
else begin up_q1 <= up_b; up_q2 <= up_q1; end
assign ready_a = rst_a_n && up_q2;
B's flag crosses through two flops, so A sends only after B is awake.
The flag is up_b, a flop in B, rather than rst_b_n itself. A first version that sent rst_b_n across drew the Lint warning SYNCASYNCNET: a signal used as an asynchronous reset and as data. A flop of the receiving domain is an ordinary bit to synchronize.
Reset values that match the idle level
A synchronizer is reset too, and its reset value must equal the level its input rests at. The toggle rests at 0 in reset, so t1, t2 and t3 reset to 0. Reset them to 1 and the release itself looks like an edge: the receiver reports an event nobody sent.
The asynchronous FIFO of R6.4 needs the same care. Each side releases through its own synchronizer, and its pointers are levels, so a word written before the read side wakes waits in memory. But a side still in reset must not invite traffic: full and empty stay 1 until both sides are out of reset. R6.6 builds that into the FIFO.
The lab: wait for the receiver
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk_a | input | 1 | domain A: the sender |
| clk_b | input | 1 | domain B: the receiver |
| rst_n | input | 1 | one pin for both domains, active-low, asynchronous |
| send_a | input | 1 | domain A: each cycle with send_a = 1 is one event |
| ready_a | output | 1 | domain A: 1 only when A is out of reset and B has said it is; the sender waits for it |
| event_b | output | 1 | domain B: B raises event_b for one cycle per event |
- Reset
- each domain releases
rst_nthrough its own two-flop synchronizer; the release order is not known - Throughput
- events at least eight A cycles apart, so the toggle stays legal for B
- Corner cases
- B's clock starts late; an event sent during B's reset; the release of a reset synchronizer
The lab ships with ready_a tied to A's own reset. Run it: three events sent, one received. Replace the assign with the right-hand block of the compare and run again. A now sends at edges 20, 28 and 36, and B receives all three. The house reset synchronizer reset_sync.sv sits beside the design for reference.
Asked in interviews
How do you reset a design with two clock domains?
One reset source, asserted asynchronously everywhere, and one reset synchronizer per domain, so each release lands just after an edge of that domain's clock. Then decide what crosses before both sides are out of reset.
What is wrong with releasing one reset into both domains?
A raw release is asynchronous to both clocks, so flops can leave reset in different cycles or go metastable. Synchronized releases are clean but land at different times, so the first transaction must wait for the slower side.
How do you know the other side is out of reset?
It tells you: a flag it raises after its own release, synchronized into your domain, or the first acknowledge of a handshake. A fixed delay is a guess that breaks when a clock starts late.
Try it
Events 1 and 2 were taken while B was in reset; the toggle was back at 0 when B woke. Event 3 arrives four A edges after it was sent.
With the ready flag the bench prints e20 A sends event 1, e24 B has received 1, e28 A sends event 2, e32 B has received 2, e36 A sends event 3, e40 B has received 3 and 3 sent, 3 received. Synthesize: 10 cells and 8 flops become 14 cells and 11 flops, with 1 level of logic in both.
Synthesize the design to measure its cells and inspect the mapped gates.
e4 A sends event 1 e12 A sends event 2 e20 A sends event 3 e24 B has received 1 3 sent, 1 receivedOriginal design · 10 cells · 8 flip-flops · 0 latches · 1 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: reset_sync.sv
// reset_sync.sv: asynchronous assertion, synchronous release (read-only).
// rst_n_in may fall at any time and the output falls with it; when rst_n_in rises, the output rises
// two rising edges later, so every flop in the domain leaves reset in the same cycle.
module reset_sync (input logic clk, input logic rst_n_in, output logic rst_n_out);
logic q1;
always_ff @(posedge clk or negedge rst_n_in)
if (!rst_n_in) begin q1 <= 1'b0; rst_n_out <= 1'b0; end
else begin q1 <= 1'b1; rst_n_out <= q1; end
endmodule
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // the sampling clock: rising edges at 5, 15, 25, ... ns
logic clk_b = 0; // the second clock: rising edges at 150, 163, ... ns
initial begin
#150;
forever begin clk_b = 1; #6.5; clk_b = 0; #6.5; end
end
logic rst_n = 1; // falls at 1 ns: a real edge, so asynchronous resets act before any clock
int edge_n = -1; // the number of the last rising edge of clk
always @(posedge clk) edge_n = edge_n + 1;
logic send_a = 0, ready_a, event_b;
int sent = 0, got = 0, shown = 0;
always @(posedge clk_b) if (event_b) got++; // B counts the events it receives
task automatic report();
if (send_a) $display("e%0d A sends event %0d", edge_n, sent + 1);
if (got != shown) begin shown = got; $display("e%0d B has received %0d", edge_n, got); end
endtask
event_link dut (.clk_a(clk), .clk_b(clk_b), .rst_n(rst_n), .send_a(send_a), .ready_a(ready_a), .event_b(event_b));
// Advance to the next rising edge of clk, wait for the design to settle, then print.
task automatic tick();
@(posedge clk); #1;
report();
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, clk edges 0 and 1 in reset, released 1 ns after edge 1
repeat (3) begin
while (!ready_a) tick(); // wait until A may send
send_a = 1; tick(); send_a = 0; sent++; // one event, taken at this edge
repeat (7) tick();
end
repeat (20) tick();
$display("%0d sent, %0d received", sent, got);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- Each clock domain releases the shared reset through its own synchronizer, so the domains leave reset at different, unknown times.
- A toggle or a handshake request is a level and waits for a sleeping receiver; two toggles in the gap cancel, and a timed pulse can expire.
- The first transaction waits for proof the receiver is awake: a ready flag it raises after release, synchronized back, or the first acknowledge.
- Reset a synchronizer to the idle level of what it carries, or the release looks like an event.