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.

Two events sent into a sleeping domainThe lab design. Every value is sampled 1 ns after an edge of the sender's clock clk, edges 2 to 24. t2 is the receiver's synchronized copy of the toggle; got counts the events it has received.
012345678910111213141516171819202122clkrst_a_nrst_b_ntog_at2gotB out of reset
  1. Cycle 1

    Edge 3: the sender's domain is out of reset, so ready_a is 1.

  2. Cycle 2

    Edge 4: event 1. The toggle goes to 1 while the receiver is still in reset.

  3. Cycle 10

    Edge 12: event 2. The toggle goes back to 0, still before the receiver wakes.

  4. 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.

  5. 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.

Two reset synchronizers and a ready flagArrows show signals. The top row is domain A on clk_a, the bottom row domain B on clk_b.
Two reset synchronizers and a ready flagArrows show signals. The top row is domain A on `clk_a`, the bottom row domain B on `clk_b`. component rst_n pin shared by bothdomains clk_a ra_q1, rst_a_n release on clk_a clk_a tog_a flips once per event clk_b rb_q1, rst_b_n release on clk_b clk_b t1, t2, t3 two flops, edgedetect ready up_b to up_q2 B is awake, told to A assert at onceassert at oncerst_a_nrst_b_ntoggleup_b
  1. Assert

    The pin falls and both domains are in reset at once, with or without their clocks.

  2. 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
    1. data release: ra_q1, rst_a_n → tog_a
  3. 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
    1. data event: tog_a → t1, t2, t3
  4. B says it is awake

    The fix: B raises up_b one edge after its release, and the flag crosses into A through two flops. A sends only when ready_a is 1.

    Follow the calls, values and waits
    1. data release: rb_q1, rst_b_n → t1, t2, t3
    2. 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 flagWith the ready flag
13 ns, starts at 150 ns (the lab)3 sent, 1 received3 sent, 3 received
27 ns, starts at 150 ns3 sent, 1 received3 sent, 3 received
13 ns, running from the start3 sent, 3 received3 sent, 3 received
27 ns, running from the start3 sent, 3 received; event 1 was sent during B's reset3 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

Design brief
PortDirectionWidthMeaning
clk_ainput1domain A: the sender
clk_binput1domain B: the receiver
rst_ninput1one pin for both domains, active-low, asynchronous
send_ainput1domain A: each cycle with send_a = 1 is one event
ready_aoutput1domain A: 1 only when A is out of reset and B has said it is; the sender waits for it
event_boutput1domain B: B raises event_b for one cycle per event
Reset
each domain releases rst_n through 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
  1. 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.

  2. 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.

  3. 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.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: e4 A sends event 1 e12 A sends event 2 e20 A sends event 3 e24 B has received 1 3 sent, 1 received

Original 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

Predict the outputThe shipped link, without the ready flag, runs with B's 27 ns clock running from the start. A sends event 1 at edge 4, while B is still in reset; B leaves reset between edges 5 and 6. Events 2 and 3 follow eight edges apart. What does the bench print?
`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 2.6, 29.6, ... ns
  initial begin
    #2.6;
    forever begin clk_b = 1; #13.5; clk_b = 0; #13.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;
    
  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
// event_link: carries events from domain A (clk_a) to domain B (clk_b) with a toggle
// synchronizer. One reset pin feeds both domains; each releases it through its own reset
// synchronizer.
module event_link (
  input  logic clk_a,          // domain A: the sender
  input  logic clk_b,          // domain B: the receiver
  input  logic rst_n,          // the reset pin, shared by both domains
  input  logic send_a,         // domain A: 1 for one cycle per event to send
  output logic ready_a,        // domain A: 1 when an event may be sent
  output logic event_b         // domain B: 1 for one cycle per event received
);
  // Reset synchronizers: assert at once, release two edges of the domain's own clock later.
  logic ra_q1, rst_a_n, rb_q1, rst_b_n;
  always_ff @(posedge clk_a or negedge rst_n)
    if (!rst_n) begin ra_q1 <= 1'b0; rst_a_n <= 1'b0; end
    else        begin ra_q1 <= 1'b1; rst_a_n <= ra_q1; end
  always_ff @(posedge clk_b or negedge rst_n)
    if (!rst_n) begin rb_q1 <= 1'b0; rst_b_n <= 1'b0; end
    else        begin rb_q1 <= 1'b1; rst_b_n <= rb_q1; end

  // A: flip the toggle once per event.
  logic tog_a;
  always_ff @(posedge clk_a or negedge rst_a_n)
    if (!rst_a_n)    tog_a <= 1'b0;
    else if (send_a) tog_a <= ~tog_a;

  // A may send once its own reset is released.
  assign ready_a = rst_a_n;

  // B: two flops, then an edge detector on the synchronized toggle.
  logic t1, t2, t3;
  always_ff @(posedge clk_b or negedge rst_b_n)
    if (!rst_b_n) begin t1 <= 1'b0; t2 <= 1'b0; t3 <= 1'b0; end
    else          begin t1 <= tog_a; t2 <= t1; t3 <= t2; end
  assign event_b = t2 ^ t3;
endmodule
Spot the bugEach option is B's receiving block in the link with the ready flag. The toggle rests at 0 in reset, and the bench, with both clocks running, expects as many events received as sent. Which hookup fails?
ConceptWhy does each clock domain get its own reset synchronizer, rather than one shared by both?
ConceptThe sender must not send until the receiver is out of reset. What can prove that the receiver is awake?
Read the waveformThis is the lab with the ready flag, edges 14 to 24. Which reading is right?
The sender waits for ready_a
012345678910clkrst_b_nready_asend_atog_agot

Values sampled 1 ns after each edge of the sender's clock clk. rst_b_n belongs to B and is shown as A's edges see it.

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.