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.

One frame of 0x31 at two cycles per bitThe byte is accepted at edge 2. Each bit lasts two cycles, and 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.
012345678910111213141516171819202122clkstSTARTDATAPARITYSTOPIDLEtickbit012345670txdtxd_bug
  1. Cycle 0

    Edge 2 accepts 0x31 and the state becomes START. The line is still high: txd is a flop, one cycle behind.

  2. 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.

  3. Cycle 3

    Edge 5: data bit 0 is on the line. 0x31 is 00110001, and least significant first means a 1 comes first.

  4. Cycle 17

    Edge 19: the slot of data bit 7, a 0 on txd. The txd_bug line already sends the parity bit, 1, because its bit counter counted the start bit and left DATA after seven bits.

  5. Cycle 19

    Edge 21: txd sends parity 1, since 0x31 has three ones. The txd_bug line is sending its stop bit here.

  6. 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.

The transmitter: one state machine, two countersTop row: the states, with the condition of each transition. Bottom row: the counters that produce those conditions.
The transmitter: one state machine, two countersTop row: the states, with the condition of each transition. Bottom row: the counters that produce those conditions. component IDLE line high, in_ready = 1 component START line 0 for onebit-time component DATA sh_q[0] for eightbit-times component PARITY par_q for onebit-time component STOP line 1 for onebit-time counter Baud counter tick everyCLKS_PER_BITcycles; held at 0 inIDLE counter Bit counter counts data bits only acceptticktick, bit 7tickticktickbit_q == 7
  1. Accept

    In IDLE in_ready is 1. At the edge where in_valid is 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
    1. data byte 0x31: IDLE → START
  2. 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
    1. data tick: Baud counter → START
    2. data byte 0x31: START → DATA
  3. 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
    1. data byte 0x31: DATA → PARITY
  4. 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 sentDecoder read (shipped design)Why
0x800x80 okthe parity bit, 1, lands in bit 7, which was 1 anyway
0x5a0x5a PARITY ERRORbit 7 is right by luck; the stop bit is read as parity
0xc30x43 okbit 7 reads the parity, 0; the stop bit happens to make the parity right
0x31, then 0x62 back to back0xb1 FRAME ERROR, then 0xfc PARITY ERRORthe 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

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1reset, asynchronous, active-low
in_validinput1a byte is accepted at an edge where in_valid and in_ready are 1
in_datainput[7:0]the byte to send
in_readyoutput11 only in IDLE
txdoutput1start bit 0; eight data bits, least significant first; even parity; one stop bit 1; the idle line is 1
Reset
reset drives txd high
Latency
txd is a register, one cycle behind the state
Throughput
at CLKS_PER_BIT = 4, in_ready is 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
  1. 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.

  2. 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.

  3. 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.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: 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 errors

Original 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

Predict the outputThe corrected transmitter runs at CLKS_PER_BIT = 4 with even parity and one stop bit. The bench counts the samples, one per edge, in which in_ready is 0 after a byte is accepted. What does it print?
`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;
  logic in_valid = 0; logic [7:0] in_data = 0; logic in_ready, txd;
  int n = 0;
  uart_tx #(.CLKS_PER_BIT(4)) dut (.*);
  // 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
    in_valid = 1; in_data = 8'ha5; tick(); in_valid = 0;   // accepted at this edge
    while (!in_ready) begin n++; tick(); end               // one sample per edge
    $display("in_ready low for %0d cycles", n);
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule
// uart_tx: sends each accepted byte as a serial frame on txd: a start bit (0), eight data bits
// (least significant first), an even parity bit and a stop bit (1). Every bit lasts CLKS_PER_BIT
// clock cycles. A byte is accepted at an edge where in_valid and in_ready are both 1.
module uart_tx #(parameter int CLKS_PER_BIT = 4) (
  input  logic       clk,
  input  logic       rst_n,      // active-low, asynchronous
  input  logic       in_valid,
  input  logic [7:0] in_data,
  output logic       in_ready,
  output logic       txd
);
  typedef enum logic [2:0] {IDLE, START, DATA, PARITY, STOP} state_e;
  state_e st_q, st_d;

  localparam int BW = $clog2(CLKS_PER_BIT);
  localparam logic [BW-1:0] LAST = BW'(CLKS_PER_BIT - 1);

  logic [BW-1:0] baud_q;                     // cycles spent in this bit
  logic [2:0] bit_q;                         // data bits sent
  logic [7:0] sh_q;                          // the byte, shifted right as it is sent
  logic       par_q;                         // its even parity bit

  wire tick   = (baud_q == LAST);            // the last cycle of a bit-time
  assign in_ready = (st_q == IDLE);
  wire accept = in_valid && in_ready;

  // Next state: every move after IDLE happens on the last cycle of a bit-time.
  always_comb begin
    st_d = st_q;
    unique case (st_q)
      IDLE:    if (accept) st_d = START;
      START:   if (tick) st_d = DATA;
      DATA:    if (tick && bit_q == 3'd7) st_d = PARITY;
      PARITY:  if (tick) st_d = STOP;
      STOP:    if (tick) st_d = IDLE;
      default: st_d = IDLE;
    endcase
  end

  // The line value of the current state.
  logic line;
  always_comb begin
    unique case (st_q)
      START:   line = 1'b0;
      DATA:    line = sh_q[0];
      PARITY:  line = par_q;
      default: line = 1'b1;                  // IDLE and STOP
    endcase
  end

  always_ff @(posedge clk or negedge rst_n)
    if (!rst_n) begin
      st_q <= IDLE; baud_q <= '0; bit_q <= '0; sh_q <= '0; par_q <= 1'b0;
      txd  <= 1'b1;                          // the idle line is high
    end else begin
      st_q   <= st_d;
      baud_q <= (st_q == IDLE || tick) ? '0 : baud_q + 1'b1;   // restarts with each bit
      if (accept) begin
        sh_q  <= in_data;
        par_q <= ^in_data;                   // 1 when the byte has an odd number of ones
        bit_q <= '0;
      end else if (tick && st_q == DATA) begin
        bit_q <= bit_q + 3'd1;
        sh_q  <= sh_q >> 1;
      end
      txd <= line;                           // the line leaves through a flop
    end
endmodule
Spot the bugEach option computes the even parity bit loaded with the byte. The bench sends 0x31 and 0x5a and expects the decoder to report no error. Which one fails?
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
ConceptThe baud counter is held at 0 in IDLE and starts counting when a byte is accepted. What would a free-running baud counter, never restarted, do to the frame?
ConceptWhy does the transmitter send txd through a flop, one cycle behind the state, when it could decode the line straight from the state?
Read the waveformIn the frame of 0x31 at two cycles per bit, which sample of txd carries the parity bit?
One frame of 0x31
012345678910111213141516171819202122stSTARTDATAPARITYSTOPIDLEtxd

Values after edges 2 to 24, left to right. The txd line follows the state one cycle later.

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.