Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
    • Reset Architecture
    • Metastability and Synchronisation
      • What Metastability Means in Practice
      • The Two-Flop Synchroniser
      • When to Use Three Flops
      • CDC Constraints and Implementation Hygiene
      • Do Not Synchronise Buses Bit by Bit
    • Asynchronous FIFO
    • CDC Protocols
    • Reset Domain Crossing
    • CDC Verification
  • Memory & Buses57
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Metastability and Synchronisation
  1. Topics
  2. RTL Design
  3. Reset & CDC
  4. Metastability and Synchronisation
  5. Do Not Synchronise Buses Bit by Bit

Part 6 · Reset & CDC · Intermediate

Do Not Synchronise Buses Bit by Bit

Bit-by-bit bus synchronisers mix old and new bits; Gray codes, handshakes, and FIFOs avoid it.

On this page

    Why this matters

    Every bit of a bus has its own wire delay, so the destination can catch some bits from the old value and some from the new. It then reads a value that was never on the bus.

    After this lesson you can: explain why per-bit synchronisers corrupt a bus, and choose Gray code, a handshake or a FIFO for the data you need to cross.

    The bits do not stay together

    Each bus bit has its own wire, delay, and synchroniser, and each synchroniser resolves independently. When several bits change at once, the destination can mix old and new bits.

    Binary 0111 → 1000 changes four bits at once. A sample taken mid-change reads a value that was never on the bus.

    How to read it: one slot is one destination clock cycle; each lane b3 to b0 is the output of one bit's synchroniser, and the bottom lane is those four bits read together as one word. The destination clock period is 1 ns and the per-bit wire delays are 0.0, 0.4, 0.8 and 1.2 ns.

    • Bits b1 and b0 fall one edge before b2 falls and b3 rises. The skew is smaller than one clock period, yet it is enough to split the change across two edges.

    • In the slot between them the word is 0100 (4). The source never held 4: it went from 7 straight to 8.

    • A receiver that reads the bus in that slot acts on a value nobody sent. Whether it happens depends on wire delay, not on logic, so a simulation without delays cannot show it.

    How to read it: the top lane is the registered Gray word in the source domain and the bottom lane is the word seen after the per-bit synchronisers; both are shown in hex.

    • Each source step flips exactly one bit, so there is only one bit whose arrival time can differ.

    • The destination moves from 6 to 7 and from 7 to 5 a fixed synchroniser latency after the source, and never shows another value.

    • The same wire skew that produced the bad word 4 for binary has nothing to split here. This holds only while the source value changes one step at a time.

    What works for a multi-bit value

    The safe structure depends on the value type.

    Value type

    Technique

    Counter or pointer that increments by 1

    Gray code (each step flips one bit); used by async FIFO pointers

    Arbitrary multi-bit data

    Handshake: hold data stable, synchronise a request, capture, synchronise the acknowledge

    Stream of data

    Asynchronous FIFO

    Slowly changing configuration

    Bundled-data capture: hold data, transfer a captured/acknowledged load event, then sample under a timing contract

    Independent single bits (status flags with no relation)

    Per-bit synchronisers are fine

    A stable bus alone is insufficient for a load pulse. Guarantee pulse capture and minimum event spacing (or use a request/acknowledge handshake), bound the latest data arrival before the earliest destination capture including setup margin, and hold the bus through capture until the returned acknowledgement. Reset both sides' event history consistently and prevent traffic during restart; otherwise a strobe can be lost or appear as a phantom event. See the canonical bundled-data timing contract and pulse capture and spacing.

    Warning

    The Gray code must come from flops. An earlier version of this test computed Gray code combinationally from the counter (assign gray = cnt ^ (cnt >> 1)). In simulation the combinational output glitched for zero time as bits settled, and the Gray crossing then showed 496 invalid jumps. Register the Gray value in the source domain and cross the register.

    Common mistakes

    Common pitfall

    Gray code works only when the value changes by one step at a time. A counter that jumps (reload, skip) breaks the single-bit rule and the Gray crossing breaks with it. If the value can jump, use a handshake.

    The one-step condition applies to source updates, not consecutive destination observations. A slower destination may coherently see 4 then 6, skipping source value 5; those observed Gray words differ in two bits and that is legal. Gray pointer crossings convey snapshots, not lossless events. Use a handshake or FIFO for every event/data item. Require source-registered Gray and effective max-delay/bus-skew bounds that prevent successive source transitions overlapping at the receiving stage (normally bounded against the minimum source update interval, with implementation margin). Audit exception precedence and routed reports as in implementation hygiene; RTL simulations do not sign off those bounds.

    Interview

    “Why can't you just put a 2FF on each bit of a bus?” Because the bits resolve independently, so the destination can see a mixture of old and new bits. Offer Gray code, a handshake with stable data, or an async FIFO.

    Check yourself A Gray-coded pointer goes 0110 → 0111 → 0101. How many bits change at each step, and is the sequence a legal Gray sequence?

    One bit each time (bit 0, then bit 1). It is a legal Gray sequence of steps (these are the Gray codes of 4, 5 and 6).

    Testbench

    meta_ff.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    `timescale 1ns/1ps
    
    // Sampling uncertainty only: random digital capture at fixed TCQ after a window hit.
    // No analogue state, late-resolution tail, or downstream disagreement is modelled.
    module meta_ff #(parameter real TCQ = 0.3, TSU = 0.15, THD = 0.1) (
      input  logic clk, d,
      output logic q
    );
      realtime t_d = -100.0, t_clk = -100.0;
      int      meta_events = 0;
      initial q = 1'b0;
    
      always @(d) begin
        t_d = $realtime;
        if ($realtime - t_clk < THD) begin                    // changed just after the edge (hold window)
          meta_events++;  q <= #TCQ $urandom_range(0, 1);
        end
      end
      always @(posedge clk) begin
        t_clk = $realtime;
        if ($realtime - t_d < TSU) begin                      // changed just before the edge (setup window)
          meta_events++;  q <= #TCQ $urandom_range(0, 1);
        end else q <= #TCQ d;
      end
    endmodule
    
    
    // A W-bit bus crossing with one two-flop synchroniser per bit (what NOT to do for a counter)
    module bus_sync_bits #(parameter int W = 4) (input logic clk, input logic [W-1:0] d, output logic [W-1:0] q);
      for (genvar i = 0; i < W; i++) begin : g_bit
        logic q1;
        meta_ff f1 (.clk, .d(d[i]), .q(q1));
        meta_ff f2 (.clk, .d(q1),   .q(q[i]));
      end
    endmodule
    

    Crossing

    Anomalous steps (simulation run)

    Why

    Binary counter, per-bit 2FF

    454

    Multi-bit transitions captured partly old, partly new

    Gray-coded counter, per-bit 2FF, decoded after the crossing

    0

    Only one bit changes per step, so the destination sees either the old or the new value

    Here an anomalous step means anything other than 0 or +1 between observations. The 454/0 counts are digital-model results, not physical failure rates; the bounded drain changes the sample total. Skips are legal when a destination undersamples a Gray pointer. Neither a zero anomaly count nor this rate-specific diagnostic proves physical CDC safety.

    A coherent skipped snapshot

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    `timescale 1ns/1ps
    module tb_gray_skip;
      logic src_clk = 0, dst_clk = 0;
      logic [3:0] count = 4, gray = 6;
      logic [3:0] stage1 = 0, stage2 = 0;
      logic [3:0] old_gray, source_delta;
      always #5 src_clk = ~src_clk;
      always #20 dst_clk = ~dst_clk;
      always_ff @(posedge dst_clk) begin
        stage1 <= gray;
        stage2 <= stage1;
      end
      function automatic logic [3:0] decode(input logic [3:0] g);
        logic [3:0] b;
        b[3] = g[3];
        for (int i = 2; i >= 0; i--) b[i] = b[i+1] ^ g[i];
        return b;
      endfunction
      initial begin
        $timeformat(-9, 0, " ns", 0);   // print %t values in ns, not ps
        // Source starts at 4; both subsequent source transitions are unit increments.
        #30; #1;
        old_gray = gray; count = 5; gray = count ^ (count >> 1);
        source_delta = old_gray ^ gray;
        if ($isunknown(source_delta) || !$onehot(source_delta)) $fatal(1, "4->5 source Gray step");
        @(negedge src_clk); #1;
        old_gray = gray; count = 6; gray = count ^ (count >> 1);
        source_delta = old_gray ^ gray;
        if ($isunknown(source_delta) || !$onehot(source_delta)) $fatal(1, "5->6 source Gray step");
        @(posedge dst_clk); #1;
        if (decode(stage2) !== 4'd4) $fatal(1, "expected snapshot 4");
        $display("snapshot %0d at %0t", decode(stage2), $time);
        @(posedge dst_clk); #1;
        if (decode(stage2) !== 4'd6) $fatal(1, "expected coherent skipped snapshot 6");
        $display("snapshot %0d at %0t", decode(stage2), $time);
        $display("PASS: legal source unit steps, destination legitimately skips 5");
        $finish;
      end
      initial begin #200; $fatal(1, "Gray skip watchdog"); end
    endmodule

    Remember

    • Independent bits may each use a synchroniser. Related bits may not.

    • A sample taken mid-change can be neither the old word nor the new one: the bus 0111 to 1000 was read as 4 between 7 and 8.

    • Gray code works for counters because only one bit changes per step. For general data, use a handshake or a FIFO.

    Rule of thumb: cross one bit with a synchroniser; cross a word with a protocol.

    How this was checked

    Finite source checker, run with Icarus Verilog 13.0 (2026-10-16): 454 anomalous binary steps against 0 Gray, then PASS. A binary counter and its registered Gray image cross bit by bit. Here the destination period (7.13 ns) is shorter than each source update interval (10 ns), with equal fixed-TCQ model stages and no added routing delay. The step <= 1 diagnostic is specific to this experiment; it is not a universal Gray crossing property. Window choices model sampling uncertainty only.

    Directed checker, run with Icarus Verilog 13.0 (2026-10-16): prints snapshot 4 at 61 ns, then 6 at 101 ns. Source Gray updates 4 to 5 to 6 between destination captures; a 40 ns destination clock and two-register pipeline observe 4 then 6. The named delta vector checks the one-bit rule at the source only. This ideal digital example verifies the skip interpretation, not skew constraints or metastability reliability.

    Keep going

    • Practice: Debug data corruption on a multi-bit CDC bus transfer

    • Practice: Design an asynchronous FIFO with CDC handling

    • Interview: run a mock interview on this topic

    Related topics

    • Async FIFO

    Previous

    164 / 350

    Next: Asynchronous FIFO

    Up next

    Up nextAsynchronous FIFO

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • What Metastability Means in Practice
    • The Two-Flop Synchroniser
    • When to Use Three Flops
    clk_dstb3b2b1b0bus748
    Simulation output: the source bus steps once from 0111 to 1000, but four per-bit two-flop synchronisers, whose wires differ in delay by 0.4 ns per bit, deliver the new bits at different destination edges, so the destination bus reads 7, then 4, then 8 (values shown in hex)
    clk_dstgray_src675gray_dst675
    Simulation output: the same four per-bit synchronisers and wire delays carrying Gray words 0110, 0111, 0101 (hex 6, 7, 5); each step changes one bit, so the destination word is always the old word or the new word