How the Receiver Knows When to Sample

Read one byte on four timing schemes, measure why a clock sent from the far end limits a read, and why an embedded clock needs edges.

Lesson
P5.2
Time
30 min
Runs on
Your browser
Check
4 questions

A boot flash read cleanly until the clock was doubled and the flash moved to a daughter card. The controller sampled the returning data on its own clock edge, and the extra cable now delayed the clock out and the data back. The data still arrived, just after the edge that was waiting for it.

One Byte, Four Ways to Find the BitsThe same byte, 0xA5, on four links, all at 16 ns per bit. Dots are where each receiver sampled in the run.
start or sync0xA5stop 012345678910111213141516171819202122232425262728293031323334353637383940414243CLKDATATXSTBDQLINEDATA sampled at 10.0625DATA sampled at 14.0625DATA sampled at 18.0625DATA sampled at 22.0625DATA sampled at 26.0625DATA sampled at 30.0625DATA sampled at 34.0625DATA sampled at 38.0625TX sampled at 9.875TX sampled at 13.875TX sampled at 17.875TX sampled at 21.875TX sampled at 25.875TX sampled at 29.875TX sampled at 33.875TX sampled at 37.875TX sampled at 41.875DQ sampled at 10.0625DQ sampled at 14.0625DQ sampled at 18.0625DQ sampled at 22.0625DQ sampled at 26.0625DQ sampled at 30.0625DQ sampled at 34.0625DQ sampled at 38.0625LINE sampled at 6.125LINE sampled at 10.125LINE sampled at 14.125LINE sampled at 18.125LINE sampled at 22.125LINE sampled at 26.125LINE sampled at 30.125LINE sampled at 34.125LINE sampled at 38.125LINE sampled at 42.125
Read phases and sample points (quarter bit)
  • start or sync: columns 4 through 7.
  • 0xA5: columns 8 through 39.
  • stop: columns 40 through 43.
  • DATA sampled at 10.063, 14.063, 18.063, 22.063, 26.063, 30.063, 34.063, 38.063.
  • TX sampled at 9.875, 13.875, 17.875, 21.875, 25.875, 29.875, 33.875, 37.875, 41.875.
  • DQ sampled at 10.063, 14.063, 18.063, 22.063, 26.063, 30.063, 34.063, 38.063.
  • LINE sampled at 6.125, 10.125, 14.125, 18.125, 22.125, 26.125, 30.125, 34.125, 38.125, 42.125.
  1. Cycle 4

    TX and LINE fall. On the async link it is a start bit; on the embedded-clock link a sync bit. Nothing else warns those two receivers that a byte is coming.

  2. Cycle 10

    Bit 7. CLK rises in the middle of DATA and STB toggles in the middle of DQ: the sender says when. The TX receiver counts its own 1 ns clock from the start edge; the LINE receiver counts from the last edge it saw.

  3. Cycle 26

    Bits 4 and 3 are both 0. DATA and DQ still get an edge of CLK or STB for each bit; LINE has no edge between them, so its receiver keeps counting.

  4. Cycle 40

    The UART stop bit. The async receiver checks it is 1, ready for the next start edge. The other three links end with the last data bit.

Measured: one run, sampled every 4 ns. 0xA5 reads the same with either bit order, so all four lanes carry the same pattern.

Four Answers to One Question

Who Tells the Receiver WhenEach row is one link. Arrows are wires, from the side that drives them.
Who Tells the Receiver WhenEach row is one link. Arrows are wires, from the side that drives them. component Sender drives CLK and DATA component Receiver samples on CLK component Sender drives TX only component Receiver own clock, 16 per bit component Sender drives DQ and STB component Receiver samples on STB edges component Sender drives LINE component Receiver re-times on each edge shared clockoversampled asyncsource-synchronousstrobeembedded clock
  1. A clock wire

    One side drives a clock and both sides use its edges. SPI and I2C work this way.

  2. No clock wire

    The receiver runs its own faster clock, finds the start edge and counts to the middle of each bit. Both ends must agree on the rate. UART works this way.

  3. A strobe from the sender

    Whoever sends the data also sends the timing beside it, so both see the same delay.

  4. Timing in the data

    The receiver re-times its own clock on every edge of the data. It needs edges to keep coming.

Read the diagram as text
  • Sender: drives CLK and DATA
  • Receiver: samples on CLK
  • Sender: drives TX only
  • Receiver: own clock, 16 per bit
  • Sender: drives DQ and STB
  • Receiver: samples on STB edges
  • Sender: drives LINE
  • Receiver: re-times on each edge
  • Sender → Receiver: shared clock
  • Sender → Receiver: oversampled async
  • Sender → Receiver: source-synchronous strobe
  • Sender → Receiver: embedded clock

Lesson P5.1 showed why one lane is fast. Each row is a different way to find the bits on that lane.

The Same Byte Four Ways

The opening figure is one run of the example: four transmitters send 0xA5 at once, each to its own receiver. Every receiver read the byte, and the two that sample on a sender edge counted no setup or hold misses.

SchemeWiresThe receiver needsMeasured
Shared clockCLK, DATAthe clock edge to arrive with the datashared clock: a5 from 8 rising edges of CLK, 0 timing misses
Oversampled asyncTXits own clock at 16 times the rate, the same rate at both endsasync 16x: a5 from the receiver, framing error 0
StrobeSTB, DQto use both strobe edgesstrobe: a5 from 8 edges of STB, 0 timing misses
Embedded clockLINEan edge often enough to re-timeembedded clock: a5 after the sync bit, re-timed at 8 edges

Measured, from the example. The async receiver is the UART library's 16-tick receiver; lesson P6.3 opens it up.

A Clock Wire Has a Round Trip

Now the data comes back. The controller owns the clock, and the peripheral answers over a cable. With a shared clock, the clock travels out and the data travels back before the controller samples on its own edge. With a strobe, the peripheral sends a strobe beside the data, so both travel the same way once.

A read at 16 ns per bitLargest flight each way that still reads 0xA5
Controller samples on its own clock3.25 ns
Controller samples on the peripheral's strobeevery flight swept, up to 8.00 ns
Strobe arriving late against its data, at 5 ns of flightup to 7.50 ns late

Measured: flight swept in 0.25 ns steps. The peripheral changes its data 1 ns after the falling clock edge it sees; the controller needs 0.5 ns of setup.

The clocked read pays the flight twice inside half a bit, so it breaks at 3.25 ns. The strobe read only cares about skew between the strobe and its own data, which a designer can match. Lesson P7.3 turns the clocked read into SPI's half-cycle budget.

No Clock Wire: Edges Must Keep Coming

The embedded-clock receiver counts its own clock from the last edge. If the transmitter runs slow, each bit without an edge adds error. The bench sends a run of equal bits, longer each time, and finds the first run the receiver miscounts.

Transmitter slow byLongest run read rightFirst run misread
1 %54 bits55 bits read as 56
2 %25 bits26 bits read as 27
5 %11 bits12 bits read as 13

Measured. A slow transmitter makes the receiver sample one bit twice, so it reads an extra bit.

A line code fixes this by promising edges. The simplest one stuffs an opposite bit after five equal bits, and the receiver drops it. At 5 % slow, 64 zero bits read as 67 without it and 64 with it, at a cost of 12 stuffed bits.

UART lives with no line code because it re-times on every start bit and a frame is only ten bits long. Lesson P6.4 measures that budget.

Multi-gigabit serial links take the embedded clock as far as it goes. They send no clock wire at all; a clock-recovery loop in the receiver tracks the data edges. Their line codes, 8b/10b and its successors among them, map data to symbols that limit runs of equal bits. Lesson P5.10 shows why such links also go differential.

SchemeWhere it shows upLessons
Shared clockSPI, QSPI and I2C: the controller drives the clockP7.1, P7.3, P8.1
Shared clock, heldAn I2C target holds the clock low until it is readyP8.5
Oversampled asyncUART: 16 ticks per bit and a rate budgetP6.3, P6.4
Clock and edgesWhy a sampling edge needs setup and holdP1.2

The I2C and I3C clocks also come from the controller, so their reads carry the same round trip.

Try It

One line per scheme. The shared-clock and strobe receivers shift DATA or DQ in on their sender's edges and count any sample taken too close to a data change. The async line is the library UART receiver; the embedded line counts the edges that re-timed its receiver.

Try send_all(8'h01);: the sync bit and seven zeros make a run of eight, and the embedded receiver is re-timed at only two edges. Then put etx.error = 0.08; in front: the embedded line reads 00 while the other three still read 01. 0xA5 at the same error still reads right.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: shared clock: a5 from 8 rising edges of CLK, 0 timing misses async 16x: a5 from the receiver, framing error 0 strobe: a5 from 8 edges of STB, 0 timing misses embedded clock: a5 after the sync bit, re-timed at 8 edges
Read included file: uart_tx.sv
`timescale 1ns/1ps
module uart_tx(output reg tx=1);
  real error=0.0; int bits=8, parity=0; real stop_bits=1.0;
  bit lsb_first=1, parity_includes_start=0, parity_includes_stop=0, parity_wrong_sense=0, short_stop=0, no_idle_between=0;
  task automatic bit_time(input bit v); tx=v; #(16.0*(1.0+error)); endtask
  task automatic send_byte(input byte unsigned value);
    bit p; p=(parity==2);
    bit_time(0);
    for(int b=0;b<bits;b++) begin bit_time(value[lsb_first?b:bits-1-b]); p=p^value[b]; end
    if(parity!=0) bit_time(p ^ parity_includes_stop ^ parity_wrong_sense);
    tx=1; #(16.0*(1.0+error)*(short_stop?0.25:stop_bits));
    // no_idle_between is a legal back-to-back frame control, not a fault.
    if(!no_idle_between) #(16.0*(1.0+error));
  endtask
  task automatic send_break(); tx=0; #(200*16); tx=1; #32; endtask
  task automatic set_error(input real e); error=e; endtask
endmodule
Read included file: uart_rx.sv
`timescale 1ns/1ps
module uart_rx(input wire clk, rst_n, rx, take,
  output reg [7:0] data=0, output reg valid=0, frame_err=0, parity_err=0, overrun=0, break_seen=0, frame_done=0);
  int bits=8, parity=0; real stop_bits=1.0;
  bit trace=0;
  bit parity_includes_start=0; // XOR of the zero-valued START is an equivalent control.
  bit sample_at_edge=0, qualify_1_tick=0, no_vote=0, no_resync=0, rearm_on_low=0, stop_as_data=0, no_overrun_flag=0, parity_includes_stop=0;
  int state=0, low_run=0, left_ticks=0, index=0, votes=0, frames=0, ticks=0, next_data_tick=0;
  bit armed=1, sampled=0, p=0, pe=0, pending_zero_fe=0; reg [7:0] shift=0;
  always @(posedge clk) begin
    frame_done=0;
    if(!rst_n) begin state=0;low_run=0;left_ticks=0;index=0;votes=0;valid=0;data=0;frame_err=0;parity_err=0;overrun=0;break_seen=0;armed=1;frames=0;ticks=0;next_data_tick=0;pending_zero_fe=0; end
    else begin
      ticks++;
      if(take)valid=0;
      low_run=rx?0:low_run+1;
      if(rx)break_seen=0;
      if(low_run>=176&&!rearm_on_low)begin break_seen=1;state=4;pending_zero_fe=0;end
      if(state==4)begin if(rx)begin
        if(pending_zero_fe)begin
          if(valid&&!no_overrun_flag)overrun=1;
          data=0;valid=1;frame_err=1;parity_err=pe;frames++;frame_done=1;pending_zero_fe=0;
        end
        state=0;armed=1;
      end end
      else if(state==0)begin
        if(rx)begin armed=1;end
        else if(armed&&low_run>=(qualify_1_tick?1:8))begin
          state=1;left_ticks=sample_at_edge?8:16;index=0;shift=0;p=(parity==2)^parity_includes_stop;votes=0;pe=0;
          // Fault: later frames keep the first frame's free-running schedule.
          // At nominal back-to-back pacing it can work; a new START must normally
          // reset this timing so baud error cannot accumulate across frames.
          if(no_resync&&frames>0)begin
            while(next_data_tick<=ticks)next_data_tick+=16*(1+bits+(parity!=0))+$rtoi(16*stop_bits);
            left_ticks=next_data_tick-ticks;
          end else next_data_tick=ticks+left_ticks;
          next_data_tick+=16*(1+bits+(parity!=0))+$rtoi(16*stop_bits);
        end
      end else begin
        if(left_ticks<=3)votes=votes+(rx?1:0);
        left_ticks=left_ticks-1;
        if(left_ticks==0)begin
          sampled=no_vote?rx:(votes>=2);votes=0;left_ticks=16;
          if(trace)$display("UART SAMPLE t=%0t state=%0d bit=%0d value=%0d",$time,state,index,sampled);
          if(state==1)begin shift[index]=sampled;p=p^sampled;index=index+1;if(index==bits)state=parity==0?3:2;end
          else if(state==2)begin pe=(sampled!=p);state=3;end
          else begin
            if(shift==0&&!sampled&&!rearm_on_low)pending_zero_fe=1;
            else begin
              if(valid&&!no_overrun_flag)overrun=1;
              data=stop_as_data?{sampled,shift[7:1]}:shift;
              frame_err=!sampled;parity_err=pe;valid=1;frames=frames+1;frame_done=1;
            end
            state=(sampled||rearm_on_low)?0:4;armed=1;
          end
        end
      end
    end
  end
endmodule
Read included file: uart_flow.sv
`timescale 1ns/1ps
module uart_flow(input wire clk,rst_n,push,pop,cts_n,input wire [7:0] din,
 output wire rts_n,output wire send,output wire [7:0] dout,output reg overrun=0);
  reg [7:0] fifo[0:15]; int head=0,count=0,trigger=12;
  bit cts_ignored=0,trigger_off_by_one=0;
  assign rts_n=count>=(trigger+(trigger_off_by_one?1:0));
  assign send=count>0&&(!cts_n||cts_ignored);
  assign dout=count>0?fifo[head]:8'h00;
  always @(posedge clk)begin
    if(!rst_n)begin head=0;count=0;overrun=0;end
    else begin
      if(pop&&send)begin head=(head+1)%16;count=count-1;end
      if(push)begin if(count==16)overrun=1;else begin fifo[(head+count)%16]=din;count=count+1;end end
    end
  end
endmodule
Read included file: uart_checker.sv
`timescale 1ns/1ps
module uart_checker(input wire clk,rx,valid,frame_err,parity_err,input wire [7:0] data,input wire frame_done);
  int broken=0,received=0;
  task automatic expect_byte(input byte unsigned wanted,input bit fe=0,pe=0);
    if(!valid||data!==wanted||frame_err!==fe||parity_err!==pe)begin broken++;$display("UART CHECK got=%02h valid=%b FE=%b PE=%b expected=%02h FE=%b PE=%b",data,valid,frame_err,parity_err,wanted,fe,pe);end
  endtask
  always @(negedge clk)if(frame_done)received++;
endmodule
Read included file: uart_mon.sv
`timescale 1ns/1ps
// Independent wire decoder: completion is reported after the stop sample, never at START.
module uart_mon(input wire rx);
 int bits=8,parity=0; real bit_period=16.0; int frames=0; bit trace=0;
 reg [7:0] value; bit p,pe,fe;realtime began;
 initial forever begin
  @(negedge rx);began=$realtime; #(bit_period/2); if(rx==0)begin
   value=0;p=(parity==2);pe=0;
   for(int i=0;i<bits;i++)begin #(bit_period);value[i]=rx;p=p^rx;end
   if(parity!=0)begin #(bit_period);pe=(rx!=p);end
   #(bit_period);fe=!rx;
   if(value==0&&fe)begin
    while(rx==0&&$realtime-began<11*bit_period)#(bit_period/16);
    if(rx==0)begin wait(rx==1);if(trace)$display("UART -- BREAK");end
    else begin frames++;if(trace)$display("UART 00 FE");end
   end
   else begin frames++;if(trace)$display("UART %02h %s",value,fe?"FE":pe?"PE":"ok");end
  end
 end
endmodule
Read included file: uart_tasks.svh
task automatic send_byte(input byte unsigned b); tx0.send_byte(b); endtask
task automatic send_break(); tx0.send_break(); endtask
task automatic set_error(input real e); tx0.set_error(e); endtask
task automatic consume(); @(negedge clk);take=1;@(negedge clk);take=0;endtask
Read included file: p52_links.sv
`timescale 1ns/1ps
module sampler(input wire d);
  real t_su = 0.5, t_h = 0.5, last_change = -1000.0, last_sample = -1000.0;
  int misses = 0; bit armed = 0;
  always @(d) begin if (armed && $realtime - last_sample < t_h) misses++; last_change = $realtime; end
  task automatic take(output bit v);
    last_sample = $realtime; armed = 1;
    if ($realtime - last_change < t_su) misses++;
    v = d;
  endtask
endmodule

// Embedded clock: no clock wire. The receiver runs its own clock at 16 ticks per bit and samples at tick
// 8, 24, 40... counted from the last edge it saw on the line, so every edge re-times it. With `unstuff`,
// it drops the bit that follows five equal bits (the transmitter's stuffed bit).
module edge_rx(input wire clk, rx);
  reg prev = 1; int count = 0, nbits = 0, edges = 0, run = 0; bit active = 0, unstuff = 0, skip = 0, log = 0;
  reg bits[0:1023]; reg last = 1;
  always @(posedge clk) begin
    if (rx !== prev) begin prev = rx; count = 0; edges++; if (!active) active = 1; end
    else count++;
    if (active && count % 16 == 8) begin
      if (log) $display("SAMPLE embedded %0.2f", $realtime);
      if (skip) skip = 0;
      else begin bits[nbits] = rx; nbits++; end
      run = (rx === last) ? run + 1 : 1; last = rx;
      if (unstuff && run == 5) begin skip = 1; run = 0; end
    end
  end
endmodule

// The transmitter for the embedded-clock line: bits at 16 * (1 + error) ns, optionally stuffing the
// opposite bit after five equal bits on the wire.
module edge_tx(output reg line = 1);
  real error = 0.0; bit stuff = 0; int stuffed = 0, run = 0; reg last = 1;
  task automatic bit_out(input bit v);
    line = v; #(16.0 * (1.0 + error));
    run = (v === last) ? run + 1 : 1; last = v;
    if (stuff && run == 5) begin line = !v; stuffed++; last = !v; run = 1; #(16.0 * (1.0 + error)); end
  endtask
  task automatic start(); run = 0; last = 1; stuffed = 0; endtask
endmodule

Check Yourself

Predict the outputThe read twin at 20 ns per bit. The peripheral changes its data 1 ns after each falling clock edge it sees, and the controller samples on its own rising edge with 0.5 ns of setup. Flight is swept in 0.25 ns steps, the same each way. What does the program print?
`timescale 1ns/1ps
module tb;
  read_link r();
  initial begin
    bit a, b, run; real best;
    r.T = 20.0; r.t_co = 1.0;               // 50 MHz; data out 1 ns after the falling edge; setup 0.5 ns
    run = 1; best = -1;
    for (int k = 0; k <= 40; k++) begin
      r.flight = k * 0.25; r.read(a, b);
      run = run && a;
      if (run) best = r.flight;
    end
    $display("largest flight %0.2f ns", best);
    $finish;
  end
endmodule
Read included file: p52_links.sv
`timescale 1ns/1ps
module sampler(input wire d);
  real t_su = 0.5, t_h = 0.5, last_change = -1000.0, last_sample = -1000.0;
  int misses = 0; bit armed = 0;
  always @(d) begin if (armed && $realtime - last_sample < t_h) misses++; last_change = $realtime; end
  task automatic take(output bit v);
    last_sample = $realtime; armed = 1;
    if ($realtime - last_change < t_su) misses++;
    v = d;
  endtask
endmodule

// Embedded clock: no clock wire. The receiver runs its own clock at 16 ticks per bit and samples at tick
// 8, 24, 40... counted from the last edge it saw on the line, so every edge re-times it. With `unstuff`,
// it drops the bit that follows five equal bits (the transmitter's stuffed bit).
module edge_rx(input wire clk, rx);
  reg prev = 1; int count = 0, nbits = 0, edges = 0, run = 0; bit active = 0, unstuff = 0, skip = 0, log = 0;
  reg bits[0:1023]; reg last = 1;
  always @(posedge clk) begin
    if (rx !== prev) begin prev = rx; count = 0; edges++; if (!active) active = 1; end
    else count++;
    if (active && count % 16 == 8) begin
      if (log) $display("SAMPLE embedded %0.2f", $realtime);
      if (skip) skip = 0;
      else begin bits[nbits] = rx; nbits++; end
      run = (rx === last) ? run + 1 : 1; last = rx;
      if (unstuff && run == 5) begin skip = 1; run = 0; end
    end
  end
endmodule

// The transmitter for the embedded-clock line: bits at 16 * (1 + error) ns, optionally stuffing the
// opposite bit after five equal bits on the wire.
module edge_tx(output reg line = 1);
  real error = 0.0; bit stuff = 0; int stuffed = 0, run = 0; reg last = 1;
  task automatic bit_out(input bit v);
    line = v; #(16.0 * (1.0 + error));
    run = (v === last) ? run + 1 : 1; last = v;
    if (stuff && run == 5) begin line = !v; stuffed++; last = !v; run = 1; #(16.0 * (1.0 + error)); end
  endtask
  task automatic start(); run = 0; last = 1; stuffed = 0; endtask
endmodule
Read included file: read_link.sv
`timescale 1ns/1ps
module read_link;
  real T = 16.0, flight = 0.0, t_co = 1.0, skew = 0.0;
  reg cclk = 0, pclk = 0, miso_p = 0, miso_c = 0, dq_p = 0, dq_c = 0, stb_p = 0, stb_c = 0;
  reg [7:0] value = 8'ha5, got_clock = 0, got_strobe = 0;
  sampler s_clock(miso_c), s_strobe(dq_c);
  int idx = 0;
  always @(cclk) pclk <= #(flight) cclk;                     // the clock reaches the peripheral
  always @(miso_p) miso_c <= #(flight) miso_p;               // MISO comes back
  always @(dq_p) dq_c <= #(flight) dq_p;                     // DQ comes back
  always @(stb_p) stb_c <= #(flight + skew) stb_p;           // the strobe comes back beside it
  always @(negedge pclk) if (idx < 8) begin
    idx++;
    if (idx < 8) begin miso_p <= #(t_co) value[7 - idx]; dq_p <= #(t_co) value[7 - idx]; end
    if (idx < 8) stb_p <= #(t_co + T / 2) !stb_p;
  end
  always @(posedge cclk) begin bit v; s_clock.take(v); got_clock = {got_clock[6:0], v}; end
  always @(stb_c) begin bit v; s_strobe.take(v); got_strobe = {got_strobe[6:0], v}; end
  // One read of `value`, bit 7 first: bit 7 is on MISO before the first clock.
  task automatic read(output bit ok_clock, output bit ok_strobe);
    s_clock.misses = 0; s_strobe.misses = 0; s_clock.armed = 0; s_strobe.armed = 0;
    idx = 0; miso_p = value[7]; dq_p = value[7]; #(T / 2 + flight + 1);
    stb_p = !stb_p;                                         // the strobe edge for bit 7
    #(T / 2);
    repeat (8) begin cclk = 1; #(T / 2); cclk = 0; #(T / 2); end
    #(4 * T + 2 * flight);
    ok_clock = (got_clock == value && s_clock.misses == 0);
    ok_strobe = (got_strobe == value && s_strobe.misses == 0);
  endtask
endmodule
Spot the bugA strobe link at 16 ns per bit: DQ changes at each bit boundary and STB toggles once in the middle of every bit. Each option is the receiver that shifts DQ in, bit 7 first. The bench sends 0xA5 and 0x3C. Which receiver is wrong?
ConceptAt 2 % rate error the embedded-clock line misreads a run of 26 equal bits, yet a UART at 2 % reads every frame. Why?
ConceptWhy does the strobe read keep working at every flight swept while the clocked read stops at a few nanoseconds?

Remember

  • A receiver learns when to sample from a clock wire, its own faster clock, a strobe sent with the data, or the data edges.
  • A clock driven by the far end makes a read pay the flight twice; a strobe travels with its data and pays only the skew between them.
  • Without a clock wire, timing error builds between edges, so long runs of equal bits break an embedded clock.
  • Line codes promise edges; UART gets its edge from every start bit and keeps its frames short.