Altifigence Academy

63 / 63 · Concept

Capstone lab: UART transmitter and frame timing

Combine a divider counter, shift register, and flow control to produce 8N1 frames and verify bit boundaries.

Fix the frame specification first

Prerequisites: counters, shift registersRegister A circuit that stores multiple bits of state. The synchronous registers in this course store their specified inputs at a clock edge. Learn more, valid/ready, and synchronous resetReset A control that returns state to a specified initial value. Define whether it is synchronous or asynchronous and whether it has priority over other controls. Learn more.

UARTUART A device that communicates serially using start, data and stop bits without a separate clock wire. Sender and receiver must agree on the baud rate and frame format. 8N1 consists of one start bit, eight data bits, no parity, and one stop bit. Idle and stop are 1, start is 0, and data is sent LSBLSB The bit with the lowest place value. Check both the notation direction and the indexing rules. Learn more first. Every bit is held for the same duration. This lab covers transmission only and assumes a data provider using the same clock.

Bactual=fclkD,ϵ=Bactual−BtargetBtarget×100%B_{\mathrm{actual}}=\frac{f_{\mathrm{clk}}}{D},\qquad \epsilon=\frac{B_{\mathrm{actual}}-B_{\mathrm{target}}}{B_{\mathrm{target}}}\times100\%

For a target of 115200 baud at 48 MHz, an integer divisor D=417 can be chosen. The actual rate is approximately 115108 baud, with an error of about -0.080%. Review device specifications for the acceptable error including the receiver.

Expand 0x53 into transmission order

0x53 is written as 01010011, but the line carries the low bit first: 1, 1, 0, 0, 1, 0, 1, 0. Each cell in the diagram is a bit interval of D clocks, not one system clock cycle. One idle cell is shown on each side for comparison.

UART 8N1 · 0x53 · LSB first
View waveform data
Wave data: each character is one interval; a dot holds the previous state; p is a clock cycle.
SignalWaveBus values
tx101.0.10101.
phase234523452345idle → start → d0 → d1 → d2 → d3 → d4 → d5 → d6 → d7 → stop → idle

The interval from start through stop is exactly 10D clocks. Consecutive ones or zeros each occupy a full bit interval; the absence of a line transition does not remove a bit.

Capture a byte of data only once

The implementation below requires CLKS_PER_BIT to be a constant of at least 1. It does not accept input while busy and uses only the data captured at frame start. Instead of creating a new bit clock, it uses a counter within the original clock domain to control advancement.

SystemVerilog
module uart_tx #(
    parameter int unsigned CLKS_PER_BIT = 417,
    localparam int CW = (CLKS_PER_BIT <= 1) ? 1 : $clog2(CLKS_PER_BIT)
) (
    input  logic       clk, rst,
    input  logic       in_valid,
    output logic       in_ready,
    input  logic [7:0] in_data,
    output logic       tx, busy, done
);
    logic [9:0] frame;
    logic [3:0] bits_left;
    logic [CW-1:0] ticks_left;
    assign in_ready = !busy;

    always_ff @(posedge clk) begin
        if (rst) begin
            tx         <= 1'b1;
            busy       <= 1'b0;
            done       <= 1'b0;
            bits_left  <= 4'd0;
            ticks_left <= '0;
        end else begin
            done <= 1'b0;
            if (!busy) begin
                if (in_valid) begin
                    frame      <= {1'b1, in_data, 1'b0};
                    tx         <= 1'b0;
                    busy       <= 1'b1;
                    bits_left  <= 4'd10;
                    ticks_left <= CW'(CLKS_PER_BIT-1);
                end
            end else if (ticks_left != 0) begin
                ticks_left <= ticks_left - 1'b1;
            end else if (bits_left == 4'd1) begin
                tx   <= 1'b1;
                busy <= 1'b0;
                done <= 1'b1;
            end else begin
                frame      <= {1'b1, frame[9:1]};
                tx         <= frame[1];
                bits_left  <= bits_left - 1'b1;
                ticks_left <= CW'(CLKS_PER_BIT-1);
            end
        end
    end
endmodule

Let E0 be the input-acceptance edge. tx changes to start=0, then advances to the next bit at intervals of exactly D clocks. The stop bit begins at E0+9D; at E0+10D, busy falls and done pulses for one cycle. The final stop interval must also last the full D cycles.

In this implementation, in_ready remains 0 until just before the completion edge, so the earliest acceptance of the next frame is the following edge. Consecutive frame acceptances are therefore separated by 10D+110D+1 clocks. This one-clock idle gap is part of normal operation.

Predict the bit sequence by hand

The data bits of 8'h53, starting with the LSB, are 1,1,0,0,1,0,1,0. The complete frame has this order:

Orderstartd0d1d2d3d4d5d6d7stop
tx0110010101

Verification must not merely count tx transitions, because consecutive equal bits produce none. Sample the center of each bit interval relative to frame start and compare it with the expected bit. Also check start and stop durations. Use small divisors such as D=1, 2, and 3 for fast unit verification.

Extend the design by connecting a FIFO

A FIFOFIFO A queue that removes data in the order in which it was inserted. Rules for full, empty and simultaneous reads and writes are part of its behavioral contract. Learn more with registered reads updates dout and dout_valid immediately after the pop-acceptance edge. The next circuit using the same clock samples the response at the following rising edgeRising edge The instant at which the clock changes from 0 to 1. Distinguish it from a level, which refers to the entire interval during which CLK=1. Learn more. Sending dout to the UART at the very edge that accepts pop can send the previous value. Add a one-entry buffer for the returned byte and a pending state, then deliver it exactly once when the UART is ready.

Final verification includes 00, FF, 01, 80, 55, and 53; inputs changing while busy; consecutive frames; and reset during a frame. The reset policy aborts transmission and returns tx to idle=1. The receiver may nevertheless see a valid frame, so UART framing alone cannot guarantee detection of an aborted transfer. If the affected data must be discarded, define a system contract that coordinates receiver reset with the transmitter or provides a separate abort signal to the receiver.

Further reading: Microchip — EUSART and 8N1 framing

Try it yourself

With CLKS_PER_BIT=4, data is accepted at E0. Find the edges at which d0 starts, stop starts, and done occurs. If a new in_valid=1 is present at E40, is the next byte accepted at that edge?

Read the explanation

d0 starts just after E4, stop just after E36, and done just after E40. Immediately before E40, busy=1 and therefore in_ready=0, so the new byte is not yet accepted. The sender must hold valid and data; the next byte can be accepted at E41. Each frame lasts 40 clocks, and the minimum acceptance interval is 41 clocks.

Your choice applies to this browser. Change it any time using the footer.