Capstone: a UART transmitter
Turn a brief into a UART transmitter: a state machine, a baud counter, a bit counter and a shift register that send start, data, parity and stop bits a serial decoder reads back.
- Lesson
- R3.6
- Time
- 35 min
- Runs on
- Your browser
- Check
- 5 questions
A UART transmitter sent 0x80 and the receiver read 0x80. Then 0x5a arrived with a parity error, 0xc3 arrived as 0x43 with no error, and two bytes sent back to back gave a framing error. The bit counter had also counted the start bit. So the transmitter sent seven data bits, then the parity bit one bit-time early.
tick marks the last cycle of each bit-time. The txd output is a register, so the line follows the state one cycle later. The txd_bug row is the shipped design on the same input.- Cycle 0
Edge 2 accepts 0x31 and the state becomes START. The line is still high: txd is a flop, one cycle behind.
- Cycle 1
Edge 3: the line falls. This is the start bit, two cycles low. The baud counter reached its last cycle, so tick is 1.
- Cycle 3
Edge 5: data bit 0 is on the line. 0x31 is 00110001, and least significant first means a 1 comes first.
- Cycle 17
Edge 19: the slot of data bit 7, a 0 on
txd. Thetxd_bugline already sends the parity bit, 1, because its bit counter counted the start bit and left DATA after seven bits. - Cycle 19
Edge 21:
txdsends parity 1, since 0x31 has three ones. Thetxd_bugline is sending its stop bit here. - Cycle 21
Edge 23: the stop bit, high. The frame took 11 bit-times: start, eight data bits, parity, stop.
Measured with a probe of the lesson's design at CLKS_PER_BIT = 2, values after edges 2 to 24. The two lines differ only in the slot of data bit 7.
From brief to sketch
A UART (universal asynchronous receiver-transmitter) sends bytes over one wire with no clock beside them. The line idles high. A frame starts with a start bit (0), sends eight data bits least significant first, adds a parity bit, and ends with a stop bit (1). Both ends agree on the baud rate, the number of bits per second. With even parity the parity bit makes the count of ones in data plus parity even.
The sketch has four parts. A baud counter counts the cycles of one bit-time and raises tick in its last cycle. A bit counter counts the data bits sent. A shift register holds the byte and moves it right one place per data bit. A state machine ties them together, and moves only on tick.
- Accept
In IDLE
in_readyis 1. At the edge wherein_validis also 1, the byte and its parity are loaded, the bit counter clears, and the state becomes START. The baud counter, held at 0 in IDLE, starts counting with the start bit.Follow the calls, values and waits
- data byte 0x31: IDLE → START
- Bit-times
Every transition after IDLE waits for
tick, the last cycle of a bit-time. The baud counter restarts at each tick, so every bit lasts exactly CLKS_PER_BIT cycles.Follow the calls, values and waits
- data tick: Baud counter → START
- data byte 0x31: START → DATA
- Eight data bits
In DATA each tick shifts the byte right and adds one to the bit counter. At the tick where the counter holds 7, the eighth bit is ending, and the state moves to PARITY.
Follow the calls, values and waits
- data byte 0x31: DATA → PARITY
- Close the frame
PARITY sends the stored parity bit for one bit-time, STOP sends a 1 for one bit-time, and the machine returns to IDLE, ready for the next byte.
Read the diagram as text
- IDLE: line high, in_ready = 1
- START: line 0 for one bit-time
- DATA: sh_q[0] for eight bit-times
- PARITY: par_q for one bit-time
- STOP: line 1 for one bit-time
- Baud counter: tick every CLKS_PER_BIT cycles; held at 0 in IDLE
- Bit counter: counts data bits only
- IDLE → START: accept
- START → DATA: tick
- DATA → PARITY: tick, bit 7
- PARITY → STOP: tick
- STOP → IDLE: tick
- Baud counter → START: tick
- Bit counter → DATA: bit_q == 7
The counters and the shift register exist only to serve this state machine, so they live in its module rather than in blocks of their own.
Two counters that must agree
The bug in the hook is one line. The shipped bit counter adds one on every tick after a byte is accepted, including the tick that ends the start bit. By the seventh data bit it already reads 7, so DATA ends a bit early.
Counts every bit-time
end else if (tick) begin
bit_q <= bit_q + 3'd1;
if (st_q == DATA) sh_q <= sh_q >> 1;
end
The start bit is counted as data. Seven data bits go out, then parity in the slot of bit 7.
Counts data bits
end else if (tick && st_q == DATA) begin
bit_q <= bit_q + 3'd1;
sh_q <= sh_q >> 1;
end
The counter and the shift register move together, once per data bit. Eight bits, then parity.
The bench's serial decoder shows what a receiver makes of each frame. It waits for the line to fall, waits half a bit-time to reach the middle of the start bit, then samples once per bit-time. Sampling in the middle leaves the most room for a clock that runs a little fast or slow.
| Byte sent | Decoder read (shipped design) | Why |
|---|---|---|
| 0x80 | 0x80 ok | the parity bit, 1, lands in bit 7, which was 1 anyway |
| 0x5a | 0x5a PARITY ERROR | bit 7 is right by luck; the stop bit is read as parity |
| 0xc3 | 0x43 ok | bit 7 reads the parity, 0; the stop bit happens to make the parity right |
| 0x31, then 0x62 back to back | 0xb1 FRAME ERROR, then 0xfc PARITY ERROR | the next start bit lands where the decoder samples the stop bit |
From the lab's run at CLKS_PER_BIT = 4. With the fix, all five bytes read back with ok.
The lab: the transmitter against the decoder
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | clock |
| rst_n | input | 1 | reset, asynchronous, active-low |
| in_valid | input | 1 | a byte is accepted at an edge where in_valid and in_ready are 1 |
| in_data | input | [7:0] | the byte to send |
| in_ready | output | 1 | 1 only in IDLE |
| txd | output | 1 | start bit 0; eight data bits, least significant first; even parity; one stop bit 1; the idle line is 1 |
- Reset
- reset drives
txdhigh - Latency
txdis a register, one cycle behind the state- Throughput
- at CLKS_PER_BIT = 4,
in_readyis low for 44 cycles per byte; back-to-back bytes start 45 cycles apart - Parameters
CLKS_PER_BIT, the bit-time in cycles, 2 or more; the baud counter restarts when a byte is accepted- Corner cases
- bit 7 at 0 and at 1; odd and even numbers of ones; back-to-back bytes; another CLKS_PER_BIT
The lab ships the transmitter with the bit counter of the left-hand compare. Run it and read the decoder's five lines: one ok byte, a wrong byte with no error, and three errors. Apply the right-hand version and run again: five bytes, all ok. The bench fixes CPB at 4 and builds its decoder with the same value, so the two always agree. Measured with 5 and 8 as well, all five bytes still read ok.
Asked in interviews
Draw a UART frame.
Idle high. A start bit at 0, eight data bits least significant first, an optional parity bit, then one or two stop bits at 1. Every bit lasts one bit-time, the clock period times the cycles per bit.
How does a receiver find the middle of a bit?
It watches for the falling edge of the start bit, waits half a bit-time, and checks the line is still 0. From there it samples once per bit-time, so each sample sits near the middle of its bit.
What does the baud rate have to do with the clock?
The transmitter counts clock cycles per bit: the clock frequency divided by the baud rate, rounded. A 50 MHz clock at 115200 baud gives 434 cycles. The rounding error must stay small, or the samples drift out of the bits.
Try it
Each line starts with the edge where the decoder saw the start bit begin. The frames are 49 edges apart at first: 40 busy cycles for a frame one bit short, one cycle in IDLE, and the bench's pause of two bit-times. The fourth frame is followed at once by the fifth, so the decoder finds a start bit where it expects the stop bit, and loses its place.
With the bit counter fixed, the decoder prints e3 rx 0x80 ok, e56 rx 0x5a ok, e109 rx 0xc3 ok, e162 rx 0x31 ok, e207 rx 0x62 ok and 5 frames, 0 errors. The back-to-back pair starts 45 edges apart: 44 busy cycles and one in IDLE. Synthesize drops from 76 cells to 75, with 20 flops and 6 levels of logic in both.
Synthesize the design to measure its cells and inspect the mapped gates.
e3 rx 0x80 ok e52 rx 0x5a PARITY ERROR e101 rx 0x43 ok e150 rx 0xb1 FRAME ERROR e203 rx 0xfc PARITY ERROR 5 frames, 3 errorsOriginal design · 76 cells · 20 flip-flops · 0 latches · 6 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic rst_n = 1; // falls at 1 ns: a real edge, so an asynchronous reset acts before any clock
int edge_n = -1;
always @(posedge clk) edge_n = edge_n + 1;
localparam int CPB = 4; // clock cycles per bit, for the transmitter and the decoder
logic in_valid = 0;
logic [7:0] in_data = 0;
logic in_ready, txd;
// Offer one byte and hold it until the transmitter takes it at an edge where in_ready is 1.
task automatic send(input logic [7:0] b);
in_valid = 1; in_data = b;
while (!in_ready) tick();
tick(); // the edge that accepts the byte
in_valid = 0;
endtask
// Wait until the transmitter is free, then leave the line idle for two bit-times.
task automatic pause();
while (!in_ready) tick();
repeat (2 * CPB) tick();
endtask
uart_tx #(.CLKS_PER_BIT(CPB)) dut (.*);
serial_decoder #(.CLKS_PER_BIT(CPB)) dec (.clk(clk), .line(txd));
// Advance to the next rising edge, wait for the design to settle, then print one line.
task automatic tick();
@(posedge clk); #1;
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, edges 0 and 1 in reset, released 1 ns after edge 1
send(8'h80); pause();
send(8'h5a); pause();
send(8'hc3); pause();
send(8'h31); send(8'h62); // back to back: the second waits for in_ready
pause();
repeat (12 * CPB) tick(); // let the decoder finish the last frame
$display("%0d frames, %0d errors", dec.frames, dec.errors);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Read included file: serial_decoder.sv
// serial_decoder.sv: the receiving end of the bench (read-only).
`timescale 1ns/1ps
// It samples the line once per cycle, 1 ns after each rising edge. When the line falls, the start
// bit has begun; the decoder waits half a bit-time to reach its middle, then samples every
// CLKS_PER_BIT cycles: eight data bits (least significant first), the parity bit, the stop bit.
// It prints one line per frame: the edge where the start bit began, the byte, and a verdict:
// e3 rx 0x5a ok e3 rx 0x5a PARITY ERROR e3 rx 0x5a FRAME ERROR (stop bit 0)
module serial_decoder #(parameter int CLKS_PER_BIT = 4) (input logic clk, input logic line);
int e = -1; // the number of the last rising edge
always @(posedge clk) e = e + 1;
int frames = 0, errors = 0;
task automatic skip(input int n); // move n samples on
repeat (n) begin @(posedge clk); #1; end
endtask
initial begin : rx
logic prev, stop, par;
logic [7:0] b;
int s;
prev = 1'b1;
forever begin
skip(1);
if (prev === 1'b1 && line === 1'b0) begin // the first low sample of a start bit
s = e;
skip(CLKS_PER_BIT / 2); // the middle of the start bit
if (line === 1'b0) begin
for (int k = 0; k < 8; k++) begin skip(CLKS_PER_BIT); b[k] = line; end
skip(CLKS_PER_BIT); par = line;
skip(CLKS_PER_BIT); stop = line;
frames++;
if (stop !== 1'b1) begin errors++; $display("e%0d rx 0x%h FRAME ERROR", s, b); end
else if ((^b ^ par) !== 1'b0) begin errors++; $display("e%0d rx 0x%h PARITY ERROR", s, b); end
else $display("e%0d rx 0x%h ok", s, b);
end
end
prev = line;
end
end
endmodule
Check yourself
Remember
- A UART frame is a start bit (0), eight data bits least significant first, an optional parity bit and a stop bit (1). Each bit lasts one bit-time, on a line that idles high.
- A baud counter that restarts when a byte is accepted makes every bit-time exactly CLKS_PER_BIT cycles; every transition after IDLE waits for its tick.
- The bit counter must count data bits only; counting the start bit sends parity one bit-time early, and only some bytes show it.
- A receiver samples each bit in its middle, half a bit-time after the falling edge of the start bit; registering txd keeps glitches off the wire.