Skip to content
VLSI Mentor

UART · Module 7

The TX FSM and Frame Sequencing

Five states, eight transitions, and one rule that keeps the line honest: tx_o is a register written only inside the bit-boundary branch. Includes the off-by-one that emits nine data bits, and the state table the RTL was checked against.

Chapter 7.1 built a datapath that can produce every level a frame needs — a constant mark, a constant space, the shift register's bit 0, and a stored parity bit — and has no idea which one to produce.

This chapter builds the machine that decides. It is the transmitter's counterpart to Chapter 6.2, and the comparison is instructive: the two machines have the same five states and the same number of transitions, but every condition is different. The receiver's transitions test what arrived. The transmitter's test only where it is in a sequence it already knows.

That difference is the chapter. A receive FSM is a recogniser; a transmit FSM is a generator, and a generator has one property a recogniser does not need:

Between bit boundaries, the output must not move.

Everything below is organised around making that true by construction rather than by inspection.

1. The Rule That Keeps the Line Honest

Before the states, the invariant they exist to maintain:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
tx_o is a REGISTER, and it is written in exactly one place:
inside the `if (baud_tick_i)` branch.

That single structural fact delivers the stability property. There is no combinational path from the state, the shift register, the bit counter or the producer's bus to the pin, so nothing that changes between bit boundaries can move the line.

The alternative is to drive the output combinationally from state:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Conceptual SystemVerilog — combinational line generation.
// Correct in steady state, and it makes the output a function of every
// signal in the decode.
always_comb begin
    case (state_q)
        S_IDLE, S_STOP: tx_o = 1'b1;
        S_START:        tx_o = 1'b0;
        S_DATA:         tx_o = shreg_q[0];
        S_PARITY:       tx_o = par_q;
        default:        tx_o = 1'b1;
    endcase
end

This is not wrong, and plenty of working UARTs do it. What it gives up is worth naming precisely.

2. The Five States

Statetx_o sourceEntered whenOwnsLeft when
S_IDLEconstant 1reset, or a frame ended with nothing pendingnothinga tick, with work pending
S_STARTconstant 0a tick, with work pendingthe frame's first intervalnext tick
S_DATAshreg_q[0]a tick, from S_STARTthe payload field and bit_idx_qa tick at the last index
S_PARITYpar_qa tick, last data bit, parity enabledthe frame's parity intervalnext tick
S_STOPconstant 1a tick, last data bit (no parity) or from S_PARITYthe frame's final intervalnext tick

Two observations that a ring diagram hides, and both mirror Chapter 6.2's.

S_IDLE is not a waiting room for a request — it is a waiting room for a tick. A request can be accepted at any clock cycle (Chapter 7.4 owns the condition), but the frame does not begin until the next bit boundary. That keeps every interval exactly one bit period long, and Chapter 7.3 shows what the alternative costs.

S_DATA is the only state that lasts more than one tick. The other four are single-interval states, so their exit conditions are unconditional. All of the machine's conditional logic lives in one state, which is why the transition list is short.

3. Every Transition

Eight transitions. This table is the specification; §5's RTL was checked against it by enumeration in §6.

#FromToConditiontx_o becomes
1S_IDLES_STARTtick and work pending0
2S_STARTS_DATAtickshreg_q[0] — D0
3S_DATAS_DATAtick, not last indexshreg_next[0] — next bit
4S_DATAS_PARITYtick, last index, mode_q != NONEpar_q
5S_DATAS_STOPtick, last index, mode_q == NONE1
6S_PARITYS_STOPtick1
7S_STOPS_STARTtick and work pending0 — zero-gap
8S_STOPS_IDLEtick, nothing pending1

Plus default → S_IDLE for the three unreachable encodings of a three-bit state register, and a self-loop on S_IDLE that is the absence of a transition rather than a transition.

Transition 3 is a real transition, not a self-loop of convenience. It is where the shift happens and where bit_idx_q advances, and its tx_o value is the one §7 is about.

Transition 7 is what the module's registry blurb means by "no gaps the producer did not ask for." Chapter 7.4 owns the policy; the state machine's part is simply that S_STOP has two exits rather than one.

fsm
A five-state UART transmit state machine. From Idle, a bit-boundary tick with work pending moves to Start, where the line is driven to the space level. The next tick moves to Data, where the line carries the shift register's least significant bit. The machine remains in Data, shifting once per tick, until the final data index, at which point the captured parity mode selects the next state: Parity when parity is enabled for this frame, or Stop directly when it is not. From Parity the next tick moves unconditionally to Stop. Stop has two exits: if another byte is waiting the next tick goes straight back to Start with no idle interval between frames, and otherwise it returns to Idle. An illegal state encoding also returns to Idle.S_IDLES_STARTS_DATAS_PARITYS_STOPtick + pendingtick +pendingtick: drive D0tick: driveD0tick: shifttick: shiftlast + parity onlast + parity onlast + parity offlast + parity offtickticktick: nothing pendingtick: nothing pendingtick + pending: no gaptick + pending: no gaptick +pending: n…
Figure 1 — the complete transmit state graph. Every arc corresponds to exactly one row of the table above and to exactly one state_q assignment in §5's RTL. The two arcs leaving S_DATA differ only in the captured parity mode; the two leaving S_STOP differ only in whether another byte is waiting, and it is the second of those that makes zero-gap back-to-back transmission possible.

TX FSM trace — 0x53, 8N1

12 cycles
A trace of twelve bit intervals showing a UART transmit state machine emitting the byte 53 hexadecimal in an eight-data-bit, no-parity, one-stop-bit frame. The first interval is idle with the line at mark. The second is the start state with the line at space. The next eight intervals are the data state, with the bit index counting from zero to seven and the line carrying the payload least significant bit first as one, one, zero, zero, one, zero, one, zero. The eleventh interval is the stop state with the line at mark. The twelfth returns to idle. The data state is the only one occupying more than a single interval.S_DATA — eight intervalsS_DATA — eight intervalsstart — constant 0start — constant 0last data bitlast data bitstop — constant 1stop — constant 1state_qIDLESTARTDATADATADATADATADATADATADATADATASTOPIDLEbit_idx_q000123456777tx_ot0t1t2t3t4t5t6t7t8t9t10t11
Figure 2 — the machine walking one 8N1 frame carrying 0x53, extracted from simulation. Columns are BIT INTERVALS — one baud tick apart — not fabric-clock cycles. Each column shows the state that owned that interval and the level the line carried for the whole of it. S_DATA occupies eight consecutive intervals with bit_idx_q counting 0 through 7; every other state occupies exactly one.

4. The Configuration Branch Reads the Captured Copy

Transitions 4 and 5 are the only place configuration affects control flow, and the signal they test matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (mode_q == PARITY_NONE) state_q <= S_STOP;     // transition 5
else                       state_q <= S_PARITY;   // transition 4

mode_q, not parity_mode_i. Chapter 7.1 §5 captured the configuration at acceptance for exactly this reason. Testing the live input here would let a producer that reconfigures mid-frame change the shape of a frame already in flight — adding or removing a parity interval partway through, so the frame's length no longer matches what the far end is expecting. The result is a framing error at the receiver and a transmitter that looks correct in isolation.

This is the transmit-side counterpart of Chapter 6.4's status-ownership rule: a frame's properties are fixed when the frame is accepted, and everything downstream reads the frozen copy.

5. The RTL, Derived From the Table

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Synthesizable SystemVerilog — the transmit state machine and the line
// register. Capture and the shift register are Chapter 7.1; baud_tick_i is
// Module 8's, with its semantics defined in Chapter 7.3; the pending/accept
// logic is Chapter 7.4. This is sequencing and line generation only.
typedef enum logic [2:0] {
    S_IDLE   = 3'd0,
    S_START  = 3'd1,
    S_DATA   = 3'd2,
    S_PARITY = 3'd3,
    S_STOP   = 3'd4
} tx_state_e;

tx_state_e state_q;

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state_q <= S_IDLE;
        tx_o    <= 1'b1;          // idle is MARK — never a start pulse
    end else if (baud_tick_i) begin
        // EVERY line change lives inside this branch. That is the whole of
        // §1's stability guarantee, and it is structural rather than asserted.
        case (state_q)
            S_IDLE: begin
                if (pending_q || accept) begin
                    state_q   <= S_START;
                    bit_idx_q <= '0;
                    tx_o      <= 1'b0;            // transition 1
                end
            end
            S_START: begin
                state_q <= S_DATA;
                tx_o    <= shreg_q[0];            // transition 2 — D0
            end
            S_DATA: begin
                if (bit_idx_q == BI_W'(DATA_W - 1)) begin
                    // The captured mode, never the live input — §4.
                    if (mode_q == PARITY_NONE) begin
                        state_q <= S_STOP;        // transition 5
                        tx_o    <= 1'b1;
                    end else begin
                        state_q <= S_PARITY;      // transition 4
                        tx_o    <= par_q;
                    end
                end else begin
                    shreg_q   <= shreg_next;      // transition 3
                    bit_idx_q <= bit_idx_q + 1'b1;
                    // NOT shreg_q[0] — see §7.
                    tx_o      <= shreg_next[0];
                end
            end
            S_PARITY: begin
                state_q <= S_STOP;                // transition 6
                tx_o    <= 1'b1;
            end
            S_STOP: begin
                if (pending_q || accept) begin
                    state_q   <= S_START;         // transition 7 — zero gap
                    bit_idx_q <= '0;
                    tx_o      <= 1'b0;
                end else begin
                    state_q <= S_IDLE;            // transition 8
                    tx_o    <= 1'b1;
                end
            end
            default: begin
                state_q <= S_IDLE;
                tx_o    <= 1'b1;
            end
        endcase
    end
end

Three details are deliberate, and two of them mirror Chapter 6.2 §5's reasoning.

One always_ff, not a two-process next-state pattern. The next-state logic is this case statement; splitting it would add a combinational block containing the same eight decisions and put the transition list in two places, which is what makes §6's enumeration meaningful.

case, not unique case. unique asserts that a branch always matches, which is false by construction — default exists precisely for the encodings the enumeration does not cover. Tools that treat unique as a synthesis directive may take it as licence to optimise the recovery path away.

tx_o is assigned on every path that changes state, and only there. A path that changes state without assigning tx_o would leave the line showing the previous field's value for an extra interval — which is how a stop bit becomes two bits long.

6. Diagram, Table and RTL Are the Same Machine

Mechanically enumerated from the source above:

#TableRTL guardFigure 1 arc
1IDLE→STARTif (pending_q || accept)
2START→DATAunconditional
3DATA→DATAelse of the terminal test
4DATA→PARITYelse (parity enabled)
5DATA→STOPif (mode_q == PARITY_NONE)
6PARITY→STOPunconditional
7STOP→STARTif (pending_q || accept)
8STOP→IDLEelse

Eight rows, eight arcs, eight state_q assignments in the tick branch — and nine tx_o assignments, the extra one being the reset value. The counts were extracted from the source rather than eyeballed, which takes seconds and is the only way a diagram stays trustworthy as the RTL changes.

7. Two Off-By-Ones, and Only One Is Obvious

The terminal comparison. The convention, stated here and used unchanged for the rest of the module:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bit_idx_q is the index of the data bit CURRENTLY BEING DRIVEN.
It is loaded with 0 on entry to S_START, so during the first data
interval it reads 0, and during the last it reads DATA_W − 1.

Its width is derived rather than assumed, with the same guard Chapter 6.3 §5 used on the receive side, because $clog2(1) is 0 and a zero-width vector is illegal:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
localparam int unsigned BI_W = (DATA_W <= 1) ? 1 : $clog2(DATA_W);

So the last data bit is index DATA_W − 1:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (bit_idx_q == BI_W'(DATA_W - 1))     // CORRECT
if (bit_idx_q == BI_W'(DATA_W))         // WRONG — one extra data interval

The wrong form emits nine data bits in an eight-bit configuration. The frame is one interval too long, the receiver's stop sample lands on what the transmitter thinks is the stop bit but is actually the ninth data bit, and the far end reports a framing error on some payloads and not others — depending on whether that ninth bit happened to be mark. Data-dependent framing errors on a link whose configuration is correct is the signature.

The shift-and-drive ordering. This one is subtler, and it is where the nonblocking semantics matter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
shreg_q <= shreg_next;
tx_o    <= shreg_next[0];      // CORRECT
tx_o    <= shreg_q[0];         // WRONG — repeats the bit just sent

Both assignments are nonblocking, so shreg_q on the right-hand side is the old value — the bit currently on the wire. Driving tx_o from it repeats that bit and drops the last one, producing a byte where every bit after the first is shifted by one position. The frame length is correct, the start and stop are correct, and the payload is wrong in a way that looks like a bit-order problem but is not.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
capture       shreg_q = 0x53        tx_o for D0 = shreg_q[0]   = 1
first shift   shreg_next = 0x29     tx_o for D1 = shreg_next[0] = 1   <- correct
                                    tx_o for D1 = shreg_q[0]    = 1   <- wrong, but equal here

Note the trap in that last line: for 0x53 the first two bits are both 1, so the defect is invisible at D1 and first shows at D2. A test pattern whose adjacent bits differ everywhere — 0x55 or 0xAA — exposes it immediately, which is one of the few things those two patterns are genuinely good for.

8. Reset and Illegal States

tx_o resets to 1. This is not a style choice. A transmitter whose output resets low presents a permanent start condition to the far end: the receiver qualifies it, samples eight intervals of whatever follows, and delivers a fabricated byte — Chapter 6.2 §7 showed the receive side of exactly this. Worse, if reset is held, the line looks like a break condition.

S_IDLE is the only defensible reset state, for the same reason as the receiver: any other implies a frame is in progress that is not.

bit_idx_q, shreg_q, par_q and mode_q need no reset for correctness — all are written at acceptance or at S_START entry before anything reads them. They are reset anyway in Chapter 7.5's assembled module for simulation determinism, at negligible cost.

Reset during a frame truncates it, and the far end sees a malformed frame. That is a policy, not a protocol behaviour, and Chapter 7.5 §7 states what the receiver actually does with the truncated remainder — which is not "recovers cleanly".

default: state_q <= S_IDLE covers the three unreachable encodings. It costs nothing, is removed by synthesis if unreachability can be proven, and converts a permanent hang into a lost frame if it cannot.

9. Verification

Drive the FSM without a producer. Supply baud_tick_i and a pending indication directly and walk the graph. Every configuration's path is a handful of ticks, so all of them can be covered exhaustively.

Cover transitions, not states. One clean frame visits all five states and exercises six of the eight arcs. Transition 7 needs back-to-back traffic and the default arc needs forcing — neither appears in a nominal test.

Check the emitted sequence, not the state trace. A state machine that visits the right states in the right order can still drive the wrong level in each of them. Chapter 7.5 builds a monitor that reconstructs bytes from tx_o alone, which is the only check that tests the machine and the datapath together.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — the line moves ONLY at a bit boundary. This is §1's
// invariant stated as a property; against this architecture it is
// structurally true, and the assertion guards future edits.
property p_line_stable_between_ticks;
    @(posedge clk) disable iff (!rst_n)
        !$past(baud_tick_i) |-> $stable(tx_o);
endproperty
assert property (p_line_stable_between_ticks);

// Assertion — idle really is mark.
property p_idle_is_mark;
    @(posedge clk) disable iff (!rst_n)
        (state_q == S_IDLE) |-> tx_o;
endproperty
assert property (p_idle_is_mark);

// Assertion — a parity interval is emitted only when THIS frame was
// accepted with parity enabled. Uses the captured mode, per §4.
property p_parity_state_requires_captured_mode;
    @(posedge clk) disable iff (!rst_n)
        (state_q == S_PARITY) |-> (mode_q != PARITY_NONE);
endproperty
assert property (p_parity_state_requires_captured_mode);

// Assertion — the data index never exceeds the last valid index.
property p_bit_idx_in_range;
    @(posedge clk) disable iff (!rst_n)
        (state_q == S_DATA) |-> (bit_idx_q <= BI_W'(DATA_W - 1));
endproperty
assert property (p_bit_idx_in_range);

The first is the one worth writing even though the architecture makes it true by construction: it is cheap, and it fails loudly the day someone adds a combinational override to the output for a test mode and forgets to remove it.

10. What This Means on an FPGA

The registered output maps to a flip-flop next to the pin, which is the arrangement a timing-driven tool wants anyway. There is nothing to constrain beyond the normal output path.

Five states in three bits, or one-hot — let the tool choose. At this size the encoding is not worth deciding by hand; what is worth ensuring is that the default branch survives, and an over-eager unique or a full_case directive is what removes it.

state_q and tx_o are the two probes that resolve most transmit failures. A machine that never leaves S_IDLE has a request or tick problem; one that cycles correctly while the line is wrong has a datapath problem; one whose S_DATA visits are miscounted has §7's terminal off-by-one. Adding bit_idx_q makes the third immediate.

Check the synthesis report for latches. There should be none: every path in the always_ff either assigns or holds a register. As in Chapter 6.6 §1, that is a reviewed claim until a synthesis run confirms it.

11. Understanding Check

12. Summary

The transmit FSM is five states and eight transitions, mirroring the receiver's shape with entirely different conditions: a receive machine recognises, a transmit machine generates.

The output is a register written only inside the tick branch. That single structural fact is the line-stability guarantee — no combinational path from state, shift register, counter or producer bus reaches the pin. The combinational alternative is not wrong; it costs an output timing path that grows with the decode, a brief transient on a real wire, and the property that future signals cannot accidentally reach the line.

S_IDLE waits for a tick, not a request, so every interval is exactly one bit period. S_DATA is the only multi-tick state, so all conditional logic lives in one place.

The configuration branch reads mode_q, the captured copy — testing the live input would let a mid-frame reconfiguration change the frame's length while it is being emitted.

S_STOP has two exits, and the second is what makes zero-gap back-to-back possible.

Two off-by-ones: the terminal comparison against DATA_W instead of DATA_W − 1 emits nine data bits and produces data-dependent framing errors on a correctly configured link; the shift-and-drive ordering that reads shreg_q[0] instead of shreg_next[0] repeats a bit — and is invisible wherever adjacent payload bits are equal, which for 0x00 and 0xFF is everywhere.

tx_o resets to mark, because a transmitter that resets low presents a permanent start condition to the far end.

13. What Comes Next

Every transition above is gated on baud_tick_i, and this chapter has treated it as a given.

Chapter 7.3 makes it precise. It defines the tick's semantics — pulse width, which edge begins a bit interval, when the line updates — and then resolves the question this chapter deferred: what happens between a request being accepted and the frame actually starting. It compares launching immediately against waiting for the next boundary, shows why a free-running tick costs up to one bit period of latency and what buying that back would require, and derives the exact interval count of every supported configuration so that frame duration can be checked rather than assumed.

Browse the full path on the UART tutorials index. For the receive machine this one mirrors, read Chapter 6.2.

Continue learning

Where this fits

Part of the UART curriculum.