UART · Module 11
Configuration, Control and Status Plumbing
Routing configuration to both halves and status back, with defined behaviour when a change arrives mid-traffic — because a format applied to one half and not the other is worse than not applying it at all.
Chapter 11.2 treated configuration as a signal that arrives and is used. This chapter asks what happens when it changes, which is where an IP differs from a block in a way that has caught many designs.
Chapter 7.5 §3 already solved a version of this: the transmitter captures the parity mode at acceptance so a mid-frame reconfiguration cannot reformat the frame it is emitting. That was correct and it is not sufficient at the top level, for a reason that only exists once both halves share one configuration:
A parity change that reaches the transmitter and the receiver at different moments makes one IP disagree with itself — a frame is sent under one format and decoded under another, on the same device.
That failure is not detectable by either half. The transmitter emitted a well-formed frame; the receiver decoded a well-formed frame; they simply were not the same frame. §3 builds the deferral that prevents it.
1. What Is Configuration and What Is Control
Two kinds of input arrive from above, and conflating them produces an IP that is hard to drive:
| Configuration | Control | |
|---|---|---|
| Shape | a level, held | a one-cycle pulse |
| Examples | cfg_parity_i, cfg_hw_flow_en_i, cfg_loopback_i | cfg_tx_flush_i, cfg_rx_flush_i, cfg_err_clear_i |
| Question | how should the IP behave? | do this now |
| Persists | yes — it is the current setting | no — it is an action |
| Safe to re-apply | yes, idempotent | no — each pulse is another action |
Control inputs must be pulses, not levels. A flush held high keeps its FIFO in reset, which presents as a queue that is permanently empty while the write port stays ready — an easy defect to create in a register decode and a confusing one to read. Module 13 will generate these from register writes, and the one-cycle shape is part of the contract this IP publishes.
2. Distribution: Both Halves, One Value
The parity mode reaches the transmitter and the receiver. The naive distribution wires the input straight to both:
// WRONG at the top level — each half sees the change at its own moment.
uart_tx u_tx (... .parity_mode_i(cfg_parity_i) ...);
uart_rx u_rx (... .parity_mode_i(cfg_parity_i) ...);This looks safe because Chapter 7.5 established that the transmitter captures at acceptance and Chapter 6.4 that the receiver's frame shape is fixed at its start. Both are true, and they fix different frames at different moments.
3. Commit on Quiet
The fix is to hold the change at the top level until neither half is mid-anything, then apply it to both on one edge:
// Synthesizable SystemVerilog — configuration shadow and commit.
// A change is recorded immediately and applied only when the link is quiet,
// so both halves switch format on the SAME edge.
parity_mode_t cfg_parity_q; // what both halves actually see
parity_mode_t cfg_parity_shadow_q; // what software asked for
logic cfg_pending_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cfg_parity_q <= PARITY_NONE;
cfg_parity_shadow_q <= PARITY_NONE;
cfg_pending_q <= 1'b0;
end else begin
if (cfg_parity_i != cfg_parity_shadow_q) begin
cfg_parity_shadow_q <= cfg_parity_i;
cfg_pending_q <= 1'b1;
end
if (cfg_pending_q && link_quiet) begin
cfg_parity_q <= cfg_parity_shadow_q;
cfg_pending_q <= 1'b0;
end
end
endlink_quiet is stricter than busy_o, and the difference matters:
// Nothing transmitting, nothing queued to transmit, no frame being received.
assign link_quiet = !tx_busy && tx_fifo_empty && (rx_state == S_IDLE);It excludes receive FIFO occupancy, for the same reason Chapter 11.1 §6 excluded it from busy_o: unread bytes already have their format — they were decoded under the old configuration and their status travels with them — so waiting for software to read them would mean a configuration change could be blocked indefinitely by a consumer that is simply slow.
It includes the TX FIFO, because a queued byte has not yet been launched and would be sent under whichever format is active when it is. Letting a change commit with bytes still queued would send them under the new format when the producer wrote them expecting the old one.
Measured behaviour
From the integration suite, with a change requested while three frames were in flight:
| Moment | cfg_parity_q | cfg_pending_q | Observation |
|---|---|---|---|
| quiet, change requested | applied immediately | 0 | no traffic to disturb |
| 3 bytes queued, change requested | PARITY_NONE | 1 | deferred |
| in-flight bytes completing | PARITY_NONE | 1 | all 3 decoded under the old format |
| link quiet again | PARITY_EVEN | 0 | committed |
mid-traffic: link not quiet pass
config change DEFERRED while busy pass
in-flight bytes complete under OLD config (3/3) pass
config commits once quiet passChange requested during traffic, committed when quiet
8 cyclesThe policy is a choice, and alternatives are defensible. Apply immediately is correct for an IP whose software always quiesces the link first — and it makes that quiescing the software's responsibility, unenforced. Apply on the next frame boundary is finer-grained and lets the two halves disagree for one frame, which is the failure §2 describes. Committing on quiet is the conservative choice, and its cost is that a change on a permanently busy link never commits — which a status bit exposing cfg_pending_q makes visible rather than mysterious.
4. What a Disabled Feature Drives
An integration-only question with a wrong answer that looks tidy.
// Synthesizable SystemVerilog — a disabled feature drives the PERMISSIVE
// value, not its inactive electrical level.
assign rts_n_o = cfg_hw_flow_en_i ? rts_n_int : 1'b0; // 0 = "may send"
uart_tx_gate u_gate (
.cts_n_i(cfg_hw_flow_en_i ? cts_n_i : 1'b0), // 0 = permitted
...);Disabled hardware flow control must not look like backpressure. Driving rts_n_o to its inactive level when the feature is off would tell the far end to stop on a link that never intended to use flow control at all — a link that would then appear dead for no reason anyone could see in either device's configuration.
Equally, a disabled cts_n_i must read as permitted. Chapter 10.5 §7 made the gate's synchroniser reset to not permitted, which is right for a link that uses flow control and wrong for one that does not. Forcing the permissive value when the feature is disabled resolves it, and the two lines above are the whole mechanism.
The general rule: a disabled feature drives the value that makes the rest of the design behave as though the feature did not exist. That is frequently not the same as the signal's idle or reset level, and the difference is where integration defects live.
The same reasoning applies to cfg_sw_flow_en_i: with software flow control disabled the detector is inert, 0x13 is payload, and tx_paused_o stays low — which Chapter 10.6 §2 built as the transparency escape and which the top level simply must not defeat.
5. Status, From the Consumer's Side
The IP exposes eleven status outputs, and they fall into three groups with different lifetimes — the distinction Chapter 9.6 §1 established, now visible at the boundary:
| Group | Signals | Lifetime | Cleared by |
|---|---|---|---|
| per-byte | rd_parity_err_o, rd_frame_err_o | valid with rd_valid_o | popping the byte |
| live condition | tx_empty_o, rx_empty_o, busy_o, tx_trigger_o, rx_trigger_o, break_active_o | continuous | the condition ending |
| sticky history | err_frame_o, err_parity_o, err_overrun_o, err_break_o, any_error_o | until cleared | cfg_err_clear_i |
All three are needed and none substitutes for another. Per-byte status answers was this byte good; live conditions answer what is happening now; sticky flags answer has anything gone wrong since I looked. A design exposing only the third cannot attribute an error to a byte; one exposing only the first forces software to catch every byte.
break_active_o is deliberately in the middle group and has a sticky counterpart in the third. Chapter 9.4 §3 argued both are needed: is the link broken right now and was it ever are different questions, and a UART IP that merges them cannot answer the first.
Module 13 maps these to registers, and the mapping is not one-to-one — a status register typically packs the sticky group into one word with write-one-to-clear, exposes the live group as read-only, and delivers per-byte status alongside the data register. None of that is this IP's concern, and keeping it out is what lets the same IP sit behind different bus protocols.
6. Verification
The mid-traffic test is the one that matters, and it must check three things rather than one: that the change is deferred, that in-flight bytes complete under the old format, and that it commits afterwards. Checking only the last would pass on an implementation that applied the change immediately and corrupted the frames.
// Assertion — the active configuration never changes while the link is busy.
// This is §3's policy stated directly; it fails on a straight-through wiring.
property p_config_stable_while_busy;
@(posedge clk) disable iff (!rst_n)
!link_quiet |=> $stable(cfg_parity_q);
endproperty
assert property (p_config_stable_while_busy);
// Assertion — a pending change eventually commits, provided the link goes
// quiet. States the liveness half, which the stability property does not.
property p_pending_commits_when_quiet;
@(posedge clk) disable iff (!rst_n)
(cfg_pending_q && link_quiet) |=> !cfg_pending_q;
endproperty
assert property (p_pending_commits_when_quiet);
// Assertion — both halves always see the SAME value. Trivially true by
// construction here, and worth stating because the defect §2 describes is
// exactly a violation of it.
property p_halves_agree;
@(posedge clk) disable iff (!rst_n)
u_tx.parity_mode_i == u_rx.parity_mode_i;
endproperty
assert property (p_halves_agree);
// Assertion — disabled hardware flow control never requests a stop.
property p_disabled_fc_is_permissive;
@(posedge clk) disable iff (!rst_n)
!cfg_hw_flow_en_i |-> !rts_n_o;
endproperty
assert property (p_disabled_fc_is_permissive);The third is the one to keep. It is true by construction in this implementation and it is precisely what a "simplification" back to straight-through wiring would break — and it would break silently, since both halves would still be individually well-behaved.
Test each disabled feature's drive value. Three features, three checks, each asserting the permissive value rather than the inactive one. From the suite: hw flow disabled -> rts permitted passes at reset, before anything else runs.
7. Debugging
8. What This Means on an FPGA
The plumbing is a shadow register, a pending flag and a comparator. Three flip-flops plus the mux terms for the disabled features. It is the cheapest thing in the IP and it prevents a class of defect that is expensive to find.
Expose cfg_pending_q. One bit, and it turns "my configuration write did nothing" into "the change is queued and the link has not been quiet". Module 13 should carry it into the status register.
Keep the control inputs one cycle. A register write naturally produces a pulse; a register bit that software sets and clears does not. The IP's contract says pulse, and an adapter that gets it wrong produces a permanently-held flush that looks like a dead queue.
The disabled-feature mux is on a pin path. rts_n_o passes through one mux before the pad — negligible, but worth knowing it is there when reading a timing report.
9. Understanding Check
10. Summary
Configuration is a held level; control is a one-cycle pulse. Conflating them produces a flush that holds its FIFO in reset and reads as a dead queue.
Per-block capture is not enough once blocks share a configuration. The transmitter freezes format at acceptance and the receiver at the start edge; a change between those instants makes one IP disagree with itself, sending under one format and decoding under another with neither half able to detect it.
The fix is shadow-and-commit on quiet. A change is recorded immediately and applied only when nothing is transmitting, nothing is queued to transmit and no frame is being received — so both halves switch on the same edge. Verified with three frames in flight: deferred, all three completed under the old format, committed afterwards.
link_quiet excludes receive occupancy and includes transmit occupancy, because the first is finished business and the second has not yet chosen its format.
A disabled feature drives its permissive value, not its inactive level. Disabled hardware flow control that drives stop produces a dead link with nothing in either configuration to explain it.
Status falls into three groups with three lifetimes — per-byte, live condition and sticky history — and none substitutes for another. Mapping them to registers is Module 13's, and keeping that out is what lets one IP sit behind different buses.
And the assertion worth keeping is that both halves always see the same value — true by construction, and precisely what a plausible simplification would break silently.
11. What Comes Next
The IP now works and is correctly plumbed. Chapter 11.4 asks a different question: will it survive being used again?
That means deciding what belongs to a parameter and what belongs to a register — a distinction this module has applied without stating — and organising the RTL so a second project can adopt it without editing it. It is the least glamorous chapter in the module and the one that determines whether the previous ten were worth the effort.
Browse the full path on the UART tutorials index. For the per-frame capture this chapter builds on, read back to Chapter 7.5.
Continue learning
Related tutorials
- Related topic
Integrating TX, RX, Baud Generation and FIFOs
The details that only appear at the top level: one generator feeding both halves, a flush that must not corrupt a frame in flight, and several distinct loss points that must feed one honest overrun flag.
- Related topic
Top-Level UART IP Architecture
Ten independently verified blocks and the interfaces that join them. The top level adds no new function; what it adds is fan-out, configuration distribution, status aggregation and the decisions that only exist once blocks meet.
- Related topic
Parity Check, Stop-Bit Validation and Error Status
The receiver decides whether a frame was good, then faces the harder question: which frame does each status flag describe? Getting that wrong lets an incoming frame rewrite the status of a byte the consumer has not yet read.
- Related topic
Complete TX RTL Architecture and Configuration Handling
One synthesizable transmitter assembled from the module's four preceding chapters, with the configuration-capture discipline that keeps a frame's format fixed once it starts — then reviewed the way a reviewer would, including the defect found during its own development.
Where this fits
Part of the UART curriculum.
