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 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.
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.
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.
`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
endmoduleHow 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.
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:
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:
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.
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 |
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.
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.
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.
The last sample is 11.5 bits after the start edge: e < (0.5 − 0.125) / 11.5, about 3.3% total.
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.
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.