The Receiver: Oversampling and the Sample Point

Explain why a UART receiver samples sixteen times per bit, find where it takes each sample, and show what a short glitch or a sample at the edge of a bit does.

Lesson
P6.3
Time
35 min
Runs on
Your browser
Check
5 questions

A receiver clocked at the bit rate worked on the bench, where both ends shared one oscillator. In the product each end had its own crystal. Its one sample fell early or late in each bit, depending on when a frame arrived. Late in the bit, a small rate difference walked it into the next one.

Measured sweepOne Sample per Bit Against SixteenThe same 64 seeded frames at each rate error, on one wire, into two receivers.
  • one sample per bit
  • sixteen per bit (rx0)
0102030405060Frames decoded of 64-3%-2%-1%01%2%3%Transmitter rate error0204060Frames decoded of 64-2%02%Transmitter rate error
Read the plot as a table
Transmitter rate errorone sample per bitsixteen per bit (rx0)
-3%4664
-2%5564
-1%6064
0%6464
1%5664
2%5564
3%5464

Measured. The receiver with one sample per bit loses frames from the first step and keeps 46 of 64 at -3 %; the sixteen-tick receiver decodes all 64 at every step shown.

One Sample per Bit

A receiver with one clock per bit samples wherever its clock edge falls. That place, the phase, is set by when the start edge arrived: anywhere from 0 to 16 ticks into the bit. Late in the bit there is little margin, and a rate error eats it bit by bit.

Rate errorOne sample per bitSixteen per bit
-3 %46 of 6464 of 64
-2 %55 of 6464 of 64
-1 %60 of 6464 of 64
0 %64 of 6464 of 64
1 %56 of 6464 of 64
2 %55 of 6464 of 64
3 %54 of 6464 of 64

Measured: the lesson's model rx_1x draws a phase per frame from a seeded generator; rx0 is the bench receiver on the same wire.

Sixteen Ticks per Bit

A clock sixteen times faster turns the phase into a count. The start edge is seen within one tick. Eight low ticks later is the middle of the start bit, and every sixteenth tick after that is the middle of the next bit.

The Sixteen-Tick ReceiverArrows show data moving from the wire to the holding register. Every box runs on the receiver's own tick clock.
The sixteen-tick receiverArrows show data moving from the wire to the holding register. Every box runs on the receiver's own tick clock. component rx wire no clock with it component Low-run counter counts consecutivelow ticks component Start check 8 low ticks: a start component Bit counter every 16th tick: asample component Vote 2 of the 3 ticksending on the sample component Shift register data bits, then stop component Holding register data, valid, flags 8gosample nowbitafter stop
  1. Find the start

    The low-run counter resets on every high tick. Eight lows in a row mean a start bit, and that tick is the middle of it.

  2. Count to each middle

    From the start, the bit counter fires every 16 ticks: the middle of data 0, data 1 and so on to the stop bit.

  3. Sample and deliver

    Each sample is a vote of three ticks. After the stop sample the byte and its flags go to the holding register.

Read the diagram as text
  • rx wire: no clock with it
  • Low-run counter: counts consecutive low ticks
  • Start check: 8 low ticks: a start
  • Bit counter: every 16th tick: a sample
  • Vote: 2 of the 3 ticks ending on the sample
  • Shift register: data bits, then stop
  • Holding register: data, valid, flags
  • rx wire → Low-run counter
  • Low-run counter → Start check: 8
  • Start check → Bit counter: go
  • Bit counter → Vote: sample now
  • Vote → Shift register: bit
  • Shift register → Holding register: after stop

Finding the start and finding the middle are one counter: eight ticks.

SampleTick after the start edgeReads
data 0241
data 1400
data 2561
data 3720
data 4881
data 51040
data 61201
data 71360
stop1521

Measured for 0x55 with sample_log = 1. Tick n is the n-th receiver clock after the edge. Data 0 is sampled at tick 24, the stop bit at tick 152, sixteen apart.

The middle of data 0 is 24 ticks after the start edge, but the edge falls anywhere between two clock edges. So tick 24 lands up to one tick before the middle, never after it. The placement error is one sixteenth of a bit, and it does not depend on luck.

Eight Low Ticks

One low tick is not a start: noise does that. The receiver waits for eight in a row. Measured on an idle line: a 3-tick low prints nothing, a 7-tick low nothing, and an 8-tick low RX ff ok, a frame nobody sent.

A 3-tick glitch on idle, tick by tick
01234567891011rxlow run01230rx0 in a frametwin in a frame

Measured, one column per tick. The default receiver counts three lows and resets. The twin with qualify_1_tick = 1 starts a frame on the first low tick.

Bench defaults

glitch(3); #300;
send_byte(8'h55);

Measured: UART 55 ok, RX 55 ok.

rx0.qualify_1_tick = 1

rx0.qualify_1_tick = 1;
glitch(3); #300;
send_byte(8'h55);

Measured: RX ff ok, UART 55 ok, RX aa FE.

The twin delivers 0xFF with no flag at all, then misreads the real frame too. Starting on the first low tick also moves every sample seven ticks early, onto the edge of each bit. One counter does both jobs, so breaking one breaks the other.

The start check as a small model for the formal lab: fire on the eighth low tick after a high, never sooner. BMC to depth 24: PASS; with the check at the first low tick it fails at step 2; the cover is reached at step 9 (measured by the serial spike).
module top(input clk,input rx);
 reg [3:0] ticks=0;reg fire=0;reg [7:0] history=8'hff;reg was_full=0;
 always @(posedge clk)begin
  if(rx)ticks<=0;else if(ticks<8)ticks<=ticks+1;
  fire<=!rx&&(ticks==7);
  history<={history[6:0],rx};was_full<=history==0;
  assert(fire==((history==0)&&!was_full));
 end
endmodule

Why the Middle

Bench defaults: 8, then 16

sample_log = 1;
send_byte(8'h55);

Samples at ticks 24, 40, 56, 72, 88, 104, 120, 136, 152. RX 55 ok.

rx0.sample_at_edge = 1: 8, then 8

rx0.sample_at_edge = 1;
sample_log = 1;
send_byte(8'h55);

Samples at ticks 16, 32, 48, 64, 80, 96, 112, 128, 144. RX aa FE.

Sampling at the edge of each bit fails even with matched clocks. The vote reads the three ticks ending on the sample, two of them in the bit before, so every bit shifts by one. A sample in the middle has half a bit of room on each side; lesson P6.4 measures how a rate error uses it.

Where You Will Meet It

Every UART receiver block, every bench monitor for one, and every bit-level problem in this module uses this counter. A design review asks where the sample sits; a bench proves it with a glitch on idle and a sweep of rate errors.

Asked in interviews: oversampling
  1. Why does a UART receiver oversample?

    There is no clock on the wire. A fast local clock lets it find the start edge within one tick and count to the middle of every bit.

  2. Why wait for eight low ticks?

    A short glitch must not start a frame, and eight ticks is also the middle of the start bit, so one counter both confirms the start and centres the samples.

  3. Why 16 and not 2?

    The sample can be off by one tick of the fast clock: half a bit at 2 times, a sixteenth at 16 times. Beyond that, rate mismatch dominates.

Try It

sample_log = 1 prints every sample the receiver takes, in ticks after the start edge. Data 0 is at tick 24: eight to the middle of the start bit, then sixteen. The stop sample is at tick 152, and the RX line follows it.

Add set_error(0.03); before send_byte: the ticks do not move, because the receiver counts its own clock. The wire moves under them; lesson P6.4 measures how far it can go. Then try glitch(7); and glitch(8); before the frame.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: tick 24: data 0 sample reads 1 tick 40: data 1 sample reads 0 tick 56: data 2 sample reads 1 tick 72: data 3 sample reads 0 tick 88: data 4 sample reads 1 tick 104: data 5 sample reads 0 tick 120: data 6 sample reads 1 tick 136: data 7 sample reads 0 UART 55 ok tick 152: stop sample reads 1 RX 55 ok
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: p6_lab.svh
// p6_lab.svh: lesson helpers. The wire monitor follows the transmitter's format and rate, so its
// UART line shows what was sent; the RX line shows what the receiver under test delivered.
bit rx_log = 1, sample_log = 0;
int ticks = 0, edge_tick = 0, last_left = 0, last_state = 0, last_index = 0;
always @(posedge clk) ticks++;
always @(negedge rx) if (rx0.state == 0) edge_tick = ticks;
always @(tx0.parity) mon.parity = tx0.parity;
always @(tx0.bits) mon.bits = tx0.bits;
always @(tx0.error) mon.bit_period = 16.0 * (1.0 + tx0.error);
always @(negedge clk) begin
  if (sample_log && last_left == 1 && rx0.left_ticks == 16) begin
    if (last_state == 1) $display("tick %0d: data %0d sample reads %b", ticks - edge_tick, last_index, rx0.sampled);
    else if (last_state == 2) $display("tick %0d: parity sample reads %b", ticks - edge_tick, rx0.sampled);
    else $display("tick %0d: stop sample reads %b", ticks - edge_tick, rx0.sampled);
  end
  last_left = rx0.left_ticks; last_state = rx0.state; last_index = rx0.index;
end
// The RX line is printed one bit-time after the receiver's stop sample, after the monitor's line.
always @(rx0.frames) if (rx_log) begin
  #16;
  if (overrun) $display("RX %02h %s overrun", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
  else $display("RX %02h %s", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
end
always @(posedge break_seen) if (rx_log) begin wait (rx == 1); #16 $display("RX break"); end
// A low pulse of the given number of ticks on the idle line.
task automatic glitch(input int width); tx0.tx = 0; #(width); tx0.tx = 1; endtask
// Send a byte and pull the line low for `width` ticks ending at receiver tick `at` after its start edge
// (tick n is the n-th clock edge after the edge), then give the line back its level.
int hit_at = 0, hit_width = 0; reg hit_level = 1; event hit_go;
always @(hit_go) begin
  #(hit_at - 0.5 - hit_width + 0.25); hit_level = tx0.tx; tx0.tx = 0; #(hit_width); tx0.tx = hit_level;
end
task automatic send_hit(input byte unsigned b, input int at, input int width);
  hit_at = at; hit_width = width; -> hit_go; send_byte(b);
endtask

Check Yourself

Predict the outputBench defaults, matched clocks (no rate error), rx0.sample_at_edge = 1, send_byte(8'h55). The monitor is off; the receiver prints its RX line. What does it print?
`timescale 1ns/1ps
module tb;
  reg clk = 0, rst_n = 0, take = 0;
  always #0.5 clk = ~clk;                       // one tick = one receiver clock; a bit is 16 ticks
  wire rx, valid, frame_err, parity_err, overrun, break_seen, frame_done;
  wire [7:0] data;
  uart_tx  tx0(rx);
  uart_rx  rx0(clk, rst_n, rx, take, data, valid, frame_err, parity_err, overrun, break_seen, frame_done);
  uart_mon mon(rx);
  `include "uart_tasks.svh"
  `include "p6_lab.svh"
  initial begin
    mon.trace = 1;
    #4 rst_n = 1; #4;
    take = 1; mon.trace = 0;
    rx0.sample_at_edge = 1;
    send_byte(8'h55);
    #64; $finish;
  end
endmodule
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: p6_lab.svh
// p6_lab.svh: lesson helpers. The wire monitor follows the transmitter's format and rate, so its
// UART line shows what was sent; the RX line shows what the receiver under test delivered.
bit rx_log = 1, sample_log = 0;
int ticks = 0, edge_tick = 0, last_left = 0, last_state = 0, last_index = 0;
always @(posedge clk) ticks++;
always @(negedge rx) if (rx0.state == 0) edge_tick = ticks;
always @(tx0.parity) mon.parity = tx0.parity;
always @(tx0.bits) mon.bits = tx0.bits;
always @(tx0.error) mon.bit_period = 16.0 * (1.0 + tx0.error);
always @(negedge clk) begin
  if (sample_log && last_left == 1 && rx0.left_ticks == 16) begin
    if (last_state == 1) $display("tick %0d: data %0d sample reads %b", ticks - edge_tick, last_index, rx0.sampled);
    else if (last_state == 2) $display("tick %0d: parity sample reads %b", ticks - edge_tick, rx0.sampled);
    else $display("tick %0d: stop sample reads %b", ticks - edge_tick, rx0.sampled);
  end
  last_left = rx0.left_ticks; last_state = rx0.state; last_index = rx0.index;
end
// The RX line is printed one bit-time after the receiver's stop sample, after the monitor's line.
always @(rx0.frames) if (rx_log) begin
  #16;
  if (overrun) $display("RX %02h %s overrun", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
  else $display("RX %02h %s", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
end
always @(posedge break_seen) if (rx_log) begin wait (rx == 1); #16 $display("RX break"); end
// A low pulse of the given number of ticks on the idle line.
task automatic glitch(input int width); tx0.tx = 0; #(width); tx0.tx = 1; endtask
// Send a byte and pull the line low for `width` ticks ending at receiver tick `at` after its start edge
// (tick n is the n-th clock edge after the edge), then give the line back its level.
int hit_at = 0, hit_width = 0; reg hit_level = 1; event hit_go;
always @(hit_go) begin
  #(hit_at - 0.5 - hit_width + 0.25); hit_level = tx0.tx; tx0.tx = 0; #(hit_width); tx0.tx = hit_level;
end
task automatic send_hit(input byte unsigned b, input int at, input int width);
  hit_at = at; hit_width = width; -> hit_go; send_byte(b);
endtask
Spot the bugEach option is the body of a start check that runs on every tick (low_run and start are declared; start is cleared before it). The bench sends a 3-tick glitch, then a frame whose only low bit is the start bit, and expects exactly one start, 8 ticks after the real edge. Which one is wrong?
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: p6_lab.svh
// p6_lab.svh: lesson helpers. The wire monitor follows the transmitter's format and rate, so its
// UART line shows what was sent; the RX line shows what the receiver under test delivered.
bit rx_log = 1, sample_log = 0;
int ticks = 0, edge_tick = 0, last_left = 0, last_state = 0, last_index = 0;
always @(posedge clk) ticks++;
always @(negedge rx) if (rx0.state == 0) edge_tick = ticks;
always @(tx0.parity) mon.parity = tx0.parity;
always @(tx0.bits) mon.bits = tx0.bits;
always @(tx0.error) mon.bit_period = 16.0 * (1.0 + tx0.error);
always @(negedge clk) begin
  if (sample_log && last_left == 1 && rx0.left_ticks == 16) begin
    if (last_state == 1) $display("tick %0d: data %0d sample reads %b", ticks - edge_tick, last_index, rx0.sampled);
    else if (last_state == 2) $display("tick %0d: parity sample reads %b", ticks - edge_tick, rx0.sampled);
    else $display("tick %0d: stop sample reads %b", ticks - edge_tick, rx0.sampled);
  end
  last_left = rx0.left_ticks; last_state = rx0.state; last_index = rx0.index;
end
// The RX line is printed one bit-time after the receiver's stop sample, after the monitor's line.
always @(rx0.frames) if (rx_log) begin
  #16;
  if (overrun) $display("RX %02h %s overrun", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
  else $display("RX %02h %s", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
end
always @(posedge break_seen) if (rx_log) begin wait (rx == 1); #16 $display("RX break"); end
// A low pulse of the given number of ticks on the idle line.
task automatic glitch(input int width); tx0.tx = 0; #(width); tx0.tx = 1; endtask
// Send a byte and pull the line low for `width` ticks ending at receiver tick `at` after its start edge
// (tick n is the n-th clock edge after the edge), then give the line back its level.
int hit_at = 0, hit_width = 0; reg hit_level = 1; event hit_go;
always @(hit_go) begin
  #(hit_at - 0.5 - hit_width + 0.25); hit_level = tx0.tx; tx0.tx = 0; #(hit_width); tx0.tx = hit_level;
end
task automatic send_hit(input byte unsigned b, input int at, input int width);
  hit_at = at; hit_width = width; -> hit_go; send_byte(b);
endtask
ConceptWhy do UART receivers oversample at 16 times and not at 2 times the bit rate?
Read the waveformThe measured glitch figure, one column per tick. In which column does the twin (qualify_1_tick = 1) start a frame?
01234567891011rxlow run01230rx0 in a frametwin in a frame

Columns 0 to 11; the glitch is three ticks long.

ConceptBench defaults. At which tick after the start edge does the receiver take its data 0 sample?

Remember

  • With one sample per bit the sample point depends on when the frame arrived, and a small rate error loses frames.
  • Sixteen ticks per bit place every sample within one tick of the middle of its bit.
  • Eight consecutive low ticks both confirm a start and land in the middle of the start bit: one counter, two jobs.
  • A receiver that starts on one low tick takes glitches as frames and samples every bit at its edge.
  • Sampling at the edge of a bit fails even with matched clocks when the vote looks back.

Practice