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.
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.
- 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.
- 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.
- 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.
- 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
- A clock wire
One side drives a clock and both sides use its edges. SPI and I2C work this way.
- 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.
- A strobe from the sender
Whoever sends the data also sends the timing beside it, so both see the same delay.
- 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.
| Scheme | Wires | The receiver needs | Measured |
|---|---|---|---|
| Shared clock | CLK, DATA | the clock edge to arrive with the data | shared clock: a5 from 8 rising edges of CLK, 0 timing misses |
| Oversampled async | TX | its own clock at 16 times the rate, the same rate at both ends | async 16x: a5 from the receiver, framing error 0 |
| Strobe | STB, DQ | to use both strobe edges | strobe: a5 from 8 edges of STB, 0 timing misses |
| Embedded clock | LINE | an edge often enough to re-time | embedded 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 bit | Largest flight each way that still reads 0xA5 |
|---|---|
| Controller samples on its own clock | 3.25 ns |
| Controller samples on the peripheral's strobe | every flight swept, up to 8.00 ns |
| Strobe arriving late against its data, at 5 ns of flight | up 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 by | Longest run read right | First run misread |
|---|---|---|
| 1 % | 54 bits | 55 bits read as 56 |
| 2 % | 25 bits | 26 bits read as 27 |
| 5 % | 11 bits | 12 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.
Toward High-Speed Links
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.
| Scheme | Where it shows up | Lessons |
|---|---|---|
| Shared clock | SPI, QSPI and I2C: the controller drives the clock | P7.1, P7.3, P8.1 |
| Shared clock, held | An I2C target holds the clock low until it is ready | P8.5 |
| Oversampled async | UART: 16 ticks per bit and a rate budget | P6.3, P6.4 |
| Clock and edges | Why a sampling edge needs setup and hold | P1.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.
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 edgesRead 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
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.