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:
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:
// 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
endThis is not wrong, and plenty of working UARTs do it. What it gives up is worth naming precisely.
2. The Five States
| State | tx_o source | Entered when | Owns | Left when |
|---|---|---|---|---|
S_IDLE | constant 1 | reset, or a frame ended with nothing pending | nothing | a tick, with work pending |
S_START | constant 0 | a tick, with work pending | the frame's first interval | next tick |
S_DATA | shreg_q[0] | a tick, from S_START | the payload field and bit_idx_q | a tick at the last index |
S_PARITY | par_q | a tick, last data bit, parity enabled | the frame's parity interval | next tick |
S_STOP | constant 1 | a tick, last data bit (no parity) or from S_PARITY | the frame's final interval | next 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.
| # | From | To | Condition | tx_o becomes |
|---|---|---|---|---|
| 1 | S_IDLE | S_START | tick and work pending | 0 |
| 2 | S_START | S_DATA | tick | shreg_q[0] — D0 |
| 3 | S_DATA | S_DATA | tick, not last index | shreg_next[0] — next bit |
| 4 | S_DATA | S_PARITY | tick, last index, mode_q != NONE | par_q |
| 5 | S_DATA | S_STOP | tick, last index, mode_q == NONE | 1 |
| 6 | S_PARITY | S_STOP | tick | 1 |
| 7 | S_STOP | S_START | tick and work pending | 0 — zero-gap |
| 8 | S_STOP | S_IDLE | tick, nothing pending | 1 |
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.
TX FSM trace — 0x53, 8N1
12 cycles4. 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:
if (mode_q == PARITY_NONE) state_q <= S_STOP; // transition 5
else state_q <= S_PARITY; // transition 4mode_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
// 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
endThree 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:
| # | Table | RTL guard | Figure 1 arc |
|---|---|---|---|
| 1 | IDLE→START | if (pending_q || accept) | ✓ |
| 2 | START→DATA | unconditional | ✓ |
| 3 | DATA→DATA | else of the terminal test | ✓ |
| 4 | DATA→PARITY | else (parity enabled) | ✓ |
| 5 | DATA→STOP | if (mode_q == PARITY_NONE) | ✓ |
| 6 | PARITY→STOP | unconditional | ✓ |
| 7 | STOP→START | if (pending_q || accept) | ✓ |
| 8 | STOP→IDLE | else | ✓ |
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:
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:
localparam int unsigned BI_W = (DATA_W <= 1) ? 1 : $clog2(DATA_W);So the last data bit is index DATA_W − 1:
if (bit_idx_q == BI_W'(DATA_W - 1)) // CORRECT
if (bit_idx_q == BI_W'(DATA_W)) // WRONG — one extra data intervalThe 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:
shreg_q <= shreg_next;
tx_o <= shreg_next[0]; // CORRECT
tx_o <= shreg_q[0]; // WRONG — repeats the bit just sentBoth 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.
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 hereNote 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.
// 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
Related tutorials
- Related topic
The RX FSM
Five states, eight transitions, and a rule that keeps them enumerable: the next state depends on the current state, the configuration and one timing event — never on a data bit. Specified as a table first, with the RTL derived from it and checked against it.
- Related topic
Ready/Busy Handshake and Back-to-Back Frames
The write-side contract stated in cycles — what ready promises, how it differs from busy, when payload ownership transfers, and how accepting at the final boundary lets frames run with no gap and no shortened stop bit.
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
- Related topic
Frame Configurations: 8N1 and the Configuration Space
8N1 names three of the four choices a UART link depends on and omits the one most likely to be wrong. Reading the shorthand, computing what each configuration costs in intervals and line time, and why a longer frame spends timing margin as well as throughput.
Where this fits
Part of the UART curriculum.
