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

  • How to Use This Course
  • Reading Paths
  • Concept Map
  • How to Read the Diagrams
  • Part 0: What Is a Protocol?10
  • Part 1: Timing and Clocking11
    • Common-Clock Interfaces
    • Asynchronous Serial and Oversampling
    • Source-Synchronous Clocks and Strobes
    • Embedded Clocks and Clock Recovery
    • Skew and Deskew
    • Clock Relationships
    • Elastic Buffers and Clock Compensation
    • Self-Timed Handshakes
    • Crossing at the Interface
    • Protocols Compared: Timing and Clocking
    • Drills: Timing and Clocking
  • Part 2: Encoding and Framing12
  • Part 3: Handshakes13
  • Part 4: Flow Control17
  • Part 5: Transactions19
  • Part 6: Concurrency and Performance17
  • Part 7: Ordering13
  • Part 8: Sharing and Arbitration12
  • Part 9: Deadlock and Liveness13
  • Part 10: Reliability and Recovery20
  • Part 11: Bring-Up and Link Management10
  • Part 12: Power8
  • Part 13: Shared State and Coherence9
  • Part 14: Security and Isolation7
  • Part 15: Specifying and Verifying11
  • Part 16: Protocols Dissected17
  • Part 17: Capstones5
  • Part 18: Interview Preparation6
Loading lesson

‹Part 1: Timing and Clocking
  1. Topics
  2. First Principles of Hardware Protocols
  3. Part 1: Timing and Clocking
  4. Asynchronous Serial and Oversampling

Part 1 · Timing and Clocking · Foundation (F)

Asynchronous Serial and Oversampling

No clock wire at all: a start bit resynchronises the receiver, which samples near the middle of each bit with its own faster clock. How much clock mismatch that tolerates.

On this page

    Note

    Level: Foundation (F) · Answers: When is the data valid?

    Before this: Common-Clock Interfaces

    A debug console runs at 115,200 bits per second with no problem. Someone raises it to 921,600 to speed up log dumps. The terminal now shows garbage, although both ends were configured for exactly the same rate. There is no clock wire between them to blame: each side runs from its own oscillator.

    With no clock wire, how does the receiver know when each bit is valid, and how far can the two clocks disagree before it fails?

    The obvious answer, and why it breaks

    The obvious design gives each side a clock at the bit rate: the transmitter changes the line once per bit, and the receiver samples once per bit with its own clock. But the receiver's clock edges have an unknown phase relative to the incoming bits. If its edge lands near a bit boundary, it samples while the line is changing, and every bit after that is just as badly placed. Worse, the two clocks are never exactly equal, so even a lucky start drifts towards a boundary. Without a shared clock, the receiver needs a way to find the bits' phase and to keep finding it.

    The idea: resynchronise on every frame, sample in the middle

    Asynchronous serial framing solves both problems with three ideas:

    • Short frames with a known start. The line idles high. Each character starts with a start bit (low), so every frame begins with a high-to-low edge the receiver can see. The receiver re-locks its timing on that edge, so drift only has to stay small for one frame, about ten bits, not forever.

    • Oversampling. The receiver's clock runs several times faster than the bit rate, commonly 16 times. That lets it find the start edge to within a fraction of a bit and then count to the middle of each bit.

    • Sample in the middle. It waits half a bit after the start edge (checking the start bit is still low, which filters glitches), then samples once per bit time, so every sample lands near the centre of its bit, as far as possible from both edges. A stop bit (high) at the end returns the line to idle and guarantees a falling edge for the next frame.

    A frame and where the receiver samples. Every sample is timed from the start edge, so clock mismatch accumulates for at most one frame.

    Build it: a UART receiver with oversampling

    The receiver below uses 8 samples per bit (16 is also common; 8 keeps the waveform readable). The line is asynchronous to the receiver's clock, so it first goes through a two-flop synchroniser (the CDC course explains why). The receiver then counts half a bit after the falling edge, confirms the start bit, then samples each data bit one bit time apart, least significant bit first, and checks the stop bit. The testbench sends 0x55 at the nominal rate, then 0xA3 with the transmitter running 3% slow.

    uart_rx.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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    `timescale 1ns/100ps
    // UART receiver with 8x oversampling (16x is also common; 8x keeps the waveform short).
    // The line idles high and is first synchronised to clk. A falling edge starts a frame; the receiver waits half a bit and
    // checks the start bit is still low, then samples each data bit one bit time apart, near
    // its middle, least significant bit first, and finally checks the stop bit is high.
    // The testbench sends 0x55 at the nominal rate, then 0xA3 with the transmitter 3% slow.
    module uart_rx #(parameter int OS = 8) (
      input  logic       clk,      // oversampling clock: OS ticks per bit
      input  logic       rst_n,
      input  logic       rxd,
      output logic [7:0] data,
      output logic       valid,    // one-cycle pulse: data holds a received byte
      output logic       ferr,     // one-cycle pulse: stop bit was low (framing error)
      output logic       sample    // high in the cycle whose ending edge samples the line
    );
      typedef enum logic [1:0] {IDLE, START, DATA, STOP} state_t;
      state_t     state;
      logic [3:0] cnt;             // oversampling ticks within the current bit
      logic [2:0] nbit;            // data bit index
      logic [7:0] shreg;
      logic       rx_meta, rx;     // rxd is asynchronous to clk: two-flop synchroniser first
    
      always_ff @(posedge clk or negedge rst_n)
        if (!rst_n) begin rx_meta <= 1'b1; rx <= 1'b1; end
        else        begin rx_meta <= rxd;  rx <= rx_meta; end
    
      assign sample = (state == START && cnt == OS/2 - 1) || ((state == DATA || state == STOP) && cnt == OS - 1);
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          state <= IDLE; cnt <= 0; nbit <= 0; shreg <= 0; data <= 0;
          valid <= 0; ferr <= 0;
        end else begin
          valid <= 0; ferr <= 0;
          case (state)
            IDLE:  if (!rx) begin state <= START; cnt <= 0; end
            START: if (cnt == OS/2 - 1) begin            // middle of the start bit
                     if (!rx) begin state <= DATA; cnt <= 0; nbit <= 0; end
                     else state <= IDLE;                 // a glitch, not a start bit
                   end else cnt <= cnt + 1;
            DATA:  if (cnt == OS - 1) begin              // one bit time later: middle of the next bit
                     cnt <= 0;
                     shreg <= {rx, shreg[7:1]};         // least significant bit arrives first
                     if (nbit == 7) state <= STOP; else nbit <= nbit + 1;
                   end else cnt <= cnt + 1;
            STOP:  if (cnt == OS - 1) begin
                     state <= IDLE; cnt <= 0;
                     if (rx) begin data <= shreg; valid <= 1; end else ferr <= 1;
                   end else cnt <= cnt + 1;
          endcase
        end
      end
    endmodule
    
    module tb;
      localparam int OS = 8;
      logic clk = 0;
      always #5 clk = ~clk;                // 10 ns per tick, so one bit = 80 ns
      logic rst_n = 0, rxd = 1;
      logic [7:0] data; logic valid, ferr, sample;
      uart_rx #(.OS(OS)) dut (.*);
      wire [1:0] state = dut.state;        // copies of internal state, for the waveform
      wire [3:0] cnt   = dut.cnt;
      wire [2:0] nbit  = dut.nbit;
    
      task automatic send(input logic [7:0] b, input real bit_ns);
        rxd = 0; #(bit_ns);                                  // start bit
        for (int i = 0; i < 8; i++) begin rxd = b[i]; #(bit_ns); end
        rxd = 1; #(bit_ns);                                  // stop bit
      endtask
    
      logic [7:0] got [0:1]; int n = 0, errors = 0;
      always @(posedge clk) begin
        if (valid) begin got[n] = data; n++; end
        if (ferr) errors++;
      end
    
      initial begin
        $dumpfile("wave.vcd"); $dumpvars(0, tb);
        #23 rst_n = 1;
        #20 send(8'h55, 80.0);             // nominal rate
        #160 send(8'hA3, 82.4);            // transmitter 3% slow
        #200;
        if (n == 2 && got[0] == 8'h55 && got[1] == 8'hA3 && errors == 0)
          $display("PASS: received 55 at the nominal rate and a3 with the transmitter 3%% slow");
        else
          $display("FAIL: %0d bytes, errors %0d", n, errors);
        $finish;
      end
    endmodule

    Watch it run

    How to read it, slot by slot:

    • Slot 0: the line is idle (rxd = 1) and the receiver is in IDLE.

    • Slot 1: rxd has fallen: the start bit. The receiver does not see it yet: the line first passes through a two-flop synchroniser, which costs two ticks.

    • Slot 3: the synchronised line is now low, so the receiver moves to START and counts ticks (cnt).

    • Slot 6: sample is high: at the edge ending this slot, half a bit after the receiver saw the edge, it checks the start bit is still low. It is, so it moves to DATA in slot 7.

    • Slots 9–16: rxd is high: data bit 0 of 0x55 is 1. The receiver samples it at the edge ending slot 14. Allowing for the two-tick synchroniser, that is the value rxd had around slot 12, near the middle of the bit.

    • Slots 22 and 30: bits 1 (0) and 2 (1) are sampled exactly 8 ticks apart, and nbit counts them. The synchroniser delays every sample by the same two ticks, so it shifts the sampling points without spending any of the timing margin.

    The second frame, sent 3% slow, is received correctly too: the testbench's PASS line confirms both bytes. That margin is the next calculation.

    Worked numbers: how much clock mismatch a frame tolerates

    Every sample is timed from the start edge. If the two clocks differ by a fraction e, the receiver's idea of "one bit time" is wrong by e bits per bit, and after n bits its sample has drifted n × e bits from the true centre. The last sample, the stop bit, is taken 9.5 bit times after the start edge. It stays inside its bit as long as the drift is less than half a bit, minus the uncertainty in finding the start edge, which is up to one oversampling tick:

    Diagram
    drift at the last sample  = 9.5 x e   must stay below   0.5 - 1/OS   (bit times)
    
    16x oversampling:  e < (0.5 - 0.0625) / 9.5 = 0.046  -> about 4.6% total mismatch
     8x oversampling:  e < (0.5 - 0.125)  / 9.5 = 0.039  -> about 3.9% total mismatch
    
    "Total" is shared between the two ends: each side's clock may be off by about half of it.
    Real lines lose more to slow edges and noise, so practical budgets are tighter.

    Now the opening failure, with illustrative numbers. A receiver that derives its 16x oversampling clock from 50 MHz by an integer divider gets:

    Diagram
    115,200 bit/s: divider = 50,000,000 / (16 x 115,200) = 27.13 -> 27
                   actual rate = 50,000,000 / (16 x 27) = 115,741  -> +0.47%   fine
    921,600 bit/s: divider = 50,000,000 / (16 x 921,600) = 3.39  -> 3
                   actual rate = 50,000,000 / (16 x 3)  = 1,041,667 -> +13.0%  far beyond 4.6%

    Both sides were "set to 921,600", but one of them could not produce it. The protocol's tolerance is a budget for the clock sources, and divider rounding spends it.

    The design space

    Choice

    What it costs

    When it wins

    Higher oversampling (16x rather than 8x)

    A faster receiver clock

    More tolerance and better glitch rejection

    Majority vote of three samples around the middle

    A little logic

    Noisy lines

    Longer frames (more data bits per start bit)

    Less tolerance: drift accumulates over more bits

    Never, if clocks are loose; short frames are the point

    A clock wire instead (synchronous serial)

    One more wire

    Higher rates, or when no accurate local clocks exist

    How real protocols chose

    Protocol

    How the receiver finds bit timing

    UART

    Idle high, start bit low, 5 to 9 data bits least significant first, optional parity, stop bit high. Receivers commonly oversample at 16 times the bit rate and sample near the middle of each bit.

    CAN

    No clock wire either. Bit stuffing guarantees an edge at least every six bits, which keeps receivers aligned through long frames (lesson 2.4).

    Ethernet

    Each frame starts with a 7-octet preamble of alternating ones and zeros and a start frame delimiter, giving the receiver a burst of edges to lock onto before the data.

    All three answer the same question the same way: give the receiver edges at known places, and keep the distance between them short compared with what the clocks' mismatch allows.

    What breaks

    Beyond rate mismatch, the classic UART bug is sampling at the wrong place (illustrative). A receiver samples each bit at its start instead of its middle, counting a full bit time after the falling edge for the first sample. On the bench, with clean edges and matched clocks, it works. In the field, with a slightly slow transmitter and a long cable that rounds the edges, the samples land on the transitions and bytes come out shifted by a bit.

    • Symptom: occasional wrong characters, worse with long cables or some partner devices.

    • Evidence: on a waveform, the sample strobe sits next to the bit edges rather than between them.

    • Fix: wait half a bit after the start edge before the first sample, then one bit per sample. The margin from both edges is what the whole scheme relies on.

    Why this matters

    Senior lens. Asynchronous framing trades a clock wire for two promises: both clock sources are accurate to a few percent, and frames are short. When you integrate a UART-like link, put the rate error of both ends, including divider rounding, in the interface specification, and check it at every supported rate rather than only the default. The same reasoning, "how long can the receiver coast between timing references?", returns at gigabit rates in lesson 1.4, where the coasting distance is set by parts per million instead of percent.

    Interview

    Asked: "How much can the baud rates of a UART transmitter and receiver differ?"

    Weak answer: They have to be exactly the same.

    Strong answer: Each frame re-synchronises on the start edge, and the last sample is 9.5 bits later, so the accumulated drift must stay under about half a bit minus the edge-detection uncertainty. With 16x oversampling that's about (0.5 − 1/16) / 9.5, roughly 4.6% total, shared between both ends, and less in practice because of edge rate and noise. Integer clock dividers can spend that budget at high rates.

    Follow-ups: (1) Why does the receiver wait half a bit before the first sample? (2) Why not send 64 data bits per start bit?

    Check yourself Allowing for the synchroniser, the receiver samples bit 0 about four ticks after the bit began. Why not right at the start of the bit?

    Right after the bit edge, a slightly fast or slow transmitter, or a slow edge, would put the sample on the transition. Sampling in the middle leaves half a bit of margin on both sides.

    Check yourself A link uses 8x oversampling and 12-bit frames (start, 10 data, stop). Roughly what total mismatch does it tolerate?

    The last sample is 11.5 bits after the start edge: e < (0.5 − 0.125) / 11.5, about 3.3% total.

    Check yourself Why must every frame end with a stop bit at the idle level?

    So the line returns high before the next start bit. Without it, the next frame's start bit would not create a falling edge, and the receiver would have nothing to re-lock on.

    Key takeaways

    • Asynchronous framing replaces a clock wire with a start edge per frame and accurate local clocks on both sides.

    • The receiver oversamples, waits half a bit to confirm the start bit, and samples near the middle of every bit.

    • A frame tolerates roughly (0.5 − 1/OS) / 9.5 total rate mismatch for ten-bit frames; divider rounding spends that budget.

    Keep going

    Next: Lesson 1.3, Source-Synchronous Clocks and Strobes. Send the clock along with the data, so it suffers the same delay. How DDR memory captures data on both edges of a strobe.

    Related topics

    • Part 1 overview: Timing and Clocking

    • RTL course: CDC Protocols

    Previous

    16 / 234

    Next: Source-Synchronous Clocks and Strobes

    Up next

    Up nextSource-Synchronous Clocks and Strobes

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Embedded Clocks and Clock Recovery
    • Skew and Deskew
    • Clock Relationships
    clkrxdstate012cnt01230123456701234567012345670samplenbit0123
    Simulation output: the start of the frame carrying 0x55. One slot is one oversampling tick (8 per bit). State 0 is IDLE, 1 is START, 2 is DATA.